Plume is a pure Roc package that builds Plotly chart specifications and complete HTML documents. It supports bar, scatter, pie, donut and Sankey charts. The package performs no I/O; applications choose where to send its output.

Run an example§

Install the latest Roc nightly. From the repository root:

roc examples/simple.roc > chart.html

Open the resulting file in a browser. HTML loads Plotly from cdn.plot.ly with a fixed versioned URL and integrity hash; viewing the chart requires network access to that CDN. Chart labels and hover text follow Plotly’s own text and markup rules. HTML document titles and embedded JSON are escaped separately.

Read Working with charts for constructors, styling and output, and API guarantees for validation and encoding contracts. The API reference describes individual public types and methods.

Working with charts§

A chart has three layers: validated domain values, a trace describing one series, and a figure that combines traces with a layout. Applications import the modules they use from their plume dependency and decide where to write the resulting text.

Construct a figure§

Inside an application’s main!, create a trace and wrap it in the matching Chart.Trace tag:

sales = Bar.new({
    data: [
        { x: "Apples", y: 12 },
        { x: "Oranges", y: 8 },
    ],
    name: "Sales",
})?

chart = Chart.new({
    traces: [Bar(sales)],
    layout: Layout.new({ title: Title.new({ text: "Fruit sales" }) }),
})

html = chart.to_html()

Chart.new and Layout.new are infallible. Constructors that validate raw numeric settings, such as Bar.new, return Try; use ? to propagate their structured errors. Empty datasets and an empty trace list are valid. Bar width uses axis units and may exceed one. A figure can contain several trace kinds, and each trace retains the order of its input points or slices.

ConstructorUseImportant settings
Bar.newVertical or horizontal barsPer-point color, orientation, width in axis units
Scatter.newLines, scatter plots, bubbles and textMode, per-point marker and text, shared line style
Pie.newPies and donutsLabeled nonnegative slices; hole in the closed interval zero to one
Sankey.newDirected flowsStable node IDs, links, hover overrides and orientation

Coordinates and styling§

Cartesian point fields use Coordinate. Plain string and number literals convert at compile time; runtime data uses explicit constructors:

label = Coordinate.from_text(product_name)
quantity = Coordinate.from_number(measured_quantity)?
sales = Bar.new({ data: [{ x: label, y: quantity }] })?

Text coordinates remain text even when they contain digits. Numeric coordinates use F64 precision, and Coordinate.from_number rejects NaN and both infinities with NonFiniteCoordinate(number). For date axes, supply appropriate date text and select Axis’s Date type; Plotly interprets the strings. Plume does not parse date syntax.

Markers and line styles are validated before they enter a scatter trace:

purple = Color.rgb({ red: 124, green: 56, blue: 245 })
marker = Marker.new({ size: 12, symbol: Diamond, color: purple })?
line = Line.new({ width: 2, color: purple, dash: Dash })?
points = Scatter.new({
    data: [{ x: 1, y: 2, marker }],
    mode: LinesMarkers,
    line,
})

Scatter.new is infallible because its coordinates and styles have already been checked. The mode selects lines, markers, text, or a combination; per-point text is drawn only by a mode that includes text. Point order is also line-connection order. A marker’s size is its diameter in pixels, and a line’s width is measured in pixels. Their zero sizes hide the corresponding shape; negative and nonfinite values are errors.

Use named symbol tags in source. Marker.Symbol.from_str accepts canonical Plotly names when reading configuration, returning InvalidSymbol(text) for unknown or unsupported names. Color.hex accepts six hexadecimal digits following #; Color.named accepts supported CSS color names without regard to ASCII case. RGB and alpha channels use bytes, so alpha zero is transparent and alpha 255 is opaque.

Font.default, Axis.default and Title.default leave their settings to Plotly. Calling their constructors applies the documented props explicitly. A Font’s size must be finite and at least one pixel. Font families are CSS family lists resolved by the browser; the package does not load fonts. Numeric axis bounds may run in either direction. On logarithmic axes the bounds are base-10 exponents: zero to two displays values from one to 100.

Titles, legends and hover text§

Layout’s title is the title drawn in the chart. Its legend is hidden by default; set legend: Shown to display trace names. Chart’s separate title controls the HTML document title: FromLayout derives it from the visible title, with a fallback when no title is set; Text(text) overrides the document title without changing the chart or its JSON figure.

Sankey nodes use distinct string IDs independently of display labels. Links must reference existing, different IDs; duplicate IDs, absent endpoints and self-links produce separate errors from Sankey.new. Nonnegative finite flow values, including zero, are accepted. Cycles through distinct nodes are supported.

A node or link’s hover: Default preserves Plotly’s normal hover content. Text(text) overrides only that node or link. Chart labels and hover text follow Plotly’s supported formatting rules. The package escapes their JSON representation; this does not turn Plotly’s text formatting into plain text.

Render the result§

chart.to_json() returns a complete figure object containing data and layout. Use it when another application manages Plotly and its document. chart.to_html() returns a complete HTML document with a pinned Plotly CDN script and an integrity hash. Viewing that document requires network access to the CDN. HTML document-title text and JSON embedded in the script element are escaped for their separate contexts.

Low-level to_json_value methods are composition hooks used by the package’s renderers. Their InternalJson result is an opaque, safely encoded value; it has no unchecked raw-JSON constructor and is intentionally absent from the package’s exposed modules. Applications normally use the complete to_json or to_html output instead of these hooks.

Browse the API reference§

Browse the API reference. To generate it locally, run blueprint run api-docs and open generated-docs/index.html. The generated pages describe each constructor, its props, defaults, closed choices and failure tags. blueprint run policy checks that public definitions have attached documentation, and blueprint run check runs the same policy gate. Presence checks do not judge clarity or completeness: review the rendered pages whenever public types, options or behavior change.

API guarantees§

Plume’s API should make invalid chart states difficult to express and impossible to carry past a validated constructor. This is a product contract, enforced by types and executable tests rather than naming conventions alone.

BoundaryGuaranteeEnforcement
Cartesian coordinateA category/date string or a finite F64 numberOpaque Coordinate; string/number literal hooks; checked runtime numeric constructor
Style valueValid size, width, color and closed symbol/dash optionsOpaque types, props and structured constructor errors
TraceValidated points and presentation; aligned arrays preserve point orderNon-generic opaque trace types and decoded-output tests
Sankey graphUnique IDs, existing endpoints, nonnegative finite flows and no self-linksConstructor validation with distinct error tags
JSONOnly properly encoded scalars and structured arrays/objectsOpaque internal builder; no unchecked string-fragment constructor
HTMLChart text cannot terminate its JSON script element or document titleSeparate context escaping and adversarial round-trip tests

Coordinate.from_quote and Coordinate.from_numeral let normal literals use the same checked boundary without wrapping every point. Runtime strings use Coordinate.from_text; runtime numbers use Coordinate.from_number and return Try. Numeric coordinates have F64 precision. Records, lists and booleans are not coordinates. Date strings are interpreted by Plotly according to the axis configuration; the coordinate type does not validate a date grammar.

Constructors return errors only when their input can be invalid. Scatter.new is infallible because its coordinates, marker and line already carry their guarantees. Bar.new, Pie.new and Sankey.new validate their additional numeric or graph inputs. Rendering a valid value needs no new user-input validation. Text resembling template placeholders remains literal data in the encoded output.

Keep the contract executable§

blueprint run check runs four complementary forms of evidence:

Type-contract fixtures are data; the driver writes temporary Roc applications and requires a normal compiler rejection with the expected diagnostic. A warning, timeout, signal or unrelated setup failure cannot satisfy a case. The corresponding positive control must compile successfully first.

The fuzz smoke checks the target’s declared examples and committed allocation baseline before a bounded exploration. The check task owns the build options, execution budget and process deadline. Its output identifies skipped checks and records diagnostics for failures.

Review changes to expected output and allocation baselines as code changes. Use the test reports to understand the observations behind a baseline change; routine checks never accept new expectations automatically.

When changing a public type, demonstrate both a valid intended use and the invalid construction it excludes. When a runtime constructor gains a rule, add its boundary cases and extend the relevant generated-input property. Keep error tags distinct and avoid adding a fallible return to an operation whose input types already guarantee success.

Contribute to Plume§

Documentation§

Every paragraph should help someone use, maintain or assess the current project. Delete superseded instructions and duplicate explanations. Keep implementation history in Git, unresolved work in issues, and release-specific changes on the published release; do not include release notes in the manual. Preserve current compatibility guidance, security boundaries, API contracts and required notices.

Use the shared documentation guide for structure and publishing.

Development and validation§

Use the Blueprint release declared in Blueprint.roc and the latest Roc nightly as roc on PATH:

blueprint check
blueprint run check
blueprint run docs
blueprint run release-package -- build 0.0.1-dryrun
blueprint run release-test -- test-archive

The documentation build includes the manual and generated API reference in .docs-out/site/. Pull requests retain that site for preview; the default branch deploys the same build to GitHub Pages.

Blueprint.lock records the digest and length of each released utility and the fixed compiler used for repository scripts. The product compiler is supplied by the host and by CI. Checks use the dev backend. These are correctness checks, not performance measurements.

check formats sources, runs package and consumer expect tests, and runs every example against tests/spec.json. Expected output changes are recorded with blueprint run release-test -- test-examples --update and reviewed as a diff. The release utility builds each example once and compares output, stderr and exit status for each case. Failed commands and timeouts fail the run. The release archive is tested separately against the exact bundled package over loopback.

The README gallery uses the same images as the visual CI check. With Node available, run blueprint run screenshots-setup to install the configured browser, then blueprint run screenshots to rebuild the examples and compare fresh renders with those images. A mismatch retains expected, actual and difference images for review. For an intentional visual change, update any changed example output first, then run blueprint run screenshots-update and review the PNG diff before committing.

The workflows pin shared roc-automation code and use its pure-package model. Roc source follows the style guide.

Release operation§

Pull requests run the shared release dry run. Publication requires dispatching Release from the default branch with a version, after reviewing its artifacts and configuring the repository’s release environment. The required checks are Release / Build and test the source and Release / Test the release candidate, plus the documentation job if required by repository policy. Adding these workflow files does not configure GitHub branch rules or environment protection.

The nightly workflow checks the latest published release, rather than the working tree. It requires a published examples archive that follows the shared release contract. Local checks and dry runs do not establish hosted CI or publication success.