Tutorial Part 1: The Basics
In this tutorial, you'll make this diagram of the four terrestrial planets:
import "./styles.css"; document.getElementById("app").innerHTML = ` <h1>Hello world</h1> `;
This tutorial doesn't assume you know anything about Bluefish or UI frameworks. Along the way we'll encounter marks, relations, and declarative references.
Tutorial setup
Click "Open Sandbox" in the bottom right corner to open the editor in a new tab using the website CodeSandbox. If that doesn't work, you can also open this tutorial in a new tab and work in this editor directly.
INFO
You may see an error like
Argument of type 'HTMLElement | null' is not assignable to parameter of type 'MountableElement'. Type 'null' is not assignable to type 'MountableElement'.typescript(2345)
This is expected and won't affect your ability to follow the tutorial. If you want to fix it, replace the last line of code with render(Diagram, document.getElementById("app")!);
(Notice the ! towards the end.)
import "./styles.css"; document.getElementById("app").innerHTML = ` <h1>Hello world</h1> `;
A look at the starter code
There are a bunch of files in the sandbox, but the one to pay attention to is index.ts. This is where our spec lives. Let's walk through the file line by line.
Import Bluefish
// prettier-ignore
import { Group, StackH, StackV, Circle, Text, Ref, Background, Arrow, Align, Distribute, Rect, render } from "bluefish-js";
function Diagram() {
return Circle({ r: 15, fill: "#EBE3CF", "stroke-width": 3, stroke: "black" });
}
render(Diagram, document.getElementById("app"));2
3
4
5
6
7
8
We first import all the components we'll need from the Bluefish package, which is called bluefish-js.
INFO
We use // prettier-ignore to tell the linter to ignore the next line of code. We're doing this to prevent the linter from complaining about the import statement being too long and reformatting it.
Export Default Function
// prettier-ignore
import { Group, StackH, StackV, Circle, Text, Ref, Background, Arrow, Align, Distribute, Rect, render } from "bluefish-js";
function Diagram() {
return Circle({ r: 15, fill: "#EBE3CF", "stroke-width": 3, stroke: "black" });
}
render(Diagram, document.getElementById("app"));2
3
4
5
6
7
8
This defines a function called Diagram that will return our diagram when it's called.
The Circle mark
// prettier-ignore
import { Group, StackH, StackV, Circle, Text, Ref, Background, Arrow, Align, Distribute, Rect, render } from "bluefish-js";
function Diagram() {
return Circle({ r: 15, fill: "#EBE3CF", "stroke-width": 3, stroke: "black" });
}
render(Diagram, document.getElementById("app"));2
3
4
5
6
7
8
Right now our diagram consists of a single Circle mark. A mark draws a primitive object (like a circle or a piece of text) on the screen.
We give arguments to the Circle element, called props, as a JavaScript object.
NOTE
Marks in Bluefish are similar to SVG primitives, not marks in charting libraries. They only draw a single element.
render
// prettier-ignore
import { Group, StackH, StackV, Circle, Text, Ref, Background, Arrow, Align, Distribute, Rect, render } from "bluefish-js";
function Diagram() {
return Circle({ r: 15, fill: "#EBE3CF", "stroke-width": 3, stroke: "black" });
}
render(Diagram, document.getElementById("app"));2
3
4
5
6
7
8
The render function takes two arguments: a function that returns our diagram, and an HTML element to render the diagram to. The render function inserts our diagram into the DOM element we pass in.
Build the row of planets
Right now we just have one circle. To make more, we can just copy-paste the Circle mark a few times with different sizes and fills, and put them in a Group:
// prettier-ignore
import { Group, StackH, StackV, Circle, Text, Ref, Background, Arrow, Align, Distribute, Rect, render } from "bluefish-js";
function Diagram() {
return Group([
Circle({ r: 15, fill: "#EBE3CF", "stroke-width": 3, stroke: "black" }),
Circle({ r: 36, fill: "#DC933C", "stroke-width": 3, stroke: "black" }),
Circle({ r: 38, fill: "#179DD7", "stroke-width": 3, stroke: "black" }),
Circle({ r: 21, fill: "#F1CF8E", "stroke-width": 3, stroke: "black" }),
]);
}
render(Diagram, document.getElementById("app"));2
3
4
5
6
7
8
9
10
11
12
13
But they're drawn on top of each other!

That's because Group just renders all its children. It doesn't do any fancy layout. If a child's position or size fields aren't otherwise constrained, Group will default those fields to 0.
NOTE
When we pass multiple children to a relation, we pass them as an array.
Create a row using the StackH relation
To fix this, we can replace our Group with the StackH relation. This relation places its children in a horizontal row. A relation visually arranges its child elements.
// prettier-ignore
import { Group, StackH, StackV, Circle, Text, Ref, Background, Arrow, Align, Distribute, Rect, render } from "bluefish-js";
function Diagram() {
return Group([
return StackH(
{ spacing: 50 },
[
Circle({ r: 15, fill: "#EBE3CF", "stroke-width": 3, stroke: "black" }),
Circle({ r: 36, fill: "#DC933C", "stroke-width": 3, stroke: "black" }),
Circle({ r: 38, fill: "#179DD7", "stroke-width": 3, stroke: "black" }),
Circle({ r: 21, fill: "#F1CF8E", "stroke-width": 3, stroke: "black" }),
]
);
}
render(Diagram, document.getElementById("app"));2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

This StackH relation horizontally stacks its children 50 pixels apart from each other and vertically centers them.
Nest StackH in a Background relation
Next we'll put a background around the planets. To do so, we'll use the Background relation.
// prettier-ignore
import { Group, StackH, StackV, Circle, Text, Ref, Background, Arrow, Align, Distribute, Rect, render } from "bluefish-js";
function Diagram() {
return Background(
{ padding: 40, fill: "#859fc9", stroke: "none" },
StackH({ spacing: 50 }, [
Circle({ r: 15, fill: "#EBE3CF", "stroke-width": 3, stroke: "black" }),
Circle({ r: 36, fill: "#DC933C", "stroke-width": 3, stroke: "black" }),
Circle({ r: 38, fill: "#179DD7", "stroke-width": 3, stroke: "black" }),
Circle({ r: 21, fill: "#F1CF8E", "stroke-width": 3, stroke: "black" }),
]),
);
}
render(Diagram, document.getElementById("app"));2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

We've given this Background relation 40px of padding on each side and specified fill and stroke colors.
Add a label
Now that we've created a horizontal row of the planets, let's add a label to one of them.
Overlap relations with Ref
We first want our diagram to look like this:

Let's first describe our addition to the diagram in words. We've vertically stacked a piece of text above the Mercury circle. We can then annotate this with all the marks and relations we're using:
We've (i) vertically stacked a piece of (ii) text above the (iii) Mercury circle.
And now we can translate this description into Bluefish code!
// prettier-ignore
import { Group, StackH, StackV, Circle, Text, Ref, Background, Arrow, Align, Distribute, Rect, render } from "bluefish-js";
function Diagram() {
return [
Background(
{ padding: 40, fill: "#859fc9", stroke: "none" },
StackH(
{ spacing: 50 },
Circle({ r: 15, fill: "#EBE3CF", "stroke-width": 3, stroke: "black" }),
Circle({ name: "mercury", r: 15, fill: "#EBE3CF", "stroke-width": 3, stroke: "black" }),
Circle({ r: 36, fill: "#DC933C", "stroke-width": 3, stroke: "black" }),
Circle({ r: 38, fill: "#179DD7", "stroke-width": 3, stroke: "black" }),
Circle({ r: 21, fill: "#F1CF8E", "stroke-width": 3, stroke: "black" })
)
),
StackV({ spacing: 30 }, [
Text("Mercury"),
Ref({ select: "mercury" }),
]),
];
}
render(Diagram, document.getElementById("app"));2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
We've (i) added a StackV relation (similar to StackH) to vertically stack the text and circle. This relation contains (ii) a Text mark and (iii) a Ref element that selects the Mercury circle.
Let's walk through this carefully. First we give the Mercury circle a name so we can refer to it later:
Circle({ r: 15, fill: "#EBE3CF", "stroke-width": 3, stroke: "black" }),
Circle({ name: "mercury", r: 15, fill: "#EBE3CF", "stroke-width": 3, stroke: "black" }), 2
Then we create a vertical stack:
StackV({ spacing: 30 }, ...)And place a text mark above the Mercury circle:
StackV({ spacing: 30 }, [Text("Mercury"), Ref({ select: "mercury" })]);A Ref creates a declarative reference. This reference is a pointer to an existing element in our diagram. A relation (e.g., StackV) can read and write the size and position of an element through a Ref.
Ref's are declarative, because they cannot override properties that have already been set. For example, notice that StackV does not break the Mercury circle's relationship to the other planets that StackH already defined.
Put a Background behind the planet and the label
Finally, we can place a Background relation behind the label just as we did with the Background behind the planets.
// prettier-ignore
import { Group, StackH, StackV, Circle, Text, Ref, Background, Arrow, Align, Distribute, Rect, render } from "bluefish-js";
function Diagram() {
return [
Background(
{ padding: 40, fill: "#859fc9", stroke: "none" },
StackH({ spacing: 50 }, [
Circle({ name: "mercury", r: 15, fill: "#EBE3CF", "stroke-width": 3, stroke: "black" }),
Circle({ r: 36, fill: "#DC933C", "stroke-width": 3, stroke: "black" }),
Circle({ r: 38, fill: "#179DD7", "stroke-width": 3, stroke: "black" }),
Circle({ r: 21, fill: "#F1CF8E", "stroke-width": 3, stroke: "black" }),
])
),
Background(
{ rx: 10 },
StackV({ spacing: 30 }, [Text("Mercury"), Ref({ select: "mercury" })])
),
];
}
render(Diagram, document.getElementById("app"));2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

Wrapping up
Tada! We've completed our first Bluefish diagram. Along the way we encountered some Bluefish marks (Circle and Text), some Bluefish relations (StackH, StackV, and Background), and declarative references (Ref) for referring to existing elements.
Here's what the finished code should look like:
import "./styles.css"; document.getElementById("app").innerHTML = ` <h1>Hello world</h1> `;
If you want to explore more, try playing around with spacing and padding, or swapping StackH and StackV.
