npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

golf-shot-viz

v0.2.0

Published

3D golf shot visualizer for measured launch monitor trajectories. Three.js scene with studio and replay modes, volley playback, TrackMan adapter, React wrapper.

Readme

golf-shot-viz

3D golf shot visualizer for measured launch monitor trajectories.

npm CI license

Studio mode, 29 real range shots colored by club

Feed it flight paths from a launch monitor and it renders them in an interactive Three.js scene. Orbit camera, hover metadata, replay playback with reconstructed timing. Every shot in these images is a real measured trajectory, not a simulation.

Live demo · Options catalog

Reference consumer: swing-stack, a launch monitor telemetry dashboard. This library was extracted from it and powers its 3D shot view, installed from npm like any other dependency.

Why this exists

Open source golf 3D projects are simulators. They take launch parameters and compute physics. If you own launch monitor data, the flight is already measured. This library draws the measured points and skips physics entirely. That makes it small, and it makes the output true to what actually happened.

Replay

Every ball launches at once (volley) or staggered (sequence). Each shot flies with its own reconstructed timing, so fast balls visibly outpace slow ones and land in carry order.

Volley replay, every shot launching at once

Sequence replay of one club

Trajectories carry no per-point timestamps, but two anchors are measured: ball speed at launch and hang time. Playback models speed as v(t) = v0 · e^(-kt) and solves k so the distance integral over the hang time equals the spline arc length. Shots missing either anchor fall back to a mild ease-out.

Views

| Top (dispersion) | Side (gapping and apex) | | --- | --- | | Top-down dispersion view | Side view |

Install

npm install golf-shot-viz three

three is a peer dependency (>= 0.160). ESM only.

Quick start

import { ShotScene } from 'golf-shot-viz'

const scene = new ShotScene(document.querySelector('#viz'), {
  mode: 'studio',
  colorBy: 'club',
})

scene.setShots([
  {
    id: 'shot-1',
    // [downrange, height, side] in meters, Y-up
    points: [[0, 0, 0], [50, 15, -1], [100, 25, -3], [154, 0, -6]],
    meta: { club: '7 Iron', carry: 154.2, ballSpeed: 55.2, hangTime: 5.5 },
  },
])

scene.play({ order: 'volley' })

The container just needs a size. Zero config renders a sensible scene; every option is optional.

TrackMan data

import { ShotScene } from 'golf-shot-viz'
import { fromTrackman } from 'golf-shot-viz/trackman'

const scene = new ShotScene(container)
scene.setShots(fromTrackman(reportJson))

fromTrackman reads a TrackMan report export (the JSON with StrokeGroups). It extracts trajectories, ball metrics, and the static loft club fingerprint. Exports carry no club names, so map loft to labels yourself:

fromTrackman(reportJson, {
  club: (loftDeg) => (loftDeg === null ? undefined : myClubTable[loftDeg]),
})

The adapter never reads player fields. Names and emails in the export stay out of the output by construction. Still, treat raw exports as private data and keep them out of version control.

TrackMan is a trademark of TrackMan A/S. This project is not affiliated with or endorsed by TrackMan.

React

import { useRef } from 'react'
import { GolfShotViz } from 'golf-shot-viz/react'
import type { GolfShotVizHandle } from 'golf-shot-viz/react'

function ShotPanel({ shots }) {
  const viz = useRef<GolfShotVizHandle>(null)
  return (
    <div style={{ height: 480 }}>
      <GolfShotViz ref={viz} shots={shots} colorBy="club" />
      <button onClick={() => viz.current?.scene?.play({ order: 'volley' })}>
        Replay
      </button>
    </div>
  )
}

Declarative props for shots and options, the ref handle for playback commands. Client-side only (WebGL).

Data format

interface ShotInput {
  id: string
  /** [downrange, height, side] in meters, Y-up */
  points: [number, number, number][]
  /** any CSS color; overrides colorBy assignment */
  color?: string
  meta?: {
    club?: string
    session?: string
    carry?: number         // meters
    totalDistance?: number // meters
    apex?: number          // meters
    ballSpeed?: number     // m/s, drives replay timing
    hangTime?: number      // seconds, drives replay timing
    launchAngle?: number   // degrees
    spinRate?: number      // rpm
  }
}

Adapters for other monitors (Garmin, Mevo, GCQuad) are one pure function from an export to ShotInput[]. Contributions welcome, the TrackMan adapter is the template.

Options

| Option | Default | Values | | --- | --- | --- | | mode | 'studio' | 'studio' static view, 'showcase' replay | | units | 'meters' | 'meters', 'yards' (display only) | | colorBy | 'club' | 'club', 'session', 'index' | | cameraPreset | 'broadcast' | 'broadcast', 'behind', 'side', 'top', 'green' | | palette | built-in | ordered CSS colors, CVD-validated default | | background | '#0b0e14' | CSS color, or null for transparent | | groundColor | '#131a24' | floor disc CSS color, try a turf green | | tooltip | true | built-in hover tooltip | | autoRotate | false | slow idle orbit | | rollout | false | render measured bounce and rollout past carry |

The options catalog shows what each value looks like, captured from the same fixture shots so only the option changes between images. It is also committed as docs/CATALOG.md for browsing in the repo. Every option also has a runtime setter.

API

scene.setShots(shots)
scene.play({ order: 'volley' | 'sequence', speed, loop, stagger })
scene.pause(); scene.resume(); scene.stop()
scene.seek(0.5)          // scrub the master timeline
scene.setSpeed(2)
scene.setMode('showcase')
scene.setUnits('yards')
scene.setColorBy('session')
scene.setCameraPreset('top')
scene.setPalette(['#f2c14e', '#e4572e'])
scene.setBackground(null)   // or any CSS color
scene.setGroundColor('#215732')
scene.setTooltip(false)
scene.setAutoRotate(true)
scene.setRollout(true)
scene.select('shot-id')  // dim everything else
scene.legend             // [{ key, color }] for building a legend UI
scene.playback           // { playing, time, duration, progress }
scene.captureFrame()     // PNG data URL of the current view
scene.dispose()

const off = scene.on('hover', (shot) => {})
scene.on('select', (shot) => {})
scene.on('playback', (state) => {})

Visual QA

The media in this README is generated, not hand-captured:

npm run visuals

drives the demo page headlessly (Playwright), seeks the scene to deterministic states through the public API, and writes stills and GIFs to docs/media/. Run it after any visual change and eyeball the diff. CI uploads the same output as an artifact on every PR, so rendering regressions show up in review.

The options catalog works the same way at a larger scale:

npm run catalog

walks the variant matrix in scripts/catalog.config.mjs, captures one image per option value from identical fixture shots, and writes docs/catalog/ (images plus the catalog page) and docs/CATALOG.md. All of it is committed, like the README media; the Pages deploy ships the committed copy next to the demo, and CI regenerates everything as a PR artifact so rendering changes are reviewable across the whole option surface. When an option gains a value, add one entry to the matrix and both pages stay complete.

Development

npm run dev         # demo page with live reload
npm test            # unit tests (timing solver, adapter, palette, spline)
npm run lint
npm run typecheck
npm run build       # ESM + d.ts to dist/
npm run visuals     # regenerate README media
npm run catalog     # regenerate the options catalog page

The demo fixtures are real TrackMan range sessions scrubbed to trajectories, club labels, and numeric metrics only.

License

MIT