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 🙏

© 2025 – Pkg Stats / Ryan Hefner

mania-svg

v0.4.2

Published

A TypeScript library for rendering osu!mania beatmaps to SVG format with vertical strip layout.

Readme

Mania SVG Renderer

A TypeScript library for rendering osu!mania beatmaps to SVG format with vertical strip layout.

Installation

pnpm add mania-svg

Usage

Note: This package does not include .osu file parsing. You need to choose your own parser library.

The following example uses osu-parsers and osu-mania-stable:

import { BeatmapDecoder } from 'osu-parsers'
import { ManiaRuleset, Hold } from 'osu-mania-stable';
import { render, type Beatmap } from 'mania-svg';

const decoder = new BeatmapDecoder()
const parsed = await decoder.decodeFromPath('test.osu', false);
const ruleset = new ManiaRuleset();
const mania = ruleset.applyToBeatmap(parsed);

const data: Beatmap = {
  keys: mania.difficulty.circleSize,
  objects: mania.hitObjects.map(obj => ({
    start: obj.startTime,
    end: obj instanceof Hold ? obj.endTime : undefined,
    column: obj.column,
  })),
  timingPoints: mania.controlPoints.timingPoints.map(tp => ({
    time: tp.startTime,
    bpm: tp.bpm,
    meter: tp.timeSignature,
  })),
};

const svg = render(data);

Options

const defaultOptions = {
  background: {
    /** Whether to render background */
    enabled: true,
    /** Background color of the SVG */
    color: '#000000',
    /** Function to create SVG elements for the background */
    createElement: createBackground,
  },
  note: {
    /** Width of each column in px */
    width: 20,
    /** Height of regular notes in px */
    height: 6,
    /** Corner radius in px */
    rx: 2,
    /** Function to select color for each object */
    colorSelector: createNoteColorSelector(),
    /** Function to create SVG elements for each object */
    createElement: createNote,
  },
  time: {
    /** Start time in ms */
    start: 'auto' as 'auto' | number,
    /** End time in ms */
    end: 'auto' as 'auto' | number,
    /** Vertical scale: px per ms */
    scale: 0.1,
    /** Time direction: 'up' (bottom to top) or 'down' (top to bottom) */
    direction: 'up' as 'up' | 'down',
  },
  barline: {
    /** Stroke width of bar lines in px */
    strokeWidth: 1,
    color: '#85F000', // green
    /** Function to create SVG elements for each bar line */
    createElement: createBarLine,
  },
  axis: {
    /** Width of the axis area in px */
    width: 30,
    /** Style for labels at whole minutes (e.g., 1:00, 2:00) */
    minute: {
      color: '#FF3F00',
      strokeWidth: 2,
      fontSize: 18,
    },
    /** Style for second labels */
    second: {
      color: '#FFFFFF',
      strokeWidth: 1,
      fontSize: 18,
    },
    /** Function to create SVG elements for each axis label */
    createElement: createAxisLabel,
  },
  strip: {
    /**
     * Layout mode for strips
     * - 'num': specify number of strips
     * - 'time': specify time duration per strip
     * - 'ratio': specify target aspect ratio (width / height) to auto-calculate strip count (approximate)
    */
    mode: 'num' as 'num' | 'time' | 'ratio',
    /** Number of vertical strips to divide the timeline */
    num: 8 as number | undefined,
    /** Time duration per strip in ms */
    time: 30000 as number | undefined,
    /** Target aspect ratio (width / height), actual ratio may vary slightly */
    ratio: 1.5 as number | undefined,
  },
  layout: {
    /**
     * Margin around the entire SVG in px [horizontal, vertical]
     * When targetSize is set, this value serves as the minimum margin
     * and will be automatically adjusted to meet the target size requirements
     */
    margin: [10, 10] as [number, number],
    /**
     * Maximum scale factor applied to the entire SVG
     * When targetSize is set:
     * - Will be automatically reduced (if needed) to prevent content from exceeding targetSize
     * - After scaling, margin will be adjusted to exactly meet targetSize requirements
     */
    finalScale: 1.0 as number,
    /**
     * Target size [width, height] for the final SVG
     * When set, the SVG will be adjusted to fit this size by:
     * 1. Reducing finalScale (if needed) to prevent content from exceeding targetSize
     * 2. Adjusting margin (using the margin value as minimum) to exactly meet targetSize
     */
    targetSize: undefined as [number, number] | undefined,
  },
};

License

MIT