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

@sylwellsoftware/fray-visualization

v0.12.0

Published

Reactive analytical controls, block mosaics, and history charts for Fray applications.

Readme

Fray Visualization

@sylwellsoftware/fray-visualization provides domain-neutral analytical models and accessible Fray components: stable-key categories, category visibility, ordered recursive splits, proportional block diagrams, civil-date history series, and responsive charts.

The package is ESM-only and currently follows 0.x compatibility rules. Install it with its Glue and Fray peers:

pnpm add @sylwellsoftware/glue @sylwellsoftware/fray \
  @sylwellsoftware/fray-visualization

Import Fray's normal presentation files and either collect component styles or load the complete visualization structural asset:

import '@sylwellsoftware/fray/themes/base.css'
import '@sylwellsoftware/fray/styles/structural.css'
import '@sylwellsoftware/fray-visualization/styles/structural.css'
import '@sylwellsoftware/fray/colors/iceblue/colors.css'
import '@sylwellsoftware/fray/themes/minimal/theme.css'

With dependency collection, declare BlockGraph, CategoryHidePanel, SplitSelectionPanel, or LineGraph in the owning component's static dependencies instead.

Ownership model

Applications supply items, stable keys, domain predicates, labels, semantic colors, split presets, and dates. This package coordinates those declarations and presents them; it does not fetch, persist, or infer domain policy.

Models and derived emitters are caller-owned. Dispose them at the composition boundary that created them. Components observe passed models but never dispose them.

application items and domain declarations
                 │
                 ▼
GroupingCriterion / SplitSelectionModel / BlockSelectionModel / SeriesBuilder
                 │ caller-owned emitters and models
                 ▼
CategoryHidePanel / SplitSelectionPanel / BlockGraph / LineGraph

Categories and visibility

A Category<T> has a stable key, visible label, predicate, and ordered [dark, base, light] CSS colors. Optional hiddenByDefault applies only the first time that key appears.

Use staticCriterion() for a fixed category vocabulary:

interface Finding {
    severity: 'critical' | 'high' | 'medium'
    projectId: string
    detected: CivilDate
    resolved?: CivilDate
}

const severity = staticCriterion<Finding>({
    key: 'severity',
    label: 'Severity',
    categories: [
        {
            key: 'critical',
            label: 'Critical',
            predicate: (finding) => finding.severity === 'critical',
            colors: ['#6b0000', '#c62828', '#ffcdd2'],
        },
        {
            key: 'high',
            label: 'High',
            predicate: (finding) => finding.severity === 'high',
            colors: ['#8a3b00', '#ef6c00', '#ffe0b2'],
        },
        {
            key: 'medium',
            label: 'Medium',
            predicate: (finding) => finding.severity === 'medium',
            colors: ['#725400', '#f9a825', '#fff9c4'],
        },
    ],
})

Use derivedCriterion() when categories come from current item values. It extracts one or more keys per item, drops empty categories, sorts populated categories deterministically, and preserves hidden state when a key disappears and later returns:

const projects = derivedCriterion({
    key: 'project',
    label: 'Project',
    source$: findings,
    extractKeys: (finding: Finding) => finding.projectId,
    describe: (projectId) => ({
        label: projectId,
        colors: ['#263238', '#607d8b', '#cfd8dc'],
    }),
})

GroupingCriterion exposes:

  • categories$, hidden$, and visibleCategories$;
  • visibility(categoryKey) for a writable 'hidden' | 'visible' adapter;
  • setAllVisible() and pruneHidden() commands;
  • allowResorting, used by recursive block layout;
  • dispose().

deriveCategories() is the pure dynamic-category calculation. filterByHidden(items$, criteria) creates a blacklist-style derived item collection, and categoryCounts(items$, criterion) reports unfiltered live counts. The caller owns and disposes both derived emitters.

Ordered splits and block selection

createSplitSelection(criteria, options) returns a SplitSelectionModel. Its order$ contains every criterion, while activeSplits$ contains the currently enabled subset in recursive split order. Optional presets name exact active/inactive arrangements.

const splits = createSplitSelection(
    [severity, projects],
    {
        active: ['severity', 'project'],
        presets: [
            {key: 'severity-first', label: 'Severity first', active: ['severity', 'project']},
            {key: 'project-only', label: 'Project only', active: ['project'], inactive: ['severity']},
        ],
    },
)

const blocks = createBlockSelection(findings, splits.activeSplits$, {
    rootLabel: 'All findings',
    readabilityThreshold: 0.02,
})

The split model exposes activeState(), isActive(), toggle(), move(), moveBy(), setSplits(), applyPreset(), and dispose().

BlockSelectionModel owns a reactive strict-partition layout and rebuild-safe selection:

  • layout$ contains the root block, child blocks, and partition issues;
  • selectedPath$ is the stable criterion/category path;
  • selectedBlock$ re-resolves that path after every rebuild;
  • selectedItems$ exposes the selected subset;
  • select(), clear(), and dispose() manage selection and lifetime.

Every active criterion must assign every item under each parent to exactly one category. Zero matches and multiple matches make the layout invalid and are reported as BlockPartitionIssue entries; the model does not silently guess.

For non-reactive use, buildBlockLayout() performs the pure calculation, criterionSnapshot() converts a criterion to a split snapshot, and findBlock() resolves a path.

Component reference

CollapsibleOptionGroup

CollapsibleOptionGroup extends Fray's OptionGroup with disclosure behavior. Its ordinary children are the ordered option content. Supply optional trailing legend content with Fray's parent-specific OptionGroupHeaderEnd marker:

<CollapsibleOptionGroup label="Severity">
    <OptionGroupHeaderEnd>
        <small>4 visible</small>
    </OptionGroupHeaderEnd>
    <Checkbox label="Critical" value="critical" />
    <Checkbox label="High" value="high" />
</CollapsibleOptionGroup>

collapsed configures only the initial disclosure state. The marker is a direct-child declaration consumed by the option group; it does not render an extra component host.

CategoryHidePanel

CategoryHidePanel<T> renders collapsible criterion groups, per-category visibility checkboxes, color swatches, and counts against the unfiltered item source.

| Prop | Meaning | | --- | --- | | items$ | Required readable source used for live counts | | criteria | Required criterion list | | label | GroupBox heading; defaults to “Show or hide categories” | | description | Introductory help text | | initiallyOpen | Initial disclosure policy per criterion |

The outer surface inherits Fray's GroupBox contract: a labelled group with a vertical chromed header. Criterion summaries remain horizontal disclosure headers above their options.

SplitSelectionPanel

SplitSelectionPanel<T> enables criteria, applies presets, and changes their recursive order.

| Prop | Meaning | | --- | --- | | model | Required caller-owned SplitSelectionModel | | label | GroupBox heading | | description | Introductory help text |

Pointer dragging reorders entries. From a drag handle, Alt+ArrowUp/Alt+ArrowDown provides the keyboard equivalent and announces the new position. Preset buttons expose their active state with aria-pressed.

BlockGraph

BlockGraph<T> renders a nested proportional mosaic from a BlockSelectionModel.

| Prop | Meaning | | --- | --- | | model | Required caller-owned block selection model | | label | Accessible graph name and visible heading | | description | Visible explanation of the area encoding | | emptyMessage | Message for an empty valid layout |

The graph exposes an ARIA tree, keyboard selection, a current-path readout, clear-selection action, loading/error states, and explicit partition diagnostics. Each block applies Fray's colored trait using the category's --c1, --c2, and --c3 values. Nested mosaics are inset by --viz-block-graph-child-inset (default 1.6em) so parent surfaces remain visible. Labels overlay their surfaces and keep criterion and category on one line without distorting area ratios. Hover emphasis targets only the deepest block under the pointer.

LineGraph

LineGraph renders responsive SVG history as individual lines or stacked areas.

| Prop | Meaning | | --- | --- | | shapes$ | Static HistoryShape[] or readable source | | stacked$ | Static boolean or readable source | | smooth$ | Static boolean or readable source | | range$ | Static or readable {minX?, maxX?, minY?} | | label, emptyMessage | Accessible heading and empty state | | formatDate, formatValue | Optional readout formatters |

Pointer movement updates the readout. Arrow keys move by one day, Shift+Arrow by one week, Home/End jump to the range bounds, and Escape clears a pinned cursor. Readable inputs propagate loading/error state; static values are wrapped in component-owned ready sources.

Civil dates and history series

CivilDate is a strict YYYY-MM-DD string interpreted with UTC-day arithmetic, so calculations do not move across daylight-saving boundaries. The date module exports validation/day conversion, comparison, addition, and current-date helpers.

SeriesBuilder converts dated category deltas into ordinary or cumulative HistoryShape arrays:

const history = new SeriesBuilder(severity.categories$.get())
for (const finding of currentFindings) {
    history.addOne(finding.detected, finding.severity)
    if (finding.resolved != null) history.removeOne(finding.resolved, finding.severity)
}

const cumulativeShapes = history.buildCumulative()

An ordinary series reports date deltas. A cumulative series carries values forward and includes a pre-range anchor when earlier activity affects the visible period.

For custom renderers, buildLineChartModel() computes normalized chart data without a DOM. linePath(), areaPath(), valueAtDate(), and buildIntegerTicks() expose the same pure calculations used by LineGraph.

Complete export groups

| Module area | Public exports | | --- | --- | | Grouping | Category, CategoryColors, GroupingCriterion, staticCriterion, derivedCriterion, deriveCategories, filterByHidden, categoryCounts, categoryColorVariables, setsEqual and related option/state types | | Splits | SplitSelectionModel, SplitPreset, createSplitSelection | | Blocks | BlockSelectionModel, createBlockSelection, buildBlockLayout, criterionSnapshot, findBlock and block path/layout/issue types | | History | SeriesBuilder, HistoryShape, SeriesCategory | | Dates | CivilDate, civilDateToDay, dayToCivilDate, addCivilDays, todayCivilDate, compareCivilDates | | Charts | buildLineChartModel, linePath, areaPath, valueAtDate, buildIntegerTicks and chart model types | | Components | CategoryHidePanel, SplitSelectionPanel, BlockGraph, LineGraph and their props |

Styling and accessibility

Visualization components use fixed Fray hosts: fray-categoryhidepanel, fray-splitselectionpanel, fray-blockgraph, and fray-linegraph. Structural CSS remains owned by component classes. Themes provide values, not component selectors.

Category triples populate both the established --c1/--c2/--c3 inputs and the descriptive --colored-dark/--colored-base/--colored-light aliases. Fray's base trait supplies the gradient; themes may add treatment such as Shiny's shared colored shadow. Series use the base color for paths and legend swatches.

The components expose names, statuses, keyboard equivalents, and forced-color fallbacks, but applications remain responsible for meaningful domain labels, contrast in supplied category colors, and manual assistive-technology review.

See the Fray guide, the repository API surface, and this package's release history.