with one click
gum-jsx
// Create plots, diagrams, and other visualizations with the "gum.jsx" language.
// Create plots, diagrams, and other visualizations with the "gum.jsx" language.
| name | gum-jsx |
| description | Create plots, diagrams, and other visualizations with the "gum.jsx" language. |
The gum.jsx language allows for the elegant and concise creation of SVG visualizations. It has a React-like JSX syntax. When interpreted, it produces pure SVG of a specified size. It is a library of Element derived components such as Circle, Stack, Plot, Network, and many more. Some of these map closely to standard SVG objects, while others are higher level abstractions and layout containers. You can add standard SVG attributes (like fill, stroke, stroke-width, opacity, etc.) to any Element component and they will be applied to the resulting SVG.
Proportional values: In most cases, values are passed in proportional floating point terms. So to place an object in the center of its parent, you would specify pos = [0.5, 0.5], with the size specified by size. When dealing with inherently absolute concepts like stroke-width, standard SVG units are used, and numerical values assumed to be specified in pixels. Most Element objects fill the standard coordinate space [0, 0, 1, 1] by default. To reposition them, either pass the appropriate internal arguments (such as pos or size) or use a layout component such as Box or Stack to arrange them.
Aspect ratio: Any Element object can have an aspect ratio aspect. If aspect is not defined, it will stretch to fit any box, while if aspect is defined it will be sized so as to fit within the specified rectangle while maintaining its aspect ratio. However, when expand is set to true, the element will be sized to cover the specified rectangle, while maintaining its aspect ratio.
Subunit arguments: For compound elements that inherit Group, some keyword arguments are passed down to the constituent parts. For instance, in Plot, one can specify arguments intended for the XAxis unit by prefixing them with xaxis-. For example, setting the stroke-width for this subunit can be achieved with xaxis-stroke-width.
Functional approach: Avoid explicit for loops. There are numpy-like functions such as range and linspace for generating arrays, zip for combining arrays, and palette for interpolating colors. Additionally, you can use map to generate a list of elements (such as bars for a bar plot).
Style tips: There will be cases where a user prompt does not fully specify every detail. In these cases, use your best judgment and consider the following suggestions:
ysize of about 0.1 to 0.2 works well0.03 is good for small features0.1 to 0.2 works well. The best way to create outer margins is to wrap the final output in a Box or Frame component2 for things like plots and diagrams<svg> element will have fill="none" and stroke="black" applied automatically, so you only need to specify these attributes when they differ from these defaultsBelow are some examples of user prompts and code output. In these examples, we try to highlight the most common use cases for the library, as well as some of the common pitfalls that one might encounter.
Example 1: Basic Frame
Let's start with a very simple example that creates a blue circle (aspect = 1) and encloses it in a rounded frame. In general, Box is an extremely useful component, as its child class Frame, which simply adds a default border = 1. This closely mirrors the behavior of a typical HTML element with Tailwind-like syntax.
Prompt: Create a blue circle that is enclosed in a rounded box. It should mostly fill the box, but not completely.
Generated code:
<Frame padding margin rounded>
<Circle fill={blue} />
</Frame>
Here we used boolean defaults for padding, margin, and rounded. These will get mapped to padding={0.1}, margin={0.1}, and rounded={0.1} respectively. This adds a little bit of a Tailwind-like flavor and is used throughout the library. Adding a margin on the outside is usually good so things like borders don't get clipped.
Example 2: Symbolic Plot
Now let's do a more complex example that uses a higher level component that is not a standard SVG element. In this case, we use Plot, which is a Graph subclass that adds axes (with Axis/HAxis/VAxis), labels, and other plot-specific features.
Prompt: Create a simple plot of a sine wave titled "Sine Wave" with dashed grid lines.
Generated code:
<Plot aspect={2} margin={0.2} ylim={[-1.5, 1.5]} title="Sine Wave" grid grid-stroke-dasharray={4}>
<SymLine fy={sin} xlim={[0, 2*pi]} />
</Plot>
Notice how we can pass arguments to sub-components using prefixing. Here we set the stroke-dasharray for the grid lines to 4. For convenience, Plot accepts Box arguments such as margin, border, etc.
Example 3: Custom Component
Creating custom components is a powerful way to avoid code duplication and to make your code more readable and maintainable. Here we create a Squire component that is a Square with a default rounded border. Then we use this component to create two squares side by side.
Prompt: Create two rounded boxes side by side. Left one should be blue, right one should be red.
Generated code:
const Squire = (attr) => <Square rounded {...attr} />
return <HStack spacing>
<Squire fill={blue} />
<Squire fill={red} />
</HStack>
Note: Because this is two squares (aspect = 1) stacked side by side, the HStack will have an aspect ratio of 2. You can also use the VStack component to stack elements vertically. If you wish to override aspect-based sizing, you can use the stack-size argument to specify the size of the child elements.
Example 4: Modular Approach
Now let's cover a VERY IMPORTANT method of using custom components. We want to create modular and parameterizable components that can be reused in a variety of ways. Here we create a Star component that specifies its Spline path in internal coordinates, and we then pass the pos/size arguments to the component to position and scale the star.
Prompt: Create a semi-random starfield with about 10 yellow stars.
Generated code:
const aster = [
[0.5, 0], [0.65, 0.35],
[1, 0.5], [0.65, 0.65],
[0.5, 1], [0.35, 0.65],
[0, 0.5], [0.35, 0.35],
]
const points = [
[0.15, 0.20], [0.72, 0.12], [0.45, 0.55],
[0.88, 0.68], [0.30, 0.80], [0.60, 0.35],
[0.10, 0.58], [0.82, 0.42], [0.50, 0.90],
[0.25, 0.10],
]
const Star = (attr) =>
<Spline size={0.06} aspect closed points={aster} {...attr} />
return <Box border rounded fill={gray}>
{points.map(p => <Star pos={p} fill={yellow} />)}
</Box>
Note: By creating a reusable Star component that accepts pos/size arguments, we can easily create a starfield by mapping over points. This offloads the logic of adding and scaling the spline coordinates in aster to the positions listed in points.
Example 5: Stacking Layouts
Stacking components can be tricky. If all your children have well-defined aspect ratios, it's often quite straightforward. But even in that case, you may want to override the default aspect-based sizing. Let's look at the case of a wide figure with a short text label underneath it.
<VStack spacing={0.05}>
<Rect rounded aspect={5} fill={blue} />
<Text stack-size={0.2}>Hello World!</Text>
</VStack>
Without specifying stack-size, the Text element would be quite large (it has an aspect ratio of around 6). By specifying stack-size, we can force the text to be smaller. Because it will be thinner than its assigned rectangle, we need to take a stance on how to justify it. By default it will be centered, but this can be overridden with the justify argument.
Here are the handy array functions provided by the library. All of these mimic the behavior of their counterparts in Python and numpy. This can be useful for generating Element objects from arrays:
function zip(...arrs: any[]): any[]
function range(ia: number, ib?: number, step?: number = 1): number[]
function linspace(x0: number, x1: number, num: number, end?: boolean = false): number[]
function enumerate(x: any[]): any[]
function repeat(x: any, n: number): any[]
Some of the most commonly used mathematical constants are pre-defined in the global scope:
const e = Math.E // base of the natural logarithm
const pi = Math.PI // ratio of circumference to diameter
const phi = (1 + sqrt(5)) / 2 // golden ratio
const r2d = 180 / Math.PI // conversion from radians to degrees
const d2r = Math.PI / 180 // conversion from degrees to radians
Additionally, there is a default gum.jsx color palette that is pre-defined in the global scope, but you can also use any valid CSS color string:
const none = 'none'
const white = '#ffffff'
const black = '#000000'
const blue = '#1e88e5'
const red = '#ff0d57'
const green = '#4caf50'
const yellow = '#ffb300'
const purple = '#9c27b0'
const gray = '#f0f0f0'
To interpolate colors between color values, you can use these functions:
function interp(c1: string, c2: string, x: number): string
function palette(c1: string, c2: string, clim: range): number => string
Below is the full documentation for the core gum.jsx components: Element, Group, and Box. All of the other components are derived from these and many use them as sub-components. Understanding these three components will give you a good foundation for working with the library.
The base class for all gum.jsx objects. You will usually not be working with this object directly unless you are implementing your own custom elements. An Element has a few methods that can be overriden, each of which takes a Context object as an argument. The vast majority of implementations will override only props and inner (for non-unary elements).
The position and size of an element are specified in the internal coordinates (coord) of its parent, which defaults to the unit square. Rectangles are always specified in [left, top, right, bottom] format. You can also specify the placement by specifying pos and size or various combinations of xsize/ysize. When not specified, rect defaults to the unit square.
Parameters:
tag = g — the SVG tag associated with this elementunary = false — whether there is inner text for this elementaspect = null — the width to height ratio for this elementpos — the desired position of the center of the child's rectanglesize — the desired size of the child's rectangle (can be single number or pair)xsize/ysize — specify the size for a specific dimension (and expand the other)rect — a fully specified rectangle to place the child in (this will override pos/size)aspect — the aspect ratio of the child's rectangleexpand — when true, instead of embedding the child within rect, it will make the child just large enough to fully contain rectalign — how to align the child when it doesn't fit exactly within rect, options are left, right, center, or a fractional position (can set vertical and horizontal separately with a pair)rotate — how much to rotate the child by (degrees counterclockwise)spin — like rotate but will maintain the same sizeflex — override to set aspect = null... = {} — additional attributes that are included in propsMethods:
props(ctx) — returns a dictionary of attributes for the SVG element. The default implementation returns the non-null attr passed to the constructorinner(ctx) — returns the inner text of the SVG element (for non-unary). Defaults to returing empty stringsvg(ctx) — returns the rendered SVG of the element as a String. Default implementation constructs SVG from tag, unary, props, and innerExample
Prompt: draw a 2x1 rectangle in red with a blue square embedded within it
Generated code:
<Group aspect={2}>
<Rect stroke={red} />
<Square stroke={blue} />
</Group>
Inherits: Element
This is the main container class that all compound elements are derived from. It accepts a list of child elements and attempts to place them according to their declared properties. Child placement positions are specified in the group's internal coordinates (coord), which defaults to the unit square. The coordinate space is specified in [left, top, right, bottom] format.
The child's aspect is an important determinant of its placement. When the child does not have an aspect, it will fit exactly in the given rect. When it does have an aspect, it will be made as large as possible while still fitting in the given rect. The align argument governs the exact placement in this case, while the expand flag makes it as small as possible while still covering the given rect.
One common pitfall: using coord will affect that placement of child elements. But for graphing elements like Line or Points, their points values are evaluate based on their own coordinate system, not the containing Group's. You must either give them their own coord or use Graph, which automatically propagates the coordinate system to all children.
To help with debugging, a debug flag can be passed to show stencil lines indicating the childrens' placement. The allocated space is shown in dashed blue, while the realized position (accounting for aspect and alignment) is shown in dashed red.
Parameters:
children = [] — a list of child elementsaspect = null — the aspect ratio of the group's rectangle (can pass 'auto' to infer from the children)coord = [0, 0, 1, 1] — the internal coordinate space to use for child elements (can pass 'auto' to contain children's rects)clip = false — clip children to the group's rectangle if true (or a custom shape if specified)debug = false — show debug boxes for the childrenExample
Prompt: a square in the top left and a circle in the bottom right
Generated code:
<Group>
<Rectangle pos={[0.3, 0.3]} size={0.2} spin={15} />
<Ellipse pos={[0.7, 0.7]} size={0.2} />
</Group>
Inherits: Group > Element
This is a simple container class allowing you to add padding, margins, and a border to a single Element. It's pretty versatile and is often used to set up the outermost positioning of a figure. Mirroring the standard CSS definitions, padding is space inside the border and margin is space outside the border. This has no border by default, but there is a specialized subclass of this called Frame that defaults to border = 1.
Box can be pretty handly in various situations. It is differentiated from Group in that it will adopt the aspect of the child element. This is useful if you want to do something like shift an element up or down by a certain amount while maintaining its aspect ratio. Simply wrap it in a Box and set child's pos to the desired offset.
There are multiple ways to specify padding and margins. If given as a scalar, it is constant across all sides. If two values are given, they correspond to the horizontal and vertical sides. If four values are given, they correspond to [left, top, right, bottom].
The adjust flag controls whether padding/margins are adjusted for the aspect ratio. If true, horizontal and vertical components are scaled so that their ratio is equal to the child element's aspect ratio. This yields padding/margins of constant apparent size regardless of aspect ratio. If false, the inputs are used as-is.
Parameters:
padding = 0 / 0.1 — the padding to be added (inside border)margin = 0 / 0.1 — the margin to be added (outside border)border = 0 / 1 — the border width to use (stroke in pixels)rounded = 0 / 0.1 — the border rounding to use (proportional to the box size)fill = null — the background color to use (default is no fill)adjust = true — whether to adjust values for aspect ratioshape = Rect — the shape class to use for the borderclip = false — whether to clip the contents to the border shapeSubunit names:
border — keywords to pass to border, such as stroke or stroke-dasharrayExample
Prompt: the text "hello!" in a frame with a dashed border and rounded corners
Generated code:
<Box padding border rounded border-stroke-dasharray={5}>
<Text>hello!</Text>
</Box>
Below is a list of topics to reference for documentation and usage examples for the various components. Every gum.jsx component is documented here. Your code must either use these components or create its own custom components. Before using a component, be sure to read the relevant reference to fully understand its parameters and capabilities.
File: layout
These are the raw layout components that assist you in arranging elements in a figure. They typically take a list of child elements and arrange them in a specified way. Box is a simple container element that can be used to add padding, border, and rounded corners to a group of elements.
Stack lets you arrange elements in a vertical or horizontal stack (like flexbox in CSS) in a way that respects the aspect ratio of the child elements. Typically you would use the specialized subclasses VStack and HStack for vertical and horizontal stacks, respectively. Grid does something similar but for a 2D grid of rows and columns (like grid in CSS).
Points is different in that it takes a list of locations and arranges a given element at each of those locations (with the default element being Dot, a solid filled Circle).
Components:
File: shapes
These are the basic geometric shapes that can be used to create more complex figures. Both Rect and Ellipse are aspectless by default but can be given an aspect ratio to control their shape (i.e., a circle is an Ellipse with an aspect of 1).
Line is actually more general than just a single straight line. It can be used to draw piecewise linear paths by passing a list of points. For the case of simple unit lines, use UnitLine and its specialized variants VLine and HLine instead. For closed paths, either pass closed to Line or use Shape instead.
For multi-segment Bézier splines, Spline is the way to go. It takes a list of control points and draws a smooth cubic spline through them. You can control the tension of the spline with the curve parameter (default is 0.5). This also accepts a closed parameter to draw a closed spline.
Components:
File: text
These are components that can be used to create text elements. Text is a fairly sophisticated component that handles text wrapping, line spacing, and other text-related features. You can specify the wrap width (in "ems", that is, in proportion to the line height) with the wrap parameter and the alignment with the justify parameter. Feel free to intersperse non-text elements with text elements to create more complex layouts.
TextStack is a simple component that stacks text elements vertically. Specifying a wrap width will cause every child element to be wrapped to the specified width. You can specify the vertical spacing between the elements with the spacing parameter. The TitleFrame is a Frame subclass that automatically adds a boxed title to the top of the frame. Finally, Slide is basically a TextStack wrapped in a TitleFrame.
Latex does what it sounds like: it renders a single LaTeX equation. This uses MathJax under the hood, so it supports most but not all inline LaTeX features.
Components:
File: math
These are components for creating mathematical expressions. By far the most common usage is to pass a LaTeX style math expression to the Latex component. However, you can get very fine grained control over the layout of mathematical expressions with MathText as your outer wrapper and the SupSub, Frac, and Bracket components.
Components:
File: symbolic
These components allow you to plot functions symbolically. That is, they accept functions as arguments and plot them accordingly. Functions can be specified as [x => y], [y => x], or [t => (x,y)]. You can control the range over which the domain is sampled with the tlim/xlim/ylim parameters. You can also control the number of samples to take with the N parameter.
These clearly extend their non-Sym counterparts by adding the ability to plot functions symbolically. The only additional element is SymFill, which plots a filled area between two functions. For this one, passing a constant to either fy1 or fy2 is equivalent to passing a constant function.
Components:
File: plotting
There are components for creating various types of plots. The core element is Graph, which is a container element that accepts a list of children to plot over a specified coordinate system (xlim/ylim/coord). Plot is a Graph subclass that adds axes (with Axis/HAxis/VAxis), labels, and other plot-specific features. BarPlot is a help element that wraps a Bars element inside of a Plot.
The Plot element in particular is highly customizable, and you can pass arguments to sub-components using axis/label/title prefixes. For instance, to specify the stroke width of the x-axis, you can use xaxis-stroke-width. This logic applies to other types of compound components as well.
Components:
Plot)File: networks
These are components for creating network diagrams. The core element is Network, which is a container element that accepts a list of Nodes and Edges, as well as potentially other elements like labels. A Node can specify an id to be used to reference it from an Edge as either the source (from) or destination (to). Default values for Node and Edge arguments can be specified with node- and edge- prefixed arguments passed to the Network element.
The Edge element has a dir1 and dir2 parameter to specify the direction of the arrowhead for the source and destination nodes, respectively. You can also toggle arrowheads on either side with arrow/from-arrow/to-arrow or specify the curve parameter to control the curvature of the edge.
Components:
File: utilities
These are the helper functions that are available in the library. They are not components themselves, but they are useful for creating and manipulating data. Many of them mimic the behavior of their counterparts in Python and numpy and are useful for generating Element objects from arrays. There are also some commonly used mathematical constants and tools for interpolating colors.
Components:
There is a gallery of more complex examples available. Each is a single markdown file with a complete gum.jsx code example and accompanying text description. These are available in the references/gala directory. Here is a brief description of each along with a list of the main elements used:
To test the output of a particular Gum JSX snippet or file, you can pipe it to the gum command. If this command is not available globally, try to install the gum-jsx package with Bun (preferred) or NPM.
If you have vision capabilities, seeing an actual image can be useful for see the actual output of the code, either in SVG or PNG format. Even without vision, one can infer properties of the output by reading the SVG output directly.
For one off tests, pipe the code using echo. It is recommended that you use single quotes as the outer delimiter, to accommodate code that includes double quotes for component properties (e.g. justify="left").
For more difficult tasks, use a file provide the filename as an argument or cat it in. Using a file allows you to view and refine your code repeatedly. If you wish to avoid output redirection to a file, use the -o option to write to a file.
In general, it makes a lot of sense to write a draft to a file, view its output, then refine the code until you're satisfied. This way you can start simple and add complexity as needed. When in doubt, write the output file to the same directory as the input file with the same base name but with the appropriate extension.
Examples:
# generate an .svg file from a .jsx file
gum test.jsx -o test.svg
# generate a .png file from a .jsx file
gum test.jsx -o test.png
# generate SVG from a Gum JSX snippet and print to stdout
echo '<Rectangle rounded fill={blue} />' | gum
# generate PNG from a Gum JSX snippet and save to file
echo '<Rectangle rounded fill={blue} />' | gum -o test.png
CLI options:
file: Gum JSX file to render (reads from stdin if not provided)-t, --theme <theme>: theme to use (default: light)-b, --background <color>: background color (default: white)-f, --format <format>: output format: json, svg, png, kitty (default: auto)-o, --output <output>: output file (default: stdout)-s, --size <size>: size of the SVG (default: 1000)-w, --width <width>: max width of the PNG (default: null)-h, --height <height>: max height of the PNG (default: null)There are a couple of ways to use Gum in TypeScript. You can evaluate JSX strings directly, or you can construct Gum components directly. If you want to use Gum components in React, you can use the react-gum-jsx package.
The evaluateGum function from gum-jsx/eval parses a JSX string and returns an Svg element tree. Call .svg() on the result to get an SVG string.
import { evaluateGum } from 'gum-jsx/eval'
import { rasterizeSvg } from 'gum-jsx/render'
import { writeFileSync } from 'fs'
// parse JSX string into element tree, then render to SVG
const tree = evaluateGum('<Rectangle rounded fill={blue} />')
const svg = tree.svg()
// render to PNG buffer
const png = rasterizeSvg(svg, {
size: tree.size,
background: 'white',
})
writeFileSync('output.png', png)
The evaluateGum function accepts options for theme, size, and extra context variables:
const tree = evaluateGum(code, {
theme: 'light',
size: 500,
})
You can also construct Gum components directly by importing them from gum-jsx and calling their constructors. Each component takes a single args object.
import { Svg, Rectangle, Circle, HStack, Text, blue, red, white } from 'gum-jsx'
// create elements by calling constructors directly
const rect = new Square({ rounded: true, fill: blue })
const circle = new Circle({ fill: red })
const label = new Text({ children: ['Hello'], fill: white })
const layout = new HStack({ children: [rect, circle, label], spacing: 0.1 })
// wrap in Svg and render
const tree = new Svg({ children: [layout], size: 500 })
const svg = tree.svg()
When constructing manually, note that:
children is always passed as an array in the args objectblue, red, none, etc. are exported from gum-jsxrange, linspace, zip are also available from gum-jsx.svg() on the top-level Svg element to get the SVG string outputSvg element as sizereact-gum-jsxYou can use Gum components directly in React components by importing from the react-gum-jsx package. This is useful for creating interactive visualizations in React.
Here's an example of how to use Gum in a React component. It's basically the same as what you would pass to evaluateGum but as a default export:
import { blue, red } from 'gum-jsx'
import { GUM } from 'react-gum-jsx'
const { Frame, HStack, Square, Circle, Text } = GUM
export default function Demo() {
return <Frame padding margin rounded>
<HStack padding>
<Square fill={blue} />
<Circle fill={red} />
<Text>Hello</Text>
</HStack>
</Frame>
}
To run this in a CLI setting, just pass a file with a default export to the gum-react command that comes with the react-gum-jsx package. This takes very similar arguments to the regular gum command.
If you are in a web setting, you'll need to wrap this export in a <Gum> component, which takes roughly the same arguments as evaluateGum. This would look like:
import { Gum } from 'react-gum-jsx'
<Gum size={[640, 360]}>
<Demo />
</Gum>
If the inner component has an aspect it will be embedded inside the given size bounds. If it is aspectless, it will be stretched to fill the given size bounds.
[HINT] Download the complete skill directory including SKILL.md and all related files