Published
Edited
Feb 11, 2022
2 forks
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
render(M.text({contents: "Hello World!", fontSize: "18pt", }))
Insert cell
// TODO: add some interactivity to these inputs
render(M.ellipse({cx: 200, cy: 50, "fill": "firebrick"}));
Insert cell
render(M.rect({width: 100, height: 200, "fill": "steelblue"}));
Insert cell
Insert cell
// `glyphView` lets you view the glyph while also being able to reuse it in other cells (in this case using the `rect` variable)
viewof rect = view(M.rect({width: 100, height: 200, "fill": "steelblue"}));
Insert cell
Insert cell
Insert cell
Insert cell
render(createShape({
shapes: {
"g1": M.text({x: 0, contents: "Hello", fontSize: "18pt", }),
"g2": M.text({x: 70, contents: "World!", fontSize: "18pt", }),
},
}));
Insert cell
render(createShape({
shapes: {
"g1": M.rect({x: 0, width: 50, height: 100, fill: "steelblue", }),
"g2": M.rect({x: 70, width: 100, height: 100, fill: "firebrick", }),
},
}));
Insert cell
Insert cell
Insert cell
render(createShape({
shapes: {
"g1": M.text({contents: "Hello", fontSize: "18pt", }),
"g2": M.text({contents: "World!", fontSize: "18pt", }),
},
rels: { "g1->g2": [C.hAlignCenter, C.hSpace(20)] }
}));
Insert cell
render(createShape({
shapes: {
"g1": M.rect({width: 200, height: 100, fill: "steelblue", }),
"g2": M.rect({width: 150, height: 50, fill: "firebrick", }),
},
rels: { "g1->g2": [C.alignBottom, C.hSpace(spacing)]},
}));
Insert cell
Insert cell
Insert cell
render(createShape({
shapes: {
"g1": M.text({contents: "Hello", fontSize: "18pt", }),
"g2": M.text({contents: "World!", fontSize: "18pt", }),
},
rels: { "g1->g2": [C.hAlignCenter, C.hSpace(spacing)] },
}));
Insert cell
Insert cell
Insert cell
// adding empty data
render({}, createShapeFn({
shapes: {
"g1": M.text({contents: "Hello", fontSize: "18pt", }),
"g2": M.text({contents: "World!", fontSize: "18pt", }),
},
rels: { "g1->g2": [C.hAlignCenter, C.hSpace(spacing)] },
}));
Insert cell
render("World", createShapeFn({
shapes: {
"g1": M.text({contents: "Hello", fontSize: "18pt", }),
},
object: createShapeFn((name) => M.text({contents: name + "!", fontSize: "18pt", })),
rels: { "g1->$object": [C.hAlignCenter, C.hSpace(spacing)] },
}));
Insert cell
helloShape = createShapeFn({
shapes: {
"g1": M.text({contents: "Hello", fontSize: "18pt", }),
},
object: createShapeFn((name) => M.text({contents: name + "!", fontSize: "18pt", })),
rels: { "g1->$object": [C.hAlignCenter, C.hSpace(spacing)] }
});
Insert cell
render("World", helloShape);
Insert cell
render("Bluefish", helloShape);
Insert cell
/* two types of shape function */
({
hostFn: (e) => M.nil(),
recordFn: createShapeFn({
shapes: {}, /* optional. shapes that are _not_ data driven (works just like a compound shape) */
fields: {}, /* optional. data-driven shapes. a record mapping fields in the input data to shape functions */
object: (e) => M.nil(), /* optional. data-driven shape. a shape function for the entire object */
rels: {}, /* optional. relations between shapes, fields, and the object (accessible as $object) */
}),
recordFnLowered: ({shapes, fields, object, rels}, {field1, field2}) => createShape({
shapes: {
...shapes,
...fields, /* with field1 and field2 applied appropriately */
"$object": object,
},
rels,
})
})

Insert cell
**important! only record shape functions can be serialized, so if you ultimately want a portable definition, you should use record functions only. this is because host functions can contain arbitrary JS**

*record function API is not complete and is subject to change to improve serialization ability*
Insert cell
Insert cell
Insert cell
**TODO: put motivating example here of adding a bunch of circles and wanting them to be spaced evenly or something.**
Insert cell
ref = bfjs.mkMyRef;
Insert cell
Suppose we want to make a list of marbles.
We can start by writing something like this.
Insert cell
marblesSet = [1, 2, 3];
Insert cell
Insert cell
marblesList = ({
marbles: [1, 2, 3],
neighbor: [
{
curr: ref("../../marbles/0"),
next: ref("../../marbles/1"),
},
{
curr: ref("../../marbles/1"),
next: ref("../../marbles/2"),
}
]
})
Insert cell
Insert cell
## Making a List Function
Lists are a pretty common data structure, so we can write some code to automatically produce this neighbor relation for us:
Insert cell
_ = require("lodash");
Insert cell
mkList = (name, xs) => ({
[name]: xs,
neighbors: xs.length < 2 ? [] :
_.zipWith(_.range(xs.length - 1), _.range(1, xs.length), (curr, next) => (
{
curr: ref(`../../${name}/${curr}`),
next: ref(`../../${name}/${next}`),
}
))
})
Insert cell
/* play around with some input arrays here! */
mkList("marbles", [1, 2, 3]);
Insert cell
marbleShape = createShapeFn({
shapes: {
"circle": M.ellipse({ rx: 300 / 6, ry: 200 / 6, fill: "coral" }),
},
object: (n) => M.text({ contents: n.toString(), fontSize: "24px" }),
rels: { "$object->circle": [C.alignCenterX, C.alignCenterY] },
});
Insert cell
render(1, marbleShape);
Insert cell
marblesShape = createShapeFn({
fields: {
0: marbleShape,
1: marbleShape,
},
rels: {"0->1": [C.hSpace(5.), C.alignCenterY]},
})
Insert cell
render([1, 2], marblesShape);
Insert cell
// uh oh!
render([1, 2, 3], marblesShape);
Insert cell
marblesListShape = createShapeFn({
fields: {
marbles: marbleShape, // renders _every_ marble in the set using marbleGlyph
neighbors: createShapeFn({
rels: { "curr->next": [C.hSpace(5.), C.alignCenterY] }
})
},
})
Insert cell
render(mkList("marbles", [1, 2]), marblesListShape);
Insert cell
render(mkList("marbles", [1, 2, 3]), marblesListShape);
Insert cell
Insert cell
render(mkList("marbles", _.range(1, numMarbles + 1)), marblesListShape);
Insert cell
## Composition in Bluefish
Insert cell
someMarbles = render(mkList("marbles", [1, 2, 3]), marblesListShape);
Insert cell
render(createShape({
shapes: {
"someMarbles": M.nil()/* marblesListShape(mkList("marbles", [1, 2, 3])) */, /* uh oh! cannot currently put someMarbles here (even if rendering step is taken away!! too many hacks) */
}
}));
Insert cell
## Let's Make a Bar Chart!(?!)
Insert cell
/* https://vega.github.io/vega-lite/examples/bar.html */
data = [
{ "category": "A", "value": 28 }, { "category": "B", "value": 55 }, { "category": "C", "value": 43 },
{ "category": "D", "value": 91 }, { "category": "E", "value": 81 }, { "category": "F", "value": 53 },
{ "category": "G", "value": 19 }, { "category": "H", "value": 87 }, { "category": "I", "value": 52 }
];
Insert cell
bar = createShapeFn({
shapes: {
// this tick mark might be a relation glyph in the future
"tick": M.rect({ width: 1., height: 8., fill: "gray" })
},
fields: {
"category": (contents) => M.text({ contents, fontSize: "12px" }),
"value": (height) => M.rect({ width: 20, height, fill: "steelblue" }),
},
rels: {
"value->tick": [C.vSpace(5), C.vAlignCenter],
"tick->category": [C.vSpace(1), C.vAlignCenter],
},
})
Insert cell
render(data[0], bar)
Insert cell
Insert cell
Insert cell
bars = createShapeFn({
fields: {
bars: bar,
neighbors: createShapeFn({
rels: { "curr/value->next/value": [C[alignment === "alignMiddle" ? "hAlignCenter" : alignment], C.hSpace(barSpacing)] }
})
}
});
Insert cell
render(mkList("bars", data), bars);
Insert cell
mkList("bars", data)
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
dev = true
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more