Skip to content

Latest commit

 

History

History
311 lines (240 loc) · 16 KB

README.md

File metadata and controls

311 lines (240 loc) · 16 KB

@thi.ng/hiccup-svg

npm version npm downloads Mastodon Follow

Note

This is one of 199 standalone projects, maintained as part of the @thi.ng/umbrella monorepo and anti-framework.

🚀 Please help me to work full-time on these projects by sponsoring me on GitHub. Thank you! ❤️

About

SVG element functions for @thi.ng/hiccup & related tooling.

Important

The functions provided here do produce valid hiccup elements, but since none of them make use of (or support) the global hiccup / hdom context object, they can ONLY be invoked directly, i.e. they MUST be called like:

import { circle, svg } from "@thi.ng/hiccup-svg";

// correct (direct invocation)
svg({}, circle([0, 0], 100, { fill: "red" }));

// incorrect / unsupported (lazy evaluation)
[svg, {}, [circle, [0, 0], 100, { fill: "red" }]]

SVG conversion of @thi.ng/geom & @thi.ng/hiccup-canvas shape trees

Since v2.0.0 this package provides a conversion utility to translate the more compact syntax used by @thi.ng/geom and @thi.ng/hiccup-canvas shape trees (designed for more performant realtime / canvas drawing) into a SVG serializable hiccup format.

The convertTree() function takes a pre-normalized hiccup tree of geom/hiccup-canvas shape definitions and recursively converts it into an hiccup flavor which is ready for SVG serialization (i.e. using stringified geometry attribs). This conversion also involves translation & re-organization of various attributes. This function returns a new tree. The original remains untouched, as will any unrecognized tree / shape nodes (those will be transferred as-is to the result tree). See example below.

Tree conversion can be implicitly triggered by providing a __convert: true attribute to the root svg() element. This conversion also supports the __prec control attribute which can be used (on a per-shape basis) to control the formatting used for various floating point values (except color conversions). Child shapes (of a group) inherit the precision setting of their parent.

import { svg } from "@thi.ng/hiccup-svg";

// create SVG root element and convert body
svg(
    { width: 100, height: 100, __convert: true, __prec: 3 },
    ["rect", { fill: [1, 0, 0] }, [1.2345, -1.2345], 100, 100]
)
// [
//   'svg',
//   {
//     version: '1.1',
//     xmlns: 'http://www.w3.org/2000/svg',
//     'xmlns:xlink': 'http://www.w3.org/1999/xlink',
//     width: 100,
//     height: 100
//   },
//   ['rect', { fill: '#ff0000', x: '1.234', y: '-1.234', width: '100', height: '100' }]
// ]

Automatic attribute conversions

Colors

Since v3.1.0:

Color conversions are only applied to fill and stroke attributes and color stops provided to linearGradient(), radialGradient()

String

String color attribs prefixed with $ are replaced with url(#...) refs (e.g. to refer to gradients), else used as is (untransformed)

Number

Interpreted as ARGB hex value:

{ fill: 0xffaabbcc } => { fill: "#aabbcc" }

Array

Interpreted as float RGB(A):

{ fill: [1, 0.8, 0.6, 0.4] } => { fill: "rgba(255,204,153,0.40)" }

Converted to CSS color strings:

{ fill: hcya(0.1666, 1, 0.8859) } => { fill: "#ffff00" }

Transforms

(i.e. transform, rotate, scale, translate)

If an element has a transform attrib, conversion of the other transformation attribs will be skipped, else the values are assumed to be either strings or:

  • transform: 6-element numeric array (2x3 matrix in column major order)
  • translate: 2-element array
  • rotate: number (angle in radians)
  • scale: number (uniform scale) or 2-elem array

If no transform, but others are given, the resulting transformation order will always be TRS. Any string values will be used as-is and therefore need to be complete, e.g. { rotate: "rotate(60)" }

Status

STABLE - used in production

Search or submit any issues for this package

Installation

yarn add @thi.ng/hiccup-svg

ESM import:

import * as svg from "@thi.ng/hiccup-svg";

Browser ESM import:

<script type="module" src="https://esm.run/@thi.ng/hiccup-svg"></script>

JSDelivr documentation

For Node.js REPL:

const svg = await import("@thi.ng/hiccup-svg");

Package sizes (brotli'd, pre-treeshake): ESM: 2.49 KB

Dependencies

Note: @thi.ng/api is in most cases a type-only import (not used at runtime)

Usage examples

17 projects in this repo's /examples directory are using this package:

Screenshot Description Live demo Source
Tool to interactively compute & visualize color contrasts against WCAG threshold Demo Source
Probabilistic color theme generator Demo Source
Heatmap visualization of this mono-repo's commits Source
Basic crypto-currency candle chart with multiple moving averages plots Demo Source
Color palette generation via dominant color extraction from uploaded images Demo Source
Mouse gesture / stroke analysis, simplification, corner detection Demo Source
Various hdom-canvas shape drawing examples & SVG conversion / export Demo Source
CLI util to visualize umbrella pkg stats Source
Generate SVG using pointfree DSL Source
Polygon to cubic curve conversion & visualization Demo Source
Animated SVG elements with reactive attributes Demo Source
rdom powered SVG graph with draggable nodes Demo Source
Interactive grid generator, SVG generation & export, undo/redo support Demo Source
SVG path parsing & dynamic resampling Demo Source
Additive waveform synthesis & SVG visualization with undo/redo Demo Source
Interactive ridge-line plot Demo Source
Interactive scatter & line plot of low-discrepancy samples Demo Source

API

Generated API docs

import * as svg from "@thi.ng/hiccup-svg";
import { serialize } from "@thi.ng/hiccup";
import * as fs "node:fs";

fs.writeFileSync(
    "hello.svg",
    serialize(
        svg.svg(
            { width: 100, height: 100 },
            svg.defs(svg.linearGradient("grad", [0, 0], [0, 1], [[0, "red"], [1, "blue"]])),
            svg.circle([50, 50], 50, { fill: "url(#grad)" }),
            svg.text([50, 55], "Hello", { fill: "white", "text-anchor": "middle" })
        )
    ));

Minimal example showing SVG conversion of a hiccup-canvas scene (also see @thi.ng/geom for another compatible approach):

import { svg } from "@thi.ng/hiccup-svg";
import { serialize } from "@thi.ng/hiccup";
import { writeFileSync } from "node:fs";

// scene tree defined for hiccup-canvas
const scene = [
    ["defs", {},
        ["radialGradient",
            { id: "bg", from: [150, 280], to: [150, 300], r1: 300, r2: 100 },
            [[0, "#07f"], [0.5, "#0ef"], [0.8, "#efe"], [1, "#af0"]]],
        ["radialGradient",
            { id: "sun", from: [110, 120], to: [110, 120], r1: 5, r2: 50 },
            [[0, "#fff"], [1, "rgba(255,255,192,0)"]]]
    ],
    ["circle", { fill: "$bg" }, [150, 150], 130],
    ["circle", { fill: "$sun" }, [110, 120], 50],
];

writeFileSync(
    "radialgradient.svg",
    serialize(
        svg({ width: 300, height: 300, __convert: true }, scene)
    )
);

Result:

<svg version="1.1"
    xmlns="http://www.w3.org/2000/svg"
    xmlns:xlink="http://www.w3.org/1999/xlink" width="300" height="300">
    <defs>
        <radialGradient id="bg" fx="150.00" fy="280.00" cx="150.00" cy="300.00" fr="300.00" r="100.00" gradientUnits="userSpaceOnUse">
            <stop offset="0" stop-color="#07f"/>
            <stop offset="0.5" stop-color="#0ef"/>
            <stop offset="0.8" stop-color="#efe"/>
            <stop offset="1" stop-color="#af0"/>
        </radialGradient>
        <radialGradient id="sun" fx="110.00" fy="120.00" cx="110.00" cy="120.00" fr="5.00" r="50.00" gradientUnits="userSpaceOnUse">
            <stop offset="0" stop-color="#fff"/>
            <stop offset="1" stop-color="rgb(255,255,192)" stop-opacity="0"/>
        </radialGradient>
    </defs>
    <circle cx="150.00" cy="150.00" r="130.00" fill="url(#bg)"/>
    <circle cx="110.00" cy="120.00" r="50.00" fill="url(#sun)"/>
</svg>

Authors

If this project contributes to an academic publication, please cite it as:

@misc{thing-hiccup-svg,
  title = "@thi.ng/hiccup-svg",
  author = "Karsten Schmidt",
  note = "https://thi.ng/hiccup-svg",
  year = 2016
}

License

© 2016 - 2024 Karsten Schmidt // Apache License 2.0