Public
Edited
Mar 23, 2024
9 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
isosceles = ({ w = 100, l = 100, color = "#0000FF" } = {}) => {
// TODO: Pass in variable
const id = DOM.uid().id; // A uid helps reconcile's matcher.
const wVar = variable(w).onWrite(() => reconcile(me, render()));
const lVar = variable(l).onWrite(() => reconcile(me, render()));
const colorVar = variable(color, () => reconcile(me, render()));
const render = () =>
viewSvg`<polygon id=${id} fill=${colorVar} points=
"${wVar.value / 2} 0, 0 ${lVar.value}, ${-wVar.value / 2} 0"
><!-- ${["w", wVar]} ${["l", lVar]} ${["color", colorVar]} -->`; // TODO, better way to bind dummies
const me = render();
return me;
}
Insert cell
isoscelesFabian = ({
w = 100 /* TODO Could we pass in a variable here and bind to it automgically*/,
l = 100,
color = "#0000FF"
} = {}) => {
const id = DOM.uid().id; // A uid helps reconcile's matcher.
const vars = propsToVars({ w, l, color }, () => reconcile(me, render(vars)));
const render = ({ color, w, l }) =>
htl.svg`<polygon id=${id} fill=${color} points=
"${w / 2} 0, 0 ${l}, ${-w / 2} 0"
></polygon>`;
const me = render(vars);
console.log("me", me.w);
me.value = {};
Object.entries(vars).forEach(([name, variable]) => {
Object.defineProperty(me, name, {
get: () => variable,
set: (v) => (variable = v),
enumerable: true,
configurable: true
});
Object.defineProperty(me.value, name, {
get: () => variable.value,
set: (v) => (variable.value = v),
enumerable: true,
configurable: true
});
});
return me;
}
Insert cell
propsToVars = (obj, onWrite) => Object.fromEntries(Object.entries(obj).map(([k, v]) => [k, variable(v).onWrite(onWrite)]))
Insert cell
viewof testIsosceles = isosceles()
Insert cell
viewof testIsosceles.values
Insert cell
Insert cell
viewof testIsosceles.outerHTML
Insert cell
Insert cell
testIsosceles.w
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
testIsosceles.w
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
transform = ({ tx = 0, ty = 0, sx = 1, sy = 1, angle = 0, inner } = {}) => {
const id = DOM.uid().id;
const txVar = variable(tx).onWrite(() => reconcile(me, render()));
const tyVar = variable(ty).onWrite(() => reconcile(me, render()));
const sxVar = variable(sx).onWrite(() => reconcile(me, render()));
const syVar = variable(sy).onWrite(() => reconcile(me, render()));
const aVar = variable(angle).onWrite(() => reconcile(me, render()));
const render = () =>
viewSvg`<g id=${id} transform="
translate(${["tx", txVar]} ${["ty", tyVar]})
scale(${["sx", sxVar]} ${["sy", syVar]})
rotate(${["angle", aVar]})">
${["inner", inner]}
</g>`;
const me = render();
return me;
}
Insert cell
viewof testTransform = transform({ inner: isosceles(), tx: 100 })
Insert cell
testTransform
Insert cell
viewof testTransform
Insert cell
viewof testTransform.outerHTML
Insert cell
htl.svg`<svg width="100" viewBox="-100 -100 200 200">
${viewof testTransform}
`
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
viewof arrayControl = view`<div style="display: flex">
<table>
${[
"r",
// Its slightly annoying we have to bind the inner control to a property
new Array(10).fill(null).map((r, i) => {
return view`<tr>
<td>${[
"v",
Inputs.range([0, 10], { label: `r[${i}].v`, value: i })
]}</td>
</tr>`;
})
]}
</table>
</div>`
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
viewof world = viewSvg`<svg style="position: fixed; top: 0px; botom:0px;" width="${width}" height=${documentHeight} viewBox="0 0 ${width} ${documentHeight}" pointer-events="none">
${[
"darts",
new Array(dartParams.n).fill(null).map((_, i) => {
const dart = isosceles({
color: `hsla(${(i * 360) / dartParams.n},100%, 50%, 20%)`
});
Inputs.bind(dart.w, viewof dartLook.w, invalidation);
Inputs.bind(dart.l, viewof dartLook.l, invalidation);
return transform({
inner: dart,
angle: -(i * 360) / dartParams.n,
tx: 100 * Math.sin((2 * i * Math.PI) / dartParams.n) + width / 2,
ty: 100 * Math.cos((2 * i * Math.PI) / dartParams.n) + width / 2
});
})
]}
`
Insert cell
world
Insert cell
viewof world
Insert cell
Insert cell
dartControl = {
now;
world.darts.forEach(dart => {
dart.tx -= dartParams.v * Math.sin((2 * Math.PI * dart.angle) / 360);
dart.ty += dartParams.v * Math.cos((2 * Math.PI * dart.angle) / 360);
const target =
(Math.atan2(dart.tx - mousePos[0], -dart.ty + mousePos[1]) * 180) /
Math.PI;
let error = target - dart.angle;
while (error > 180) error -= 360;
while (error < -180) error += 360;

if (error > 180) error = 360 - error;
dart.angle += 0.01 * error;
});
}
Insert cell
Insert cell
Insert cell
Insert cell
viewof coroutineExample
Insert cell
coroutineExample
Insert cell
Insert cell
async function* flashSquare() {
for (let index = 0; index < 360; index += 5) {
yield Object.defineProperty(
html`<span style="display:inline-block; width:50px;height:50px; background-color: hsl(${index}, 50%, 50%);"></span>`,
'value',
{
value: "square"
}
);
if (index === 0) yield new Event("input", { bubbles: true });
await Promises.delay(10);
}
return null; // End coroutine
}
Insert cell
function toView(generator) {
let current;
const holder = Object.defineProperty(document.createElement('span'), 'value', {
get : () => current?.value,
set : (v) => current ? current.value = v : null,
enumerable: true
});
new Promise(async () => {
const iterator = generator();
let { done, value } = await iterator.next();
while (!done) {
if (value instanceof Event) {
holder.dispatchEvent(value);
} else {
current = value
if (value) {
const span = document.createElement('span');
span.appendChild(value);
reconcile(holder, span);
}
}
({ done, value } = await iterator.next());
}
holder.remove();
});
return holder;
}
Insert cell
Insert cell
toView(flashSquare)
Insert cell
flashSquare()
Insert cell
viewof square = view`<span>${['sqaure', await toView(flashSquare)]}`
Insert cell
square.square
Insert cell
Insert cell
async function* flashStar() {
for (let index = 0; index < 360; index += 5) {
yield Object.defineProperty(
htl.svg`<svg height="50" width="50" viewbox="0 0 200 200">
<polygon points="100,10 40,198 190,78 10,78 160,198"
style="fill:hsl(${index}, 50%, 50%);" /></svg>`,
'value',
{
value: "star"
}
);
if (index === 0) yield new Event("input", { bubbles: true });
await Promises.delay(10);
}
}
Insert cell
flashStar()
Insert cell
async function* choose() {
let resolve;
yield Object.defineProperty(
htl.html`<button onclick=${() =>
resolve('star')}>click to play star</button>
<button onclick=${() =>
resolve('square')}>click to play square</button>`,
'value',
{
value: 'undecided'
}
);
yield new Event("input", { bubbles: true });
return await new Promise(function(_resolve) {
resolve = _resolve;
});
}
Insert cell
Insert cell
Insert cell
viewof animatedUI
Insert cell
Insert cell
Insert cell
function variable(value) {
let onWrites = [];
const self = new EventTarget();
return Object.defineProperties(self, {
value: {
get: () => value,
set: newValue => {
value = newValue;
onWrites.forEach(onWrite => onWrite(value)); // TODO: use CustomEvent
},
enumerable: true
},
toString: {
// Should this be a Text node?
value: () => `${value}`
},
onWrite: {
value: cb => {
onWrites.push(cb);
return self;
}
}
});
}
Insert cell
Insert cell
Insert cell
Insert cell
import { footer } from "@tomlarkworthy/footer"
Insert cell
footer
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more