Skip to content

djDAOjones/router-plotter-02

Repository files navigation

Route Plotter

An animated route editor for maps and images. Drop in a background, click to place waypoints, tweak styles and timing, then export as MP4, WebM, or a self-contained HTML file.

Live demo


What it does

  1. Load a background image (drag-and-drop, upload, or pick a built-in example).
  2. Click the canvas to add major waypoints (full features) or Cmd/Ctrl+Click for minor waypoints (path shaping only).
  3. A Catmull-Rom spline connects them into a smooth animated path.
  4. Configure per-waypoint: marker style, colour, beacon effect, text label, wait time, segment speed, camera zoom, and area highlight.
  5. Configure globally: path visibility mode, waypoint visibility mode, background reveal mode (spotlight, angle-of-view), tint, trail, and graphics scale.
  6. Toggle between Edit and Preview modes in the header.
  7. Export to MP4 (H.264 via WebCodecs), WebM (VP8), or standalone HTML with interactive playback.
  8. Projects auto-save to localStorage and can be saved/loaded as ZIP files.

Quick start

git clone https://github.com/djDAOjones/router-plotter-02.git
cd router-plotter-02
npm install
npm run dev        # Dev server with watch → http://localhost:3000
npm run build          # Production bundle → docs/
npm run build:deploy   # Build + copy dist → docs/ (GitHub Pages)
npm test               # Vitest (jsdom)
npm run push           # Build, commit docs/, push to origin/main

Project structure

index.html                        Single-page app shell (sidebar + canvas + controls)
build.js                          esbuild bundler, version management, dev server
version.json                      Auto-incremented build number
push.js                           GitHub Pages deploy helper

src/
  main.js                         RoutePlotter class — app entry point and orchestrator
  config/
    constants.js                  All tuneable values (animation, rendering, path, etc.)
    keybindings.js                Mouse + keyboard bindings (customisable via localStorage)
    helpContent.js                Welcome modal and inline help HTML generators
    tooltips.js                   Tooltip definitions
  components/
    SwatchPicker.js               Okabe-Ito colour-blind safe palette picker
    Dropdown.js                   Accessible dropdown menus
    Tooltip.js                    Tooltip attachment
    ParamTooltip.js               Click-label parameter tooltips (Carbon pattern)
  controllers/
    UIController.js               Sidebar controls, waypoint list, slider sync
    SectionController.js          Collapsible settings sections
  core/
    EventBus.js                   Pub-sub event system
  handlers/
    InteractionHandler.js         Mouse, keyboard, touch, and drag-and-drop input
  models/
    Waypoint.js                   Waypoint data model (position, style, camera, area, etc.)
    AnimationState.js             Playback state (progress, timing, pause tracking)
    ImageAsset.js                 Custom image references (marker, path head)
  services/
    AnimationEngine.js            Playback loop, timing, segment speed, pause markers
    PathCalculator.js             Catmull-Rom spline, reparameterisation, curvature
    PathCalculatorWithWorker.js   Web Worker wrapper (falls back to main thread)
    RenderingService.js           Canvas drawing — path, markers, labels, overlays
    BeaconRenderer.js             Animated waypoint effects (ripple, glow, pop, grow, pulse)
    TextLabelService.js           Text label layout, fade, auto-positioning
    MotionVisibilityService.js    Path/waypoint/background visibility calculations
    CameraService.js              Per-waypoint zoom with continuous interpolation
    CoordinateTransform.js        Image ↔ canvas coordinate conversion
    VideoExporter.js              MP4/WebM export (WebCodecs primary, MediaRecorder fallback)
    HTMLExportService.js           Self-contained HTML export with embedded player
    ImageAssetService.js          Custom image management and deduplication
    StorageService.js             localStorage with debounce and change detection
    UndoService.js                150-step undo/redo history
    AreaDrawingService.js         Polygon area drawing mode
    AreaEditService.js            Area highlight repositioning and vertex editing
    AreaHighlightRenderer.js      Per-waypoint area highlight rendering
  utils/
    CatmullRom.js                 Catmull-Rom spline interpolation
    Easing.js                     Easing functions (linear, quad, cubic, etc.)
    focusTrap.js                  Modal focus trapping for accessibility
  workers/
    pathWorker.js                 Web Worker for off-thread path calculation

styles/
  tokens.css                      Design tokens — UoN palette, semantic colours, spacing
  main.css                        Core layout, sidebar, canvas, controls, modals
  swatch-picker.css               Swatch picker grid (5×2, 44px AAA touch targets)
  dropdown.css                    Dropdown component styles
  tooltip.css                     Tooltip styles

tests/
  example.test.js                 Unit tests (Waypoint, AnimationState, Path, EventBus, etc.)
  setup.js                        Vitest jsdom setup

docs/                             Build output served by GitHub Pages

Architecture

Overview

RoutePlotter (in main.js) is the single orchestrator. It owns all services, handles EventBus events, manages application state, and drives the render loop.

There is no framework. The app is pure JavaScript with Canvas 2D rendering and vanilla DOM for the sidebar UI.

Event-driven communication

Components talk through EventBus (pub-sub), not direct method calls:

User clicks canvas → InteractionHandler emits event
    → main.js handles event, updates Waypoint model
    → main.js calls queueRender()
    → RenderingService draws the frame
User moves slider → UIController emits event
    → main.js handles event, updates state
    → main.js recalculates timing / path
    → main.js calls queueRender()

Key event categories

Event prefix Source Purpose
waypoint:* InteractionHandler, UIController Add, delete, select, move, restyle waypoints
animation:* AnimationEngine, UIController Play, pause, reset, speed, seek
ui:* UIController Slider sync, mode changes, export triggers
video:* VideoExporter Export lifecycle (started, progress, complete, error)
area:* AreaDrawingService, AreaEditService Area highlight draw/edit
undo:* UndoService State snapshot/restore

Rendering pipeline

  1. queueRender() batches requests via requestAnimationFrame (one frame per tick).
  2. render() builds a renderState object from current waypoints, animation progress, motion settings, camera, and preview mode.
  3. RenderingService draws layers in order: background → tint overlay → area highlights → path → waypoints → labels → path head → beacons.
  4. MotionVisibilityService computes per-frame visibility/opacity for path, waypoints, and background based on animation progress and the active visibility mode.
  5. CameraService applies zoom/pan transforms from per-waypoint camera keyframes.

Coordinate systems

Waypoints are stored in normalised image coordinates (0–1). The CoordinateTransform service converts between:

  • Image coords (imgX, imgY) — storage and serialisation.
  • Canvas coords (x, y) — rendering and hit-testing.

Zoom, pan, and fit/fill mode are handled inside the transform. Path points are recalculated when the canvas resizes.


Persistence and export

Auto-save (localStorage)

State is debounce-saved to routePlotter_autosave on every change. Loaded on startup. Includes waypoints, styles, motion settings, background reference, animation state, and export settings.

Other localStorage keys: routePlotter_preferences, routePlotter_splashShown, routePlotter_customKeybindings.

Project save/load (ZIP)

Save Project packages all state (including the background image) into a .zip file. Open Project restores from a .zip.

Video export

  • MP4: H.264 via WebCodecs + mediabunny muxer. Hardware-accelerated, immune to background-tab throttling. Requires even dimensions (auto-rounded).
  • WebM: VP8 via WebCodecs + mediabunny. Falls back to MediaRecorder on browsers without WebCodecs (Firefox, older Safari).
  • Configurable resolution (up to 7680×4320), frame rate (10–60 fps), aspect ratio presets, and path-only (transparent) mode.

HTML export

Self-contained HTML file with embedded base64 background image and a full JavaScript player. Supports all visibility modes, beacons, camera, labels, and interactive scrubbing. 80–95% smaller than equivalent video.


Versioning

Format: major.minor.build (e.g. 3.1.530).

  • major.minor — set manually in package.json.
  • build — auto-incremented in version.json once per dev-server start or production build.

The combined string is injected at build time via esbuild's define as APP_VERSION.

Change Version bumps?
Edit JS in src/ Build increments on next npm run dev restart or npm run build
Edit CSS/HTML only No (static files are copied, not rebuilt)
Force bump after CSS Restart dev server, or touch src/main.js

Keybindings

All shortcuts live in src/config/keybindings.js. User overrides are stored in routePlotter_customKeybindings localStorage key and merged at load time.

Each binding specifies: key, modifiers (meta/alt/shift), action (EventBus event name), description, and category.

meta maps to Cmd on macOS, Ctrl on Windows/Linux.

The in-app help panel (press ?) renders all bindings dynamically from this config.


Constants reference

All tuneable values are in src/config/constants.js, grouped by concern:

Group Key values
ANIMATION DEFAULT_SPEED 200 px/s, DEFAULT_DURATION 10 000 ms, DEFAULT_WAIT_TIME 1 500 ms, TARGET_FPS 60
VIDEO_EXPORT DEFAULT_FRAME_RATE 25 fps, DEFAULT_BITRATE 20 Mbps, START_BUFFER_MS 2 000 ms
RENDERING DEFAULT_PATH_COLOR #D55E00, DEFAULT_DOT_SIZE 8 px, MINOR_DOT_SIZE 4 px, CONTROLS_HEIGHT 80 px
PATH POINTS_PER_SEGMENT 100, DEFAULT_TENSION 0.1, TARGET_SPACING 2 px, MIN_CORNER_SPEED 0.2
MOTION Trail default 20%, spotlight 10% canvas, AoV 60°/25%/50%, timeline handles 2 s + 3 s
INTERACTION Hit radius 15 px, drag threshold 3 px, double-click 300 ms
TEXT_LABEL 16–48 px font, 15% width, 0.85 bg opacity, 500 ms fade, 8-direction auto-position
AREA_HIGHLIGHT Circle/rectangle/polygon, Okabe-Ito fill, configurable border, fade in/out, same visibility modes as waypoints
STORAGE Autosave debounce 1 000 ms

Visibility mode enums

Enum Values
PATH_VISIBILITY always-show, show-on-progression, hide-on-progression, instantaneous (comet), always-hide
WAYPOINT_VISIBILITY always-show, hide-before, hide-after, hide-before-and-after, always-hide
BACKGROUND_VISIBILITY always-show, spotlight, spotlight-reveal, angle-of-view, angle-of-view-reveal, always-hide
TEXT_VISIBILITY off, on, fade-up, fade-up-down
AREA_VISIBILITY Same five modes as WAYPOINT_VISIBILITY

Common development tasks

Add a new per-waypoint property

  1. Add the property with a default in Waypoint.js constructor.
  2. Include it in toJSON() and handle it in fromJSON().
  3. Add a UI control in the appropriate index.html settings section.
  4. Wire the control in UIController.js to emit an EventBus event.
  5. Handle the event in main.js (update waypoint, call queueRender()).

Add a new global setting

  1. Add the value to the relevant state object in RoutePlotter constructor (motionSettings, exportSettings, styles).
  2. Add a constant/default in constants.js.
  3. Add UI control in index.html, wire in UIController.js.
  4. Handle in main.js, persist in auto-save and project save/load.

Modify canvas rendering

Edit RenderingService.js. Drawing methods follow the naming pattern render*(). The rendering order is defined in the LAYERS constants.

Debug issues

  • Console: The app intercepts console.log/warn/error into a 500-entry ring buffer. Use Export → Download Debug Log or Copy Debug Log to capture it as markdown.
  • Browser DevTools: Check the Console tab and Network tab.
  • Version: Shown in the header tooltip and page title.

Gotchas

  • Don't edit docs/ — it is generated by the build. Edit source files in src/, styles/, or index.html.
  • Imports at top only — esbuild bundles from src/main.js. Never import mid-file.
  • Coordinate transform — always use CoordinateTransform.canvasToImage() / imageToCanvas() when converting between screen and storage positions.
  • Autosave can get stuck — if the app enters a bad state, clear routePlotter_autosave in browser DevTools → Application → Local Storage.
  • Slider feedback loops — programmatic slider updates must go through ui:slider:update-speed to avoid re-triggering input event handlers. Check isUpdatingSlider flag in UIController.
  • H.264 even dimensions — MP4 export requires even width and height. The exporter auto-rounds, but custom resolution inputs can produce odd values.
  • Web Worker fallbackPathCalculatorWithWorker initialises a Web Worker for off-thread path calculation. If it fails (e.g. CORS), it falls back to the main-thread PathCalculator silently.
  • mediabunny is the only runtime dependency — it provides the MP4/WebM mux layer. Everything else is vanilla JS.

Glossary

Precise terms used across the codebase.

  • Route — Full journey from first to last waypoint.
  • Path — Interpolated Catmull-Rom spline connecting waypoints.
  • Path points — Dense array of {x, y} coordinates defining the path.
  • Major waypoint — Full-featured: labels, pauses, beacons, area highlights, larger marker.
  • Minor waypoint — Path shaping only: smaller marker, no pause/label/beacon.
  • Marker — Visual dot, square, flag, custom image, or none.
  • Path head — Leading indicator at current animation position (arrow, dot, custom image, or none).
  • Beacon — Animated effect at waypoints: ripple, glow, pop, grow, pulse.
  • Label — Text attached to a waypoint with fade/visibility modes.
  • Area highlight — Per-waypoint overlay region (circle, rectangle, or drawn polygon) with visibility timing.
  • Tint — Background overlay from −100 (black) to +100 (white).
  • Spotlight — Circular background reveal around the path head.
  • Angle of view — Cone-shaped background reveal from the path head.
  • Trail — In comet mode, the visible path segment behind the head.
  • Segment speed — Per-segment speed multiplier (0.1x–10x).
  • Progress — Animation position, 0.0–1.0.
  • Image coordinates — Normalised 0–1 position on the background image. Used for storage.
  • Canvas coordinates — Screen-pixel position. Used for rendering and hit-testing.
  • Graphics scale — Global multiplier (0.25×–4×) applied to all vector element sizes.

License

MIT

Author

Joe Bell — University of Nottingham

Links

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors