Public
Edited
Sep 23, 2023
Insert cell
Insert cell
function* toplevel_example() {
// signal
const [count, setCount] = createSignal(3);
// derivation
const doubleCount = () => count() * 2;

// effect
createEffect(() => console.log(doubleCount()));

// update signal
setCount(count() + 1);
}
Insert cell
Insert cell
Insert cell
createSignal_V1 = (value) => {
const get = () => value;
const set = (nextValue) => value = nextValue;
return [get, set];
}
Insert cell
function* signalV1_example() {
const [count, setCount] = createSignal_V1(3);
yield ['count', count()];
setCount(5);
yield ['count', count()];
// we can set count based on the previous value of count
setCount(count() * 2);
yield ['count', count()];
}
Insert cell
Insert cell
Insert cell
Insert cell
function* derivation_example() {
const [count, setCount] = createSignal_V1(3);
yield ['count', count()];
const doubleCount = () => count() * 2;
yield ['doubleCount', doubleCount()];
setCount(5);
yield ['count', count()];
yield ['doubleCount', doubleCount()];
}
Insert cell
logResults(derivation_example);
Insert cell
Insert cell
Insert cell
createSignal_V2 = (value, subscribers) => {
const subscriptions = new Set(subscribers);

const read = () => value;

const write = (nextValue) => {
value = nextValue;

// new! everytime we write to the signal, we'll call its subscriptions
for (const sub of [...subscriptions]) {
sub();
}
}

return [read, write];
}
Insert cell
// NOTE: the output is identical, but we'll also get a console log every time we call setCount
function* effects_example() {
const [count, setCount] = createSignal_V2(3, [() => { console.log('effect triggered!') }]);
yield ['count', count()];
const doubleCount = () => count() * 2;
yield ['doubleCount', doubleCount()];
setCount(5);
yield ['count', count()];
yield ['doubleCount', doubleCount()];
}
Insert cell
Insert cell
Insert cell
createEffect_V2 = (fn) => {
fn();

return fn;
}
Insert cell
// NOTE: the output is identical, but we'll also get a console log every time we call setCount
function* createEffect_example() {
const [count, setCount] = createSignal_V2(3, [createEffect_V2(() => { console.log('effect triggered!') })]);
yield ['count', count()];
const doubleCount = () => count() * 2;
yield ['doubleCount', doubleCount()];
setCount(5);
yield ['count', count()];
yield ['doubleCount', doubleCount()];
}
Insert cell
Insert cell
Insert cell
context = []
Insert cell
Insert cell
// notice `subscriptions` is no longer an input! We're tracking it automatically
createSignal_V3 = (value) => {
const subscriptions = new Set();

// prev:
// const read = () => value;
const read = () => {
// read the latest effect in the context
const running = context[context.length - 1];
// if it exists, add it to our set of subscriptions
if (running) subscriptions.add(running);
return value;
}

const write = (nextValue) => {
value = nextValue;

for (const sub of [...subscriptions]) {
sub();
}
}

return [read, write];
}
Insert cell
createEffect_V3 = (fn) => {
const execute = () => {
// push this effect (the function we're inside!!) onto the context stack
context.push(execute);
try {
fn();
} finally {
// ensure we always pop, even if fn errors
context.pop();
}
}

// execute immediately to implement eager execution and establish dependencies
execute();
}
Insert cell
function* createEffect_V3_example() {
const [count, setCount] = createSignal_V3(3);
createEffect_V3(() => {
console.log(count());
})
yield ['count', count()];
const doubleCount = () => count() * 2;
yield ['doubleCount', doubleCount()];
createEffect_V3(() => {
console.log(doubleCount());
})
setCount(5);
}
Insert cell
logResults(createEffect_V3_example);
Insert cell
Insert cell
anotherVariableSignal = createSignalSub();
Insert cell
anotherVariable = anotherVariableSignal[0]
Insert cell
setAnotherVariable = anotherVariableSignal[1]
Insert cell
createEffectSub(() => {
if (countSub() > 10) {
console.log(countSub());
} else {
console.log(anotherVariable());
}
})
Insert cell
Insert cell
createSignalFinal = (value) => {
const subscriptions = new Set();

const read = () => {
const running = context[context.length - 1];
if (running) {
subscriptions.add(running);
// new! we need to add this signal as a dependency for the running effect
// the set of subscriptions is a stand-in for the signal itself. Note: we're adding _a pointer to the signal's set of subscriptions_ NOT each element of the subscription set.
running.dependencies.add(subscriptions);
}
return value;
};

const write = (nextValue) => {
value = nextValue;

for (const sub of [...subscriptions]) {
sub.execute();
}
};
return [read, write];
}
Insert cell
cleanup = (running) => {
// remove our effect from all our dependencies
// remember, running.dependencies is a set of pointers to subscription sets
for (const dep of running.dependencies) {
dep.delete(running);
}
running.dependencies.clear();
}
Insert cell
createEffectFinal = (fn) => {
const execute = () => {
// cleanup the previous run before running again
cleanup(running);
context.push(running);
try {
fn();
} finally {
context.pop();
}
};

const running = {
execute,
// maintain a set of signals we depend on
dependencies: new Set()
};

execute();
}
Insert cell
Insert cell
Insert cell
createMemo = (fn) => {
const [s, set] = createSignalFinal();
createEffectFinal(() => set(() => fn()));
return s;
}
Insert cell
Insert cell
Insert cell
graph = {
graph_replay;
let firstDelay = 0;
while (true) {
for (const {first, node, nodes} of computeSequence()) {
yield Promises.delay(first ? firstDelay : 1000, dot`digraph "d3-bar-chart" {
${nodes.map(n => `${n.name} [label="${n.name} (${n.indegree})" color=${node === n ? "red" : n.computed ? "black" : n.indegree ? "white" : "gray"}]`).join("\n")}
${nodes.map(i => [...i.outputs].map(o => `${i.name} -> ${o.name} [color=${node === i ? "red" : i.computed ? "black" : "gray"}]`).join("\n")).join("\n")}
}`);
}
firstDelay = 30000;
}
}
Insert cell
viewof graph_replay = Inputs.button("Replay")
Insert cell
function* computeSequence() {
const nodes = computeNodes();
const queue = [];
for (const node of nodes) {
if (!(node.indegree = node.inputs.size)) {
queue.push(node);
}
}
let node;
yield {nodes, first: true};
while (node = queue.pop()) {
node.computed = true;
for (const output of node.outputs) {
if (!--output.indegree) {
queue.push(output);
}
}
yield {node, nodes};
}
yield {nodes};
}
Insert cell
computeSequence()
Insert cell
function computeNodes() {
const map = new Map;
function get(name) {
let node = map.get(name);
if (!node) map.set(name, node = new Node(name));
return node;
}
for (const [i, o] of edges) {
const nodeIn = get(i);
const nodeOut = get(o);
nodeIn.outputs.add(nodeOut);
nodeOut.inputs.add(nodeIn);
}
return [...map.values()];
}
Insert cell
edges = [
["d3", "chart"],
["DOM", "chart"],
["width", "chart"],
["height", "chart"],
["data", "chart"],
["x", "chart"],
["y", "chart"],
["xAxis", "chart"],
["yAxis", "chart"],
["require", "data"],
["d3", "x"],
["data", "x"],
["margin", "x"],
["width", "x"],
["d3", "y"],
["data", "y"],
["height", "y"],
["margin", "y"],
["d3", "xAxis"],
["height", "xAxis"],
["margin", "xAxis"],
["x", "xAxis"],
["d3", "yAxis"],
["margin", "yAxis"],
["y", "yAxis"],
["require", "d3"]
]
Insert cell
class Node {
constructor(name) {
this.name = name;
this.computed = false;
this.indegree = 0;
this.inputs = new Set;
this.outputs = new Set;
}
}
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