Published
Edited
May 16, 2022
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
render(text('Hello, World!', {fontSize: '18pt'}))
Insert cell
Insert cell
render(M.circle({r: 30, fill: 'coral'}));
Insert cell
Insert cell
render(M.rect({width: 100, height: 200, fill: /* fill in a color here! */}));
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
render(
createGroup({
circle: M.circle({r: 30, fill: 'lightblue'}),
nodeValue: text('3', {fontSize: '16pt'}),
})
)
Insert cell
Insert cell
Insert cell
render(
createGroup({
circle: M.circle({r: 30, fill: 'lightblue'}),
nodeValue: text('3', {fontSize: '16pt'}),
}, {
'circle->nodeValue': [C.alignCenterX, C.alignCenterY],
})
)
Insert cell
Insert cell
Insert cell
nodeShapeT = createGroup({
$circle$ : (color) => M.circle({r: 30, fill: color}),
$nodeValue$: (value) => text(value, {fontSize: "16pt"}),
}, {
'circle->nodeValue': [C.alignCenterX, C.alignCenterY],
})
Insert cell
render({circle: "lightblue", nodeValue: "a"}, nodeShapeT)
Insert cell
render({circle: "coral", nodeValue: "b"}, nodeShapeT)
Insert cell
containsPadding = ({ top, bottom, left, right }) => [C.alignLeftSpace(left), C.alignRightSpace(right), C.alignTopSpace(-top), C.alignBottomSpace(-bottom)]
Insert cell
Insert cell
nodeShapeWithCircleT = createGroup({
$circle$ : (color) => M.circle({r: 30, fill: color}),
containingCircle: M.circle({fill: 'none', stroke: 'firebrick'}),
$nodeValue$: (value) => text(value, {fontSize: "16pt"}),
}, {
'circle->nodeValue': [C.alignCenterX, C.alignCenterY],
'circle->containingCircle': [...containsPadding({ top: 5, bottom: 5, left: 5, right: 5, })]
})
Insert cell
render({circle: "lightblue", nodeValue: "a"}, nodeShapeWithCircleT)
Insert cell
Insert cell
Insert cell
barShapeT = createGroup({
$data$rect : (data) => M.rect({width: data.value, height: 30, fill: data.color}),
$nodeLabel$: (value) => text(value, {fontSize: "16pt"}),
}, {
'rect->nodeLabel': [C.alignRightSpace(5), C.alignCenterY],
})
Insert cell
render({data: {color: "lightblue", value: value}, nodeLabel: "a"}, barShapeT)
Insert cell
Insert cell
Insert cell
barListShapeT = createListGroup(barShapeT, {'curr->next': [C.alignLeft, C.vSpace(3)]})
Insert cell
Insert cell
viewof value = Inputs.range([60,100])
Insert cell
Insert cell
render(addLists([
{data: {color: "lightblue", value: value}, nodeLabel: "a"},
{data: {color: "coral", value: value*1.75}, nodeLabel: "b"},
{data: {color: "lightblue", value: value+2.5}, nodeLabel: "c"},
{data: {color: "lightblue", value: value-40}, nodeLabel: "d"},
{data: {color: "lightblue", value: value-4}, nodeLabel: "e"},
// {data: {color: "lightblue", value: value}, nodeLabel: "f"},
]), barListShapeT)
Insert cell
Insert cell
Insert cell
Insert cell
addLists({
nodes: [],
trees: [],
parentchild: [],
})
Insert cell
Insert cell
addLists({
nodes: [
{circle: "lightblue", nodeValue: "a"},
{circle: "coral", nodeValue: "b"},
{circle: "lightblue", nodeValue: "c"},
{circle: "lightblue", nodeValue: "d"},
{circle: "lightblue", nodeValue: "e"},
],
trees: [{ node: ??, subtrees: [] }],
parentchild: [{ parent: ??, child: ?? }],
})
Insert cell
Insert cell
exampleTree = addLists({
// set of nodes
nodes: [
{circle: "lightblue", nodeValue: "a"},
{circle: "coral", nodeValue: "b"},
{circle: "lightblue", nodeValue: "c"},
{circle: "lightblue", nodeValue: "d"},
{circle: "lightblue", nodeValue: "e"},
],
// tree relation. useful for putting space between a node and its subtrees
trees: [
{ node: ref('nodes/elements[2]'), subtrees: [] },
{ node: ref('nodes/elements[3]'), subtrees: [] },
{ node: ref('nodes/elements[4]'), subtrees: [] },
{ node: ref('nodes/elements[1]'), subtrees: [ref("trees/elements[0]"), ref("trees/elements[1]")] },
{ node: ref('nodes/elements[0]'), subtrees: [ref("trees/elements[3]"), ref("trees/elements[2]")] },
],
// parent-child relation. useful for drawing links
parentChild: [
{ parent: ref('nodes/elements[0]'), child: ref('nodes/elements[1]') },
{ parent: ref('nodes/elements[1]'), child: ref('nodes/elements[2]') },
{ parent: ref('nodes/elements[1]'), child: ref('nodes/elements[3]') },
{ parent: ref('nodes/elements[0]'), child: ref('nodes/elements[4]') },
],
})
Insert cell
Insert cell
Insert cell
treeShapeTNoArrows = createGroup({
$nodes$: createListGroup(nodeShapeT),
$trees$: createListGroup(createGroup({
$node$: 'ref',
// relation between neighboring subtrees
$subtrees$: createListGroup('ref', { 'curr->next': [C.alignTop, C.hSpace(20)]}),
}, {
// relation between node and subtree group
'node->subtrees': [C.vSpace(10), C.alignCenter],
})),
})
Insert cell
Insert cell
render(scopeResolve(exampleTree), treeShapeTNoArrows)
Insert cell
Insert cell
treeShapeTWrongOrder = createGroup({
$nodes$: createListGroup(nodeShapeT),
$trees$: createListGroup(createGroup({
$node$: 'ref',
// relation between neighboring subtrees
$subtrees$: createListGroup('ref', { 'curr->next': [C.alignTop, C.hSpace(20)]}),
}, {
// relation between node and subtree group
'node->subtrees': [C.vSpace(10), C.alignCenter],
})),
$parentChild$: createListGroup(createGroup({
$parent$: 'ref',
$child$: 'ref',
line: M.line({stroke:'gray',strokeWidth: 10})
},
{
'parent->line': [C.makeEqual('centerX', 'left'), C.makeEqual('centerY', 'top')],
'line->child': [C.makeEqual('right', 'centerX'), C.makeEqual('bottom', 'centerY')]
})),
})
Insert cell
render(scopeResolve(exampleTree), treeShapeTWrongOrder)
Insert cell
Insert cell
treeShapeT = createGroup({
$parentChild$: createListGroup(createGroup({
$parent$: 'ref',
$child$: 'ref',
line: M.line({stroke:'gray',strokeWidth: 10})
},
{
'parent->line': [C.makeEqual('centerX', 'left'), C.makeEqual('centerY', 'top')],
'line->child': [C.makeEqual('right', 'centerX'), C.makeEqual('bottom', 'centerY')]
})),
$nodes$: createListGroup(nodeShapeT),
$trees$: createListGroup(createGroup({
$node$: 'ref',
// relation between neighboring subtrees
$subtrees$: createListGroup('ref', { 'curr->next': [C.alignTop, C.hSpace(20)]}),
}, {
// relation between node and subtree group
'node->subtrees': [C.vSpace(10), C.alignCenter],
})),
})
Insert cell
render(scopeResolve(exampleTree), treeShapeT)
Insert cell
Insert cell
Insert cell
Insert cell
annotationT = createGroup({
$location$: 'ref',
highlight: M.rect({ rx: 5, fill: 'none', stroke: 'red', strokeWidth: 3, }),
$label$: (contents) => text(contents)
},{
'highlight->label': [C.alignTop, C.hSpace(5)],
'highlight->location': [...containsPadding({ top: 5, bottom: 5, left: 5, right: 5 })]
})
Insert cell
Insert cell
Insert cell
annotatedTree = ({
tree: exampleTree,
annotation: {
location: ref('tree/nodes/elements[4]'),
label: 'This is node e!',
},
})
Insert cell
Insert cell
// Want the highlight to go behind the tree? Try swapping the order of the templates! The order is the Z-ordering
// (Note: does not apply to references.)
render(scopeResolve(annotatedTree), createGroup({
$tree$: treeShapeT,
$annotation$: annotationT,
}))
Insert cell
Insert cell
Insert cell
createListGroup = (elementShape, relations, options={}) => createGroup({
$elements$: elementShape,
$neighbors$: createGroup({
$curr$: 'ref',
$next$: 'ref',
}, relations)
}, {}, options)
Insert cell
// Function to make list
mkList = (xs) => ({
elements: xs,
neighbors: xs.length < 2 ? [] :
_.zipWith(_.range(xs.length - 1), _.range(1, xs.length), (curr, next) => (
{
curr: ref(`../../elements/${curr}`),
next: ref(`../../elements/${next}`),
}
))
})
Insert cell
function addLists(data) {
if (Array.isArray(data)) {
return mkList(data.map((d) => addLists(d)));
} else if (typeof data === 'object' && '$ref' in data) {
return data;
} else if (typeof data === 'object' && data !== null) {
return Object.fromEntries(Object.entries(data).map(([k, v]) => [k, addLists(v)]))
} else {
return data
}
}
Insert cell
addLists({
nodes: ([{circle: "lightblue", nodeValue: "A"}, {circle: "lightblue", nodeValue: "B"}, {circle: "lightblue", nodeValue: "C"}, {circle: "lightblue", nodeValue: "D"}, {circle: "lightblue", nodeValue: "E"}]),
align1: ([ref('../../nodes/elements/0'), ref('../../nodes/elements/2'), ref('../../nodes/elements/3'), ref('../../nodes/elements/4')]),
align2: ([ref('../../nodes/elements/1')]),
})
Insert cell
ref = (...args) => {
const path = args
.flatMap(s => s.split(new RegExp('/|\\[|\\]')))
.filter((x) => x)
.map(s => s === ".." ? -1 : s);

return {
$ref: true,
path,
}
}
Insert cell
$data$ = {}
Insert cell
nodeShapeT_desired = createGroup({
$circle$ : M.circle({r: 30, fill: $data$}),
$nodeValue$: text($data$, {fontSize: "16pt"}),
}, {
'circle->nodeValue': [C.alignCenterX, C.alignCenterY],
})
Insert cell
$data = {}
Insert cell
nodeShapeT_desired2 = createGroup({
$circle : M.circle({r: 30, fill: $data}),
$nodeValue: text($data, {fontSize: "16pt"}),
}, {
'circle->nodeValue': [C.alignCenterX, C.alignCenterY],
})
Insert cell
render(addLists([{circle: "lightblue", nodeValue: "A"}, {circle: "lightblue", nodeValue: "B"}, {circle: "lightblue", nodeValue: "C"}, {circle: "lightblue", nodeValue: "D"}, {circle: "lightblue", nodeValue: "E"}]), nodeListShapeT)
Insert cell
render(dataStructure, createGroup({
$nodes$: createListGroup(nodeShapeT, {'curr->next': [C.hSpace(30)]}),
$align1$: createListGroup('ref', {'curr->next': [C.alignCenterY]}),
$align2$: createListGroup('ref', {'curr->next': [C.alignCenterY]}),
},{
'align2->align1': [C.vSpace(-40)],
}))
Insert cell
dataStructure = addLists({
nodes: [{circle: "lightblue", nodeValue: "A"}, {circle: "lightblue", nodeValue: "B"}, {circle: "lightblue", nodeValue: "C"}, {circle: "lightblue", nodeValue: "D"}, {circle: "lightblue", nodeValue: "E"}],
align1: [ref('../../nodes/elements/0'), ref('../../nodes/elements/2'), ref('../../nodes/elements/3'), ref('../../nodes/elements/4')],
align2: [ref('../../nodes/elements/1')],
})
Insert cell
// PL scopes
function scopeResolve(data, candidates=[]) {
if (Array.isArray(data)) {
candidates = [new Set([...Array(data.length).keys()]), ...candidates];
return data.map((d) => scopeResolve(d, candidates));
} else if (typeof data === 'object' && '$ref' in data) {
let path = data.path;
if (path[0] === -1) return data;
const numScopes = lookup(path[0], candidates);
path.unshift(...new Array(numScopes).fill(-1));
console.log(path)
path = path.map((s) => s === -1 ? '..' : s);
return ref(...path);
} else if (typeof data === 'object' && data !== null) {
candidates = [new Set(Object.keys(data)), ...candidates];
return Object.fromEntries(Object.entries(data).map(([k, v]) => [k, scopeResolve(v, candidates)]))
} else {
return data
}
}
Insert cell
function lookup(name, candidates, numScopes=0) {
const [hd, ...tl] = candidates;
if (hd.has(name)) {
return numScopes;
} else {
if (tl.length === 0) throw `${name} not found`;
return lookup(name, tl, numScopes+1);
}
}
Insert cell
nodeListShapeT = createListGroup(nodeShapeT, {'curr->next': [C.alignCenterY, C.hSpace(30)]})
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