Published
Edited
Mar 31, 2022
Insert cell
# Omelet
Insert cell
Insert cell
recipeFullText = textBlockString2({contents: `Ingredients
- 3 large eggs
- some salt & pepper
- 1/4 cup chopped fresh herbs
- 1 tbsp unsalted butter

Cookware
- mixing bowl
- skillet

Steps
1. Whisk together eggs and salt & pepper in a mixing bowl.
2. Stir in herbs.
3. Melt butter in a skillet.
4. Add the mixture to the skillet. Stir continuously for 2 minutes, then cook without stirring for 10 seconds.`,
fontSize: '18px'})
Insert cell
render(recipeFullText)
Insert cell
recipe3ShapesAbs = createShape2({
ingredients: textBlockString2({x: 0, y: 0, contents: `Ingredients
- 3 large eggs
- some salt & pepper
- 1/4 cup chopped fresh herbs
- 1 tbsp unsalted butter`,
fontSize: '18px'}),
cookware: textBlockString2({x: 0, y: 120, contents: `Cookware
- mixing bowl
- skillet`, fontSize: '18px'}),
steps: textBlockString2({x: 0, y: 200, contents: `Steps
1. Whisk together eggs and salt & pepper in a mixing bowl.
2. Stir in herbs.
3. Melt butter in a skillet.
4. Add the mixture to the skillet. Stir continuously for 2 minutes, then cook without stirring for 10 seconds.`, fontSize: '18px'}),
})
Insert cell
render(recipe3ShapesAbs)
Insert cell
omelet image: https://foto.wuestenigel.com/freshly-cooked-homemade-omelet-with-herbs/

Marco Verch

[CC BY 2.0](https://creativecommons.org/licenses/by/2.0/)
Insert cell
recipe3ShapesRel = createShape2({
name: M.text({ contents: 'Omelet', fontSize: '32px', fontWeight: 'bold', }),
image: M.image({ width: 1024 / 3, height: 683 / 3, href: 'https://foto.wuestenigel.com/wp-content/uploads/api2/freshly-cooked-homemade-omelet-with-herbs.jpeg' }),
ingredients: textBlockString2({contents: `Ingredients
- 3 large eggs
- some salt
- 1/4 cup chopped fresh herbs
- 1 tbsp unsalted butter`,
fontSize: '18px'}),
cookware: textBlockString2({contents: `Cookware
- mixing bowl
- skillet`, fontSize: '18px'}),
steps: textBlockString2({contents: `Steps
1. Whisk together eggs and salt
in a mixing bowl.
2. Stir in herbs.
3. Melt butter in a skillet.
4. Add the mixture to the skillet.
Stir continuously for 2min,
then cook without stirring for 10s.`, fontSize: '18px'}),
},{
'name->image': [C.vSpace(15), C.alignLeft],
'image->ingredients': [C.vSpace(15), C.alignLeft],
'ingredients->cookware': [C.vSpace(15), C.alignLeft],
'cookware->steps': [C.vSpace(15), C.alignLeft],
})
Insert cell
render(recipe3ShapesRel)
Insert cell
hSpaceGuide = createShape({
shapes: {
"leftEdge": M.rect({ width: 2, height: 10, fill: "firebrick" }),
"rightEdge": M.rect({ width: 2, height: 10, fill: "firebrick" }),
"middle": M.rect({ height: 2, fill: "firebrick" }),
},
rels: {
"leftEdge->middle": [C.alignLeft, C.hAlignCenter],
"rightEdge->middle": [C.alignRight, C.hAlignCenter],
"middle->$canvas": [C.sameWidth],
}
})
Insert cell
vSpaceGuide = createShape({
shapes: {
"topEdge": M.rect({ height: 2, width: 10, fill: "firebrick" }),
"botEdge": M.rect({ height: 2, width: 10, fill: "firebrick" }),
"middle": M.rect({ width: 2, fill: "firebrick" }),
},
rels: {
"topEdge->middle": [C.alignTop, C.vAlignCenter],
"botEdge->middle": [C.alignBottom, C.vAlignCenter],
"middle->$canvas": [C.sameHeight],
}
})
Insert cell
recipe3ShapesAbsAnnotated = createShape2({
boxI: M.rect({ fill: 'none', stroke: 'magenta', }),
boxC: M.rect({ fill: 'none', stroke: 'magenta', }),
boxS: M.rect({ fill: 'none', stroke: 'magenta', }),
// alignLeft: M.line({ stroke: 'cornflowerblue', strokeWidth: 5, strokeDasharray: "4"}),
// vSpace1: vSpaceGuide,
// vSpace2: vSpaceGuide,
ingredients: textBlockString2({ x: 0, y: 0, contents: `Ingredients
- 3 large eggs
- some salt & pepper
- 1/4 cup chopped fresh herbs
- 1 tbsp unsalted butter`,
fontSize: '18px'}),
cookware: textBlockString2({x: 0, y: 120, contents: `Cookware
- mixing bowl
- skillet`, fontSize: '18px'}),
steps: textBlockString2({x: 0, y: 200, contents: `Steps
1. Whisk together eggs and salt & pepper
`
+ String.fromCharCode(8192, 8192, 8192, 8192) + `in a mixing bowl.
2. Stir in herbs.
3. Melt butter in a skillet.
4. Add the mixture to the skillet.
`
+ String.fromCharCode(8192, 8192, 8192, 8192) + `Stir continuously for 2 minutes,
`
+ String.fromCharCode(8192, 8192, 8192, 8192) + `then cook without stirring for 10 seconds.`, fontSize: '18px'}),
},{
'boxI->ingredients': [...C.containsShrinkWrap],
'boxC->cookware': [...C.containsShrinkWrap],
'boxS->steps': [...C.containsShrinkWrap],
// 'ingredients->vSpace1': [C.vSpace(1), C.alignLeftSpace(30)],
// 'vSpace1->cookware': [C.vSpace(1)],
// 'cookware->vSpace2': [C.vSpace(1), C.alignLeftSpace(30)],
// 'vSpace2->steps': [C.vSpace(1)],
// 'alignLeft->ingredients': [C.alignLeft, C.alignTop],
// 'alignLeft->steps': [C.alignLeft, C.alignBottom],
'ingredients->cookware': [C.vSpace(15), C.alignLeft],
'cookware->steps': [C.vSpace(15), C.alignLeft],
})
Insert cell
render(recipe3ShapesAbsAnnotated)
Insert cell
recipe3ShapesRelAnnotated = createShape2({
boxI: M.rect({ fill: 'none', stroke: 'magenta', }),
boxC: M.rect({ fill: 'none', stroke: 'magenta', }),
boxS: M.rect({ fill: 'none', stroke: 'magenta', }),
alignLeft: M.line({ stroke: 'cornflowerblue', strokeWidth: 5, strokeDasharray: "4"}),
vSpace1: vSpaceGuide,
vSpace2: vSpaceGuide,
ingredients: textBlockString2({contents: `Ingredients
- 3 large eggs
- some salt & pepper
- 1/4 cup chopped fresh herbs
- 1 tbsp unsalted butter`,
fontSize: '18px'}),
cookware: textBlockString2({contents: `Cookware
- mixing bowl
- skillet`, fontSize: '18px'}),
steps: textBlockString2({contents: `Steps
1. Whisk together eggs and salt & pepper
`
+ String.fromCharCode(8192, 8192, 8192, 8192) + `in a mixing bowl.
2. Stir in herbs.
3. Melt butter in a skillet.
4. Add the mixture to the skillet.
`
+ String.fromCharCode(8192, 8192, 8192, 8192) + `Stir continuously for 2 minutes,
`
+ String.fromCharCode(8192, 8192, 8192, 8192) + `then cook without stirring for 10 seconds.`, fontSize: '18px'}),
},{
'boxI->ingredients': [...C.containsShrinkWrap],
'boxC->cookware': [...C.containsShrinkWrap],
'boxS->steps': [...C.containsShrinkWrap],
'ingredients->vSpace1': [C.vSpace(1), C.alignLeftSpace(30)],
'vSpace1->cookware': [C.vSpace(1)],
'cookware->vSpace2': [C.vSpace(1), C.alignLeftSpace(30)],
'vSpace2->steps': [C.vSpace(1)],
'alignLeft->ingredients': [C.alignLeft, C.alignTop],
'alignLeft->steps': [C.alignLeft, C.alignBottom],
'ingredients->cookware': [C.vSpace(15), C.alignLeft],
'cookware->steps': [C.vSpace(15), C.alignLeft],
})
Insert cell
render(recipe3ShapesRelAnnotated)
Insert cell
recipe3ShapesRelMod = createShape2({
name: M.text({ contents: 'Omelet', fontSize: '32px', fontWeight: 'bold', }),
image: M.image({ width: 1024 / 3, height: 683 / 3, href: 'https://foto.wuestenigel.com/wp-content/uploads/api2/freshly-cooked-homemade-omelet-with-herbs.jpeg' }),
ingredients: textBlockString2({contents: `Ingredients
- 3 large eggs
- some salt
- 1/4 cup chopped fresh herbs
- 1 tbsp unsalted butter`,
fontSize: '18px'}),
cookware: textBlockString2({contents: `Cookware
- mixing bowl
- skillet`, fontSize: '18px'}),
steps: textBlockString2({contents: `Steps
1. Whisk together eggs and salt
`
+ String.fromCharCode(8192, 8192, 8192, 8192) + `in a mixing bowl.
2. Stir in herbs.
3. Melt butter in a skillet.
4. Add the mixture to the skillet.
`
+ String.fromCharCode(8192, 8192, 8192, 8192) + `Stir continuously for 2min,
`
+ String.fromCharCode(8192, 8192, 8192, 8192) + `then cook without stirring for 10s.`, fontSize: '18px'}),
},{
'name->ingredients': [C.vSpace(15), C.alignLeft],
'ingredients->cookware': [C.hSpace(15), C.alignTop],
'ingredients->image': [C.vSpace(15), C.alignLeft],
'image->steps': [C.hSpace(15), C.alignTop],
})
Insert cell
render(recipe3ShapesRelMod)
Insert cell
recipe3ShapesRelModT = createShape2({
$name$: (contents) => M.text({ contents, fontSize: '32px', fontWeight: 'bold', }),
$image$: (href) => M.image({ width: 1024 / 3, height: 683 / 3, href }),
$ingredients$: (contents) => textBlockString2({contents,
fontSize: '18px', lineSpacing: 5,}),
$cookware$: (contents) => textBlockString2({contents, fontSize: '18px'}),
$steps$: (contents) => textBlockString2({contents, fontSize: '18px'}),
},{
'name->ingredients': [C.vSpace(15), C.alignLeft],
'ingredients->cookware': [C.hSpace(15), C.alignTop],
'ingredients->image': [C.vSpace(15), C.alignLeft],
'image->steps': [C.hSpace(15), C.alignTop],
})
Insert cell
myRecipeData = ({
name: 'Omelet',
image: 'https://foto.wuestenigel.com/wp-content/uploads/api2/freshly-cooked-homemade-omelet-with-herbs.jpeg',
ingredients: `Ingredients
- 3 large eggs
- some salt
- 1/4 cup chopped fresh herbs
- 1 tbsp unsalted butter`,
cookware: `Cookware
- mixing bowl
- skillet`,
steps: `Steps
1. Whisk together eggs and salt
`
+ String.fromCharCode(8192, 8192, 8192, 8192) + `in a mixing bowl.
2. Stir in herbs.
3. Melt butter in a skillet.
4. Add the mixture to the skillet.
`
+ String.fromCharCode(8192, 8192, 8192, 8192) + `Stir continuously for 2min,
`
+ String.fromCharCode(8192, 8192, 8192, 8192) + `then cook without stirring for 10s.`,
})
Insert cell
render(myRecipeData, recipe3ShapesRelModT)
Insert cell
containsPadding = ({ top, bottom, left, right }) => [C.alignLeftSpace(left), C.alignRightSpace(right), C.alignTopSpace(-top), C.alignBottomSpace(-bottom)]
Insert cell
annotationShape = createShape2({
$location$: 'ref',
highlight: M.rect({ fill: 'none', stroke: 'cornflowerblue', strokeWidth: '5px', /* strokeDasharray: '4', */}),
// arrow: M.arrow2({ fill: 'cornflowerblue', }),
$label$: (contents) => M.text({ contents, fontSize: '18px', fill: 'cornflowerblue', fontWeight: 'bold' }),
},{
'highlight->location': [...containsPadding({ top: 10, bottom: 10, left: 10, right: 10 })],
// "highlight->arrow": [C.makeEqual('left', 'right'), C.makeEqual('top', 'bottom')],
// "arrow->label": [C.makeEqual('left', 'centerX'), C.makeEqual('top', 'bottom')],
'highlight->label': [C.hSpace(-30),],
'label->highlight': [C.vSpace(10)],
})
Insert cell
myAnnotatedRecipeData = ({
recipeData: myRecipeData,
annotation: {
location: ref('../recipeData/ingredients'),
label: 'Try parsley, basil, or dill!',
}
})
Insert cell
C.alignLeftOptions
Insert cell
C.alignLeftSpace
Insert cell
annotatedRecipeT = createShape2({
$recipeData$: recipe3ShapesRelModT,
$annotation$: annotationShape,
},{
'$canvas->annotation': [C.alignLeftSpace(30)],
})
Insert cell
render(myAnnotatedRecipeData, annotatedRecipeT)
Insert cell
```js
recipe3ShapesRelMod = createShape2({
ingredients: textBlockString2({contents: `Ingredients
- 3 large eggs
- some salt & pepper
- 1/4 cup chopped fresh herbs
- 1 tbsp unsalted butter`,
fontSize: '18px'}),
cookware: textBlockString2({contents: `Cookware
- mixing bowl
- skillet`, fontSize: '18px'}),
steps: textBlockString2({contents: `Steps
1. Whisk together eggs and salt & pepper in a mixing bowl.
2. Stir in herbs.
3. Melt butter in a skillet.
4. Add the mixture to the skillet. Stir continuously for 2 minutes, then cook without stirring for 10 seconds.`, fontSize: '18px'}),
},{
'ingredients->cookware': [C.hSpace(15), C.alignTop],
'ingredients->steps': [C.vSpace(15), C.alignLeft],
})
```
Insert cell
recipe3ShapesRelModAnnotated = createShape2({
boxI: M.rect({ fill: 'none', stroke: 'magenta', }),
boxC: M.rect({ fill: 'none', stroke: 'magenta', }),
boxS: M.rect({ fill: 'none', stroke: 'magenta', }),
alignLeft: M.line({ stroke: 'cornflowerblue', strokeWidth: 5, strokeDasharray: "4"}),
alignTop: M.line({ stroke: 'cornflowerblue', strokeWidth: 5, strokeDasharray: "4"}),
vSpace1: vSpaceGuide,
hSpace: hSpaceGuide,
ingredients: textBlockString2({contents: `Ingredients
- 3 large eggs
- some salt & pepper
- 1/4 cup chopped fresh herbs
- 1 tbsp unsalted butter`,
fontSize: '18px'}),
cookware: textBlockString2({contents: `Cookware
- mixing bowl
- skillet`, fontSize: '18px'}),
steps: textBlockString2({contents: `Steps
1. Whisk together eggs and salt & pepper
`
+ String.fromCharCode(8192, 8192, 8192, 8192) + `in a mixing bowl.
2. Stir in herbs.
3. Melt butter in a skillet.
4. Add the mixture to the skillet.
`
+ String.fromCharCode(8192, 8192, 8192, 8192) + `Stir continuously for 2 minutes,
`
+ String.fromCharCode(8192, 8192, 8192, 8192) + `then cook without stirring for 10 seconds.`, fontSize: '18px'}),
},{
'boxI->ingredients': [...C.containsShrinkWrap],
'boxC->cookware': [...C.containsShrinkWrap],
'boxS->steps': [...C.containsShrinkWrap],
'ingredients->vSpace1': [C.vSpace(1), C.alignLeftSpace(30)],
'vSpace1->steps': [C.vSpace(1)],
'ingredients->hSpace': [C.hSpace(1), C.alignTopSpace(-30)],
'hSpace->cookware': [C.hSpace(1)],
'alignLeft->ingredients': [C.alignLeft, C.alignTop],
'alignLeft->steps': [C.alignLeft, C.alignBottom],
'alignTop->ingredients': [C.alignLeft, C.alignTop],
'alignTop->cookware': [C.alignRight, C.alignTop],
'ingredients->cookware': [C.hSpace(15), C.alignTop],
'ingredients->steps': [C.vSpace(15), C.alignLeft],
})
Insert cell
render(recipe3ShapesRelModAnnotated)
Insert cell
html`<style>
@import url('https://fonts.googleapis.com/css2?family=Fira+Code:wght@400;500&display=swap');`
Insert cell
recipe3ShapesRelAnnotatedWithCode = createShape2({
recipe: recipe3ShapesRelAnnotated,
code: textBlockString2({contents:
`createShape({
ingredients: M.text({
contents: INGREDIENTS,
fontSize: '18px',
}),
cookware: M.text({
contents: COOKWARE,
fontSize: '18px',
}),
steps: M.text({
contents: STEPS,
fontSize: '18px',
}),
},{
'ingredients->cookware': [
C.vSpace(5),
C.alignLeft,
],
'cookware->steps': [
C.vSpace(5),
C.alignLeft,
],
})`,
fontSize: '18px', fontFamily: 'Fira Code'}),
},{
'code->recipe': [C.hSpace(5), C.alignTop],
})
Insert cell
render(recipe3ShapesRelAnnotatedWithCode)
Insert cell
recipe3ShapesRelModAnnotatedWithCode = createShape2({
recipe: recipe3ShapesRelModAnnotated,
code: textBlockString2({contents:
`{
'ingredients->cookware': [
C.hSpace(10),
C.alignMiddle,
],
'ingredients->steps': [
C.vSpace(5),
C.alignLeft,
],
}`,
fontSize: '18px', fontFamily: 'Fira Code'}),
},{
'code->recipe': [C.hSpace(5), C.alignTop],
})
Insert cell
render(recipe3ShapesRelModAnnotatedWithCode)
Insert cell
recipe3ShapesAbsAnnotatedWithCode = createShape2({
recipe: recipe3ShapesAbsAnnotated,
code: textBlockString2({contents:
`createShape({
ingredients: M.text({
x: 0, y: 0,
contents: INGREDIENTS,
fontSize: '18px',
}),
cookware: M.text({
x: 0, y: 120,
contents: COOKWARE,
fontSize: '18px',
}),
steps: M.text({
x: 0, y: 200,
contents: STEPS,
fontSize: '18px',
}),
})`,
fontSize: '18px', fontFamily: 'Fira Code'}),
},{
'code->recipe': [C.hSpace(5), C.alignTop],
})
Insert cell
render(recipe3ShapesAbsAnnotatedWithCode)
Insert cell
dataOneList = ({
ingredients: `Ingredients
- 3 large eggs
- some salt & pepper
- 1/4 cup chopped fresh herbs
- 1 tbsp unsalted butter`,
cookware: `Cookware
- mixing bowl
- skillet`,
steps: mkList([
`1. Whisk together eggs and salt & pepper
in a mixing bowl.`,
'2. Stir in herbs.',
'3. Melt butter in a skillet.',
`4. Add the mixture to the skillet.
Stir continuously for 2 minutes,
then cook without stirring for 10 seconds.`,
]),
})
Insert cell
recipeOneList = createShape2({
$ingredients$: (contents) => textBlockString2({contents,
fontSize: '18px'}),
$cookware$: (contents) => textBlockString2({contents, fontSize: '18px'}),
stepsTitle: M.text({ contents: 'Steps', fontSize: '18px', fontWeight: 'bold', }),
$steps$: createShape2({
$elements$: (contents) => textBlockString2({contents, fontSize: '18px'}),
$neighbors$: createShape2({
$curr$: 'ref',
$next$: 'ref',
},{
'curr->next': [C.alignLeft, C.vSpace(5)],
}),
})
},{
'ingredients->cookware': [C.hSpace(15), C.alignTop],
'ingredients->stepsTitle': [C.vSpace(15), C.alignLeft],
'stepsTitle->steps': [C.alignLeft, C.vSpace(0)],
})
Insert cell
render(dataOneList, recipeOneList)
Insert cell
recipeOneListAnnotated = createShape2({
boxI: M.rect({ fill: 'none', stroke: 'magenta', }),
boxC: M.rect({ fill: 'none', stroke: 'magenta', }),
boxS: M.rect({ fill: 'none', stroke: 'magenta', }),
boxST: M.rect({ fill: 'none', stroke: 'magenta', }),
alignLeft: M.line({ stroke: 'cornflowerblue', strokeWidth: 5, strokeDasharray: "4"}),
alignTop: M.line({ stroke: 'cornflowerblue', strokeWidth: 5, strokeDasharray: "4"}),
vSpace1: vSpaceGuide,
vSpace2: vSpaceGuide,
hSpace: hSpaceGuide,
$ingredients$: (contents) => textBlockString2({contents,
fontSize: '18px'}),
$cookware$: (contents) => textBlockString2({contents, fontSize: '18px'}),
stepsTitle: M.text({ contents: 'Steps', fontSize: '18px', fontWeight: 'bold', }),
$steps$: createShape2({
$elements$: (contents) => textBlockString2({contents, fontSize: '18px'}),
$neighbors$: createShape2({
$curr$: 'ref',
$next$: 'ref',
},{
'curr->next': [C.alignLeft, C.vSpace(5)],
}),
})
},{
'boxI->ingredients': [...C.containsShrinkWrap],
'boxC->cookware': [...C.containsShrinkWrap],
'boxS->steps': [...C.containsShrinkWrap],
'boxST->stepsTitle': [...C.containsShrinkWrap],
'ingredients->vSpace1': [C.vSpace(1), C.alignLeftSpace(30)],
'vSpace1->stepsTitle': [C.vSpace(1)],
'stepsTitle->vSpace2': [C.vSpace(1), C.alignLeftSpace(30)],
'vSpace2->steps': [C.vSpace(1)],
'ingredients->hSpace': [C.hSpace(1), C.alignTopSpace(-30)],
'hSpace->cookware': [C.hSpace(1)],
'alignLeft->ingredients': [C.alignLeft, C.alignTop],
'alignLeft->steps': [C.alignLeft, C.alignBottom],
'alignTop->ingredients': [C.alignLeft, C.alignTop],
'alignTop->cookware': [C.alignRight, C.alignTop],
'ingredients->cookware': [C.hSpace(15), C.alignTop],
'ingredients->stepsTitle': [C.vSpace(15), C.alignLeft],
'stepsTitle->steps': [C.alignLeft, C.vSpace(7.5)],
})
Insert cell
render(dataOneList, recipeOneListAnnotated)
Insert cell
recipeOneListAnnotatedWithCode = createShape2({
code: textBlockString2({contents:
`createShape({
$ingredients$: (contents) => M.text({
contents,
fontSize: '18px',
}),
$cookware$: (contents) => M.text({
contents,
fontSize: '18px',
}),
stepsTitle: M.text({
contents: 'Steps',
fontSize: '18px',
fontWeight: 'bold',
}),
$steps$: (contents) => M.text({
contents,
fontSize: '18px',
}),
},{
'ingredients->cookware': [
C.hSpace(15), C.alignTop
],
'ingredients->stepsTitle': [
C.vSpace(15), C.alignLeft
],
'stepsTitle->steps': [
C.alignLeft, C.vSpace(0)
],
})`,
fontSize: '18px', fontFamily: 'Fira Code'}),
boxI: M.rect({ fill: 'none', stroke: 'magenta', }),
boxC: M.rect({ fill: 'none', stroke: 'magenta', }),
boxS: M.rect({ fill: 'none', stroke: 'magenta', }),
boxST: M.rect({ fill: 'none', stroke: 'magenta', }),
alignLeft: M.line({ stroke: 'cornflowerblue', strokeWidth: 5, strokeDasharray: "4"}),
alignTop: M.line({ stroke: 'cornflowerblue', strokeWidth: 5, strokeDasharray: "4"}),
vSpace1: vSpaceGuide,
vSpace2: vSpaceGuide,
hSpace: hSpaceGuide,
$ingredients$: (contents) => textBlockString2({contents,
fontSize: '18px'}),
$cookware$: (contents) => textBlockString2({contents, fontSize: '18px'}),
stepsTitle: M.text({ contents: 'Steps', fontSize: '18px', fontWeight: 'bold', }),
$steps$: createShape2({
$elements$: (contents) => textBlockString2({contents, fontSize: '18px'}),
$neighbors$: createShape2({
$curr$: 'ref',
$next$: 'ref',
},{
'curr->next': [C.alignLeft, C.vSpace(5)],
}),
})
},{
'code->ingredients': [C.hSpace(5), C.alignTop],
'boxI->ingredients': [...C.containsShrinkWrap],
'boxC->cookware': [...C.containsShrinkWrap],
'boxS->steps': [...C.containsShrinkWrap],
'boxST->stepsTitle': [...C.containsShrinkWrap],
'ingredients->vSpace1': [C.vSpace(1), C.alignLeftSpace(30)],
'vSpace1->stepsTitle': [C.vSpace(1)],
'stepsTitle->vSpace2': [C.vSpace(1), C.alignLeftSpace(30)],
'vSpace2->steps': [C.vSpace(1)],
'ingredients->hSpace': [C.hSpace(1), C.alignTopSpace(-30)],
'hSpace->cookware': [C.hSpace(1)],
'alignLeft->ingredients': [C.alignLeft, C.alignTop],
'alignLeft->steps': [C.alignLeft, C.alignBottom],
'alignTop->ingredients': [C.alignLeft, C.alignTop],
'alignTop->cookware': [C.alignRight, C.alignTop],
'ingredients->cookware': [C.hSpace(15), C.alignTop],
'ingredients->stepsTitle': [C.vSpace(15), C.alignLeft],
'stepsTitle->steps': [C.alignLeft, C.vSpace(7.5)],
})
Insert cell
render(dataOneList, recipeOneListAnnotatedWithCode)
Insert cell
codeShape = (contents) => textBlockString2({contents, fontSize: '18px', fontFamily: 'Fira Code'})
Insert cell
render(codeShape(
`{
ingredients: [
'3 large eggs',
'some salt & pepper',
'1/4 cup chopped fresh herbs',
'1 tbsp unsalted butter',
],
cookware: [
'mixing bowl',
'skillet',
],
stepIngredients: mkList([
mkList([ref('../../../../ingredients/0'), ref('../../../../ingredients/1')]),
mkList([ref('../../../../ingredients/2')]),
mkList([ref('../../../../ingredients/3')]),
]),
steps: mkList([
{
instruction: '1. Whisk',
ingredients: ref('../../../stepIngredients/elements/0'),
cookware: ref('../../../cookware/0'),
},
{
instruction: '2. Stir',
ingredients: ref('../../../stepIngredients/elements/1'),
cookware: ref('../../../cookware/0'),
},
{
instruction: '3. Melt',
ingredients: ref('../../../stepIngredients/elements/2'),
cookware: ref('../../../cookware/1'),
},
{
instruction: \`4. Add.
Stir continuously for 2 min,
then cook without stirring for 10 sec.\`,
ingredients: ref('../../../cookware/0'),
cookware: ref('../../../cookware/1'),
},
]),
}`
))
Insert cell
recipe1 = M.text({contents: `Ingredients
- 3 large eggs
- some salt & pepper
- 1/4 cup chopped fresh herbs
- 1 tbsp unsalted butter

Cookware
- mixing bowl
- skillet

Steps
1. Whisk together eggs and salt & pepper in a mixing bowl.
2. Stir in herbs.
3. Melt butter in a skillet.
4. Add the mixture to the skillet. Stir continuously for 2 minutes, then cook without stirring for 10 seconds.`})
Insert cell
render(recipe1)
Insert cell
recipe2 = createShape2({
ingredients: M.text({ contents: `Ingredients
- 3 large eggs
- some salt & pepper
- 1/4 cup chopped fresh herbs
- 1 tbsp unsalted butter`}),
cookware: M.text({ contents: `Cookware
- mixing bowl
- skillet`}),
steps: M.text({ contents: `Steps
1. Whisk together eggs and salt & pepper in a mixing bowl.
2. Stir in herbs.
3. Melt butter in a skillet.
4. Add the mixture to the skillet. Stir continuously for 2 minutes, then cook without stirring for 10 seconds.`}),
})
Insert cell
render(recipe2)
Insert cell
recipe3 = createShape2({
ingredients: M.text({ contents: `Ingredients
- 3 large eggs
- some salt & pepper
- 1/4 cup chopped fresh herbs
- 1 tbsp unsalted butter`}),
cookware: M.text({ contents: `Cookware
- mixing bowl
- skillet`}),
steps: M.text({ contents: `Steps
1. Whisk together eggs and salt & pepper in a mixing bowl.
2. Stir in herbs.
3. Melt butter in a skillet.
4. Add the mixture to the skillet. Stir continuously for 2 minutes, then cook without stirring for 10 seconds.`}),
},{
'ingredients->cookware': [C.vSpace(5), C.alignLeft],
'cookware->steps': [C.vSpace(5), C.alignLeft],
})
Insert cell
render(recipe3)
Insert cell
recipe4 = createShape2({
ingredients: M.text({ contents: `Ingredients
- 3 large eggs
- some salt & pepper
- 1/4 cup chopped fresh herbs
- 1 tbsp unsalted butter`}),
cookware: M.text({ contents: `Cookware
- mixing bowl
- skillet`}),
steps: M.text({ contents: `Steps
1. Whisk together eggs and salt & pepper in a mixing bowl.
2. Stir in herbs.
3. Melt butter in a skillet.
4. Add the mixture to the skillet. Stir continuously for 2 minutes, then cook without stirring for 10 seconds.`}),
},{
'ingredients->cookware': [C.hSpace(10), C.alignMiddle],
'ingredients->steps': [C.vSpace(5), C.alignLeft],
})
Insert cell
textBlock = (params) => {
return createShape2({
$elements$: (contents) => M.text({contents, ...params}),
$neighbors$: createShape2({
$curr$: 'ref',
$next$: 'ref',
},{
'curr->next': [C.alignLeft, C.vSpace(0)],
}),
})
}
Insert cell
render(mkList(['foo', 'bar', 'baz']), textBlock())
Insert cell
// doesn't work :(
textBlockString = (params) => {
const { contents, ...textParams } = params;
const textBlock = mkList(contents.split('\n'));
return createShape2({
$elements$: (contents) => M.text({contents, ...textParams}),
$neighbors$: createShape2({
$curr$: 'ref',
$next$: 'ref',
},{
'curr->next': [C.alignLeft, C.vSpace(0)],
}),
})(textBlock)
}
Insert cell
{
const textParams = {};
const contents = `foo
bar
baz`;
const textBlock = mkList(contents.split('\n'));
return createShape2({
$elements$: (contents) => M.text({contents, ...textParams}),
$neighbors$: createShape2({
$curr$: 'ref',
$next$: 'ref',
},{
'curr->next': [C.alignLeft, C.vSpace(0)],
}),
})(bfjs.makePathsAbsolute(textBlock))
}
Insert cell
textBlockString({ contents: `foo
bar
baz`,})
Insert cell
render(`foo
bar
baz`, (contents) => textBlockString({ contents }))
Insert cell
textBlockString2 = (params) => {
const { contents, x, y, lineSpacing, ...textParams } = params;
let textBlock = contents.split('\n');
// replace leading whitespace with invisible ascii characters so they actually get rendered
textBlock = textBlock.map(line => {
// https://stackoverflow.com/a/25823987
let whitespace = line.search(/\S|$/);
if (whitespace < 0) { whitespace = line.length };
return String.fromCharCode(8192).repeat(whitespace) + line.substring(whitespace);
})

return createShape2(
Object.fromEntries(textBlock.map((line, i) => [i, M.text({ contents: line, ...textParams})])),
Object.fromEntries(d3.pairs(_.range(textBlock.length)).map(([curr, next]) => [`${curr}->${next}`, [C.alignLeft, C.vSpace(lineSpacing ?? 0)]])),
{
bbox: {
left: x,
top: y,
}
}
)
}
Insert cell
render(`foo
bar
baz`, (contents) => textBlockString2({ contents }))
Insert cell
render(recipe4)
Insert cell
recipe5 = ({ingredients, cookware, steps}) => createShape2({
ingredients: textBlockString2({ contents: ingredients }),
cookware: textBlockString2({ contents: cookware }),
steps: textBlockString2({ contents: steps }),
},{
'ingredients->cookware': [C.hSpace(10), C.alignTop],
'ingredients->steps': [C.vSpace(5), C.alignLeft],
})
Insert cell
data1 = ({ ingredients: `Ingredients
- 3 large eggs
- some salt & pepper
- 1/4 cup chopped fresh herbs
- 1 tbsp unsalted butter`,
cookware: `Cookware
- mixing bowl
- skillet`,
steps: `Steps
1. Whisk together eggs and salt & pepper in a mixing bowl.
2. Stir in herbs.
3. Melt butter in a skillet.
4. Add the mixture to the skillet. Stir continuously for 2 minutes, then cook without stirring for 10 seconds.`})
// data1 = ({ ingredients: mkList([
// 'Ingredients',
// '- 3 large eggs',
// '- some salt & pepper',
// '- 1/4 cup chopped fresh herbs',
// '- 1 tbsp unsalted butter',
// ]),
// cookware: mkList(['Cookware', '- mixing bowl', '- skillet']),
// steps: mkList(['Steps',
// '1. Whisk together eggs and salt & pepper in a mixing bowl.',
// '2. Stir in herbs.',
// '3. Melt butter in a skillet.',
// '4. Add the mixture to the skillet. Stir continuously for 2 minutes, then cook without stirring for 10 seconds.'])})
Insert cell
render(data1, recipe5)
Insert cell
recipe6 = createShape2({
$$ingredients: (o) => textBlockString2({ contents: o.ingredients }),
$$cookware: (o) => textBlockString2({ contents: o.cookware }),
$$steps: (o) => textBlockString2({ contents: o.steps }),
},{
'ingredients->cookware': [C.hSpace(10), C.alignMiddle],
'ingredients->steps': [C.vSpace(5), C.alignLeft],
})
Insert cell
render(data1, recipe6)
Insert cell
recipe7 = createShape2({
$ingredients$: (ingredients) => textBlockString2({ contents: ingredients }),
$cookware$: (cookware) => textBlockString2({ contents: cookware }),
$steps$: (steps) => textBlockString2({ contents: steps }),
},{
'ingredients->cookware': [C.hSpace(10), C.alignMiddle],
'ingredients->steps': [C.vSpace(5), C.alignLeft],
})
Insert cell
render(data1, recipe7)
Insert cell
data2 = ({
ingredients: mkList([
'3 large eggs',
'some salt & pepper',
'1/4 cup chopped fresh herbs',
'1 tbsp unsalted butter',
]),
cookware: mkList([
'mixing bowl',
'skillet',
]),
steps: mkList([
'Whisk together eggs and salt & pepper \
in a mixing bowl.',
'Stir in herbs.',
'Melt butter in a skillet.',
'Add the mixture to the skillet. \
Stir continuously for 2 minutes, \
then cook without stirring for 10 seconds.',
]),
})
Insert cell
recipe9 = createShape2({
'$ingredients$': createShape2({
'$elements$': (contents) => M.text({ contents }),
}),
'$cookware$': createShape2({
'$elements$': (contents) => M.text({ contents }),
}),
'$steps$': createShape2({
'$elements$': (contents) => M.text({ contents }),
}),
},{
'ingredients->cookware': [C.hSpace(10), C.alignMiddle],
'ingredients->steps': [C.vSpace(5), C.alignLeft],
})
Insert cell
render(data2, recipe9)
Insert cell
recipe10 = createShape2({
'$ingredients$': createShape2({
'$elements$': (contents) => M.text({ contents }),
'$neighbors$': createShape2({
$curr$: 'ref',
$next$: 'ref',
},{
'curr->next': [C.vSpace(2), C.alignLeft],
})
}),
'$cookware$': createShape2({
'$elements$': (contents) => M.text({ contents }),
'$neighbors$': createShape2({
$curr$: 'ref',
$next$: 'ref',
},{
'curr->next': [C.vSpace(2), C.alignLeft],
})
}),
'$steps$': createShape2({
'$elements$': (contents) => M.text({ contents }),
'$neighbors$': createShape2({
$curr$: 'ref',
$next$: 'ref',
},{
'curr->next': [C.vSpace(2), C.alignLeft],
})
}),
},{
'ingredients->cookware': [C.hSpace(10), C.alignMiddle],
'ingredients->steps': [C.vSpace(10), C.alignLeft],
})
Insert cell
render(data2, recipe10)
Insert cell
data3 = ({
ingredients: [
'3 large eggs',
'some salt & pepper',
'1/4 cup chopped fresh herbs',
'1 tbsp unsalted butter',
],
cookware: [
'mixing bowl',
'skillet',
],
steps: mkList([
{
instructions: '1. Whisk',
ingredients: ref('../../../stepsIngredients/elements/0'),
cookware: ref('../../../cookware/0'),
},
{
instructions: '2. Stir',
ingredients: ref('../../../stepsIngredients/elements/1'),
cookware: ref('../../../cookware/0'),
},
{
instructions: '3. Melt',
ingredients: ref('../../../stepsIngredients/elements/2'),
cookware: ref('../../../cookware/1'),
},
{
instructions: `4. Add.
Stir continuously for 2 min,
then cook without stirring for 10 sec.`,
ingredients: ref('../../../cookware/0'),
cookware: ref('../../../cookware/1'),
},
]),
stepsIngredients: mkList([
mkList([ref('../../../../ingredients/0'), ref('../../../../ingredients/1')]),
mkList([ref('../../../../ingredients/2')]),
mkList([ref('../../../../ingredients/3')]),
]),
})
Insert cell
mkListShape = (elementsFn, constraints=[]) => createShape2({
$elements$: elementsFn,
$neighbors$: createShape2({
$curr$: 'ref',
$next$: 'ref',
},{
'curr->next': constraints
})
})
Insert cell
recipe12 = createShape2({
$ingredients$: (contents) => M.text({ contents }),
$cookware$: (contents) => M.text({ contents }),
$steps$: createShape2({
$elements$: createShape2({
$ingredients$: 'ref',
$instructions$: (contents) => textBlockString2({ contents }),
$cookware$: 'ref',
line: M.arrow2({ stroke: 'black', strokeWidth: 0}),
boxI: M.rect({fill: 'none', stroke: 'magenta', strokeWidth: '5',}),
boxC: M.rect({fill: 'none', stroke: 'limegreen', strokeWidth: '5',}),
},{
'ingredients->cookware': [C.hSpace(100, bfjs.Strength.required, bfjs.Operator.Le)],
'boxI->ingredients': [...C.containsShrinkWrap],
'boxC->cookware': [...C.containsShrinkWrap],
"ingredients->line": [C.makeEqual('right', 'left'), C.makeEqual('centerY', 'top')],
"line->cookware": [C.makeEqual('right', 'left'), C.makeEqual('bottom', 'centerY')],
'instructions->line': [C.vSpace(10), C.alignCenter],
}),
$neighbors$: createShape2({
$curr$: 'ref',
$next$: 'ref',
})
}
),
$stepsIngredients$: mkListShape(
createShape2({
box: M.rect({stroke: 'black', fill: 'none', }),
$elements$: 'ref',
$neighbors$: createShape2({
$curr$: 'ref',
$next$: 'ref',
},{
'curr->next': [C.vSpace(5), C.alignLeft]
})
},{
'box->elements': [...C.containsShrinkWrap],
}),
[C.vSpace(30), C.alignRight]
)
})
Insert cell
render(data3, recipe12)
Insert cell
recipe13 = createShape2({
$ingredients$: (contents) => M.text({ contents }),
$cookware$: (contents) => M.text({ contents }),
$steps$: createShape2({
$elements$: createShape2({
$ingredients$: 'ref',
$instructions$: (contents) => textBlockString2({ contents }),
$cookware$: 'ref',
line: M.arrow2({ stroke: 'black', strokeWidth: 0}),
boxI: M.rect({fill: 'none', stroke: 'magenta', strokeWidth: '5',}),
boxC: M.rect({fill: 'none', stroke: 'limegreen', strokeWidth: '5',}),
},{
'ingredients->cookware': [C.hSpace(100, bfjs.Strength.required, bfjs.Operator.Le)],
'boxI->ingredients': [...C.containsShrinkWrap],
'boxC->cookware': [...C.containsShrinkWrap],
"ingredients->line": [C.makeEqual('right', 'left'), C.makeEqual('centerY', 'top')],
"line->cookware": [C.makeEqual('right', 'left'), C.makeEqual('bottom', 'centerY')],
'instructions->line': [C.vSpace(10), C.alignCenter],
}),
$neighbors$: createShape2({
$curr$: 'ref',
$next$: 'ref',
})
}
),
$stepsIngredients$: mkListShape(
createShape2({
box: M.rect({stroke: 'black', fill: 'none', }),
$elements$: 'ref',
$neighbors$: createShape2({
$curr$: 'ref',
$next$: 'ref',
},{
'curr->next': [C.vSpace(5), C.alignLeft]
})
},{
'box->elements': [...C.containsShrinkWrap],
}),
[C.vSpace(30), C.alignRight]
)
})
Insert cell
render(data3, recipe13)
Insert cell
pipe = {
// https://medium.com/@venomnert/pipe-function-in-javascript-8a22097a538e
const _pipe = (a, b) => (arg) => b(a(arg));
return (...ops) => ops.reduce(_pipe)
}
Insert cell
// https://stackoverflow.com/a/1026087
function capitalizeFirstLetter(string) {
return string.charAt(0).toUpperCase() + string.slice(1);
}
Insert cell
addTitle = ({
title,
vSpacing,
alignment,
}) => (s) => {
return createShape2({
title,
$body$: s,
},{
'title->body': [C.vSpace(vSpacing), C['align' + capitalizeFirstLetter(alignment)]],
})
}
Insert cell
recipe11 = createShape2({
'$ingredients$': pipe(createShape2({
'$elements$': (contents) => M.text({ contents }),
'$neighbors$': createShape2({
$curr$: 'ref',
$next$: 'ref',
},{
'curr->next': [C.vSpace(2), C.alignLeft],
})
}), addTitle({ title: 'Ingredients', vSpacing: 5, alignment: 'left'})),
'$cookware$': createShape2({
'$elements$': (contents) => M.text({ contents }),
'$neighbors$': createShape2({
$curr$: 'ref',
$next$: 'ref',
},{
'curr->next': [C.vSpace(2), C.alignLeft],
})
}),
'$steps$': createShape2({
'$elements$': (contents) => M.text({ contents }),
'$neighbors$': createShape2({
$curr$: 'ref',
$next$: 'ref',
},{
'curr->next': [C.vSpace(2), C.alignLeft],
})
}),
},{
'ingredients->cookware': [C.hSpace(10), C.alignMiddle],
'ingredients->steps': [C.vSpace(10), C.alignLeft],
})
Insert cell
recipe11(data2)
Insert cell
render(data2, recipe11)
Insert cell
apple_juice = ({
"type": "ingredient",
"name": "apple juice",
"quantity": "1,5",
"units": "cups"
})
Insert cell
ingredientShape = createShape2({
$name$: (name) => M.text({ contents: name, fontSize: '18px', }),
$quantity$: (quantity) => M.text({ contents: quantity, fontSize: '18px', }),
$units$: (units) => M.text({ contents: units, fontSize: '14px', }),
}, {
'quantity->units': [C.alignBottom, C.hSpace(2)],
'units->name': [C.alignBottom, C.hSpace(7.5)],
})
Insert cell
render(apple_juice, ingredientShape)
Insert cell
// Cooklang IR: https://biowaffeln.github.io/cooklang-parser/
({
"metadata": {
"source": "https://www.dinneratthezoo.com/wprm_print/6796",
"total time": "6 minutes",
"servings": "2"
},
"ingredients": [
{
"type": "ingredient",
"name": "apple juice",
"quantity": "1,5",
"units": "cups"
},
{
"type": "ingredient",
"name": "banana",
"quantity": "one sliced",
"units": ""
},
{
"type": "ingredient",
"name": "frozen mixed berries",
"quantity": "1,5",
"units": "cups"
},
{
"type": "ingredient",
"name": "vanilla greek yogurt",
"quantity": 0.75,
"units": "cup"
},
{
"type": "ingredient",
"name": "honey",
"quantity": "some",
"units": ""
}
],
"cookware": [
{
"type": "cookware",
"name": "blender",
"quantity": ""
}
],
"steps": [
[
{
"type": "text",
"value": "Place the "
},
{
"type": "ingredient",
"name": "apple juice",
"quantity": "1,5",
"units": "cups"
},
{
"type": "text",
"value": ", "
},
{
"type": "ingredient",
"name": "banana",
"quantity": "one sliced",
"units": ""
},
{
"type": "text",
"value": ", "
},
{
"type": "ingredient",
"name": "frozen mixed berries",
"quantity": "1,5",
"units": "cups"
},
{
"type": "text",
"value": " and "
},
{
"type": "ingredient",
"name": "vanilla greek yogurt",
"quantity": 0.75,
"units": "cup"
},
{
"type": "text",
"value": " in a "
},
{
"type": "cookware",
"name": "blender",
"quantity": ""
},
{
"type": "text",
"value": "; blend until smooth. If the smoothie seems too thick, add a little more liquid (1/4 cup). "
}
],
[
{
"type": "text",
"value": "Taste and add "
},
{
"type": "ingredient",
"name": "honey",
"quantity": "some",
"units": ""
},
{
"type": "text",
"value": " if desired. Pour into two glasses and garnish with fresh berries and mint sprigs if desired."
}
]
]
})
Insert cell
render(M.text({contents: 'foo bar', fontSize: '18px',}))
Insert cell
>> source: http://www.cookingforengineers.com/recipe/158/Dark-Chocolate-Brownies

Preheat the #oven to 325°F (160°C). Butter a #9x13-inch pan{}.

Break the @chocolate{170g} into chunks. Cut the @butter{170g} up and place both the chocolate and the butter into a #double boiler{}.

Add the @granulated sugar{300g} to the chocolate and stir in.

Lightly beat the @large eggs{3} and the @vanilla extract{1tsp} together in a #mixing bowl{}. Add the eggs and vanilla extract to the mixture and stir and fold until the eggs are blended into the chocolate.

Add the @AP flour{125g} to the batter and stir until all the flour is integrated.

Pour the batter into the buttered (and floured) baking pan.

Place the baking pan on a rack in the center position of the oven and bake for 35 minutes.
Insert cell
import {myRender} from '@joshpoll/bluefish-tutorial-0-8'
Insert cell
render(createShape2({
col1: createShape2({
orig: createShape2({
a: M.circle({r: 10}),
b: M.circle({r: 10}),
c: M.circle({r: 10}),
},{
'a->b': [C.alignMiddle, C.hSpace(10)],
'b->c': [C.alignMiddle, C.hSpace(10)],
}),
'spaced-out': createShape2({
a: M.circle({r: 10}),
b: M.circle({r: 10}),
c: M.circle({r: 10}),
},{
'a->b': [C.alignMiddle, C.hSpace(15)],
'b->c': [C.alignMiddle, C.hSpace(15)],
}),
},{
'orig->spaced-out': [C.alignLeft, C.vSpace(30)],
}),
col2: createShape2({
'clustered': createShape2({
a: M.circle({r: 10}),
b: M.circle({r: 10}),
c: M.circle({r: 10}),
},{
'a->b': [C.alignMiddle, C.hSpace(7.5)],
'b->c': [C.alignMiddle, C.hSpace(30)],
}),
'clustered-diffsize': createShape2({
a: M.circle({r: 10}),
// ab: M.line({strokeWidth: 3, stroke: 'black', }),
b: M.circle({r: 10}),
// bc: M.line({strokeWidth: 3, stroke: 'black',}),
c: M.circle({r: 10}),
},{
'a->b': [C.alignMiddle, C.hSpace(2.5)],
// 'a->ab': [C.makeEqual('centerX', 'left'), C.makeEqual('centerY', 'top')],
// 'ab->b': [C.makeEqual('right', 'centerX'), C.makeEqual('bottom', 'centerY')],
'b->c': [C.alignMiddle, C.hSpace(30)],
// 'b->bc': [C.makeEqual('centerX', 'left'), C.makeEqual('centerY', 'top')],
// 'bc->c': [C.makeEqual('right', 'centerX'), C.makeEqual('bottom', 'centerY')],
}),
},{
'clustered->clustered-diffsize': [C.alignLeft, C.vSpace(30)],
}),
},{
'col1->col2': [C.alignTop, C.hSpace(50)]
}))
Insert cell
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
mkList(['a', 'b', 'c'])
Insert cell
mkListD3 = (xs) => ({
elements: xs,
neighbors: d3.pairs(_.range(xs.length)).map(([curr, next]) => ({
curr: ref(`../../elements/${curr}`),
next: ref(`../../elements/${next}`),
}))
})
Insert cell
mkListD3(['a', 'b', 'c'])
Insert cell
render = (dataOrShape, shapeFn = undefined) => myRender(bfjs.render(dataOrShape, shapeFn))
Insert cell
ref = bfjs.ref
Insert cell
createShape = bfjs.createShape
Insert cell
createShape2 = (shapes, rels={}, options={}) => createShape({ shapes, rels, ...options})
Insert cell
C = bfjs.constraints
Insert cell
M = bfjs.marks
Insert cell
bfjs = require('@bfjs/core@0.8.8/dist/main.js')
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