Public
Edited
Feb 22
Importers
2 stars
Insert cell
Insert cell
Insert cell
bar2 = 21
Insert cell
Insert cell
Insert cell
foo = 32
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
myvalue = "42"
Insert cell
Insert cell
Insert cell
mainVariables
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
end = Symbol()
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
visualizer = (
runtime,
{
invalidation,
module = main,
filter = () => true,
inspector = Inspector.into,
detachNodes = false,
classList = ""
} = {}
) => {
console.log("creating visualizer");
backgroundJobs;
const root = html`<div class="observablehq-root lope-viz ${classList}" style="min-height: 2rem; min-width: 2rem;"></div>`;
root.filter = filter;
root.module = module;
root.inspector = inspector;
root.detachNodes = detachNodes;
const sortable = new Sortable(root, {
group: "visualizer",
preventOnFilter: false,
filter: (e, target) => {
const rect = target.getBoundingClientRect();
return !(
e.clientX >= rect.right - 14 &&
e.clientX <= rect.right &&
e.clientY >= rect.top &&
e.clientY <= rect.top + 14
);
},
onUpdate: (evt) => viewof updateEvent.send({ runtime, evt, sortable }),
onAdd: (evt) => viewof updateEvent.send({ runtime, evt, sortable })
});

viewof visualizers.value.add(root);
viewof visualizers.dispatchEvent(new Event("input"));
invalidation.then(() => {
console.log("removing visualizer", root);
root.remove();
root.dispatchEvent(new Event("input"));
sortable.option("disabled", true);
// We need to keep roots around so we know not to sync with them
// But this is also a cause of a memory leak.
// Probably we need a new set to record the disposed sync div nodes.
viewof visualizersToDelete.value.add(root);
viewof visualizersToDelete.dispatchEvent(new Event("input"));
});
return root;
}
Insert cell
Insert cell
Insert cell
inspectors = {
const inspectors = this || new Map(); // preserve state across invalidations
visualizers.forEach((root) => {
if (inspectors.has(root)) return;
const factory = root.inspector(root);
const inspector = (variable, ...args) => {
const inspector = factory(variable, ...args);
inspector._node.variable = variable;
return inspector;
};
inspectors.set(root, inspector);
});
inspectors.forEach((fn, root) => {
if (!visualizers.has(root)) {
console.log("tidy up inspector for ", root);
inspectors.delete(root);
}
});
return inspectors;
}
Insert cell
Insert cell
syncers = {
console.log("sync");
const syncers = this || new Map();

// remove stale sync state
syncers.forEach(({ observers }, root) => {
if (!inspectors.has(root)) {
console.log("tidy up syncer for ", root);
syncers.delete(root);
observers.forEach(({ observer, remove }, v) => {
remove();
if (v?._observer?.fulfilled && !!v._value) {
v._observer.fulfilled(v._value, v._name);
}
});
}
});

inspectors.forEach((inspector, root) => {
if (viewof visualizersToDelete.value.has(root)) return;
if (!syncers.has(root)) {
syncers.set(root, {
observers: new Map()
});
}
const { observers } = syncers.get(root);
const seen = new Set();
let i = 0;
const state = {};
const cells = cellMaps.get(root.module);
cells.forEach((cells) => {
const v = cells[0];
if (v._name === "debug_cell") debugger;
if (!root.filter(v, i++, state)) return;
// don't put containers in containers, TODO, if pending it can get attached
if (visualizers.has(v._value) && v._value.detachNodes) return;
seen.add(v);
if (
observers.has(v) &&
observers.get(v).version == v._version &&
observers.get(v).root == root
) {
// no change
} else {
if (observers.has(v)) {
observers.get(v).remove(); // tidy up previous
}
const observer = inspector(v);
const remove = observe(v, observer, { detachNodes: root.detachNodes });
observers.set(v, {
observer,
version: v._version,
root,
remove: () => {
if (v._name === "debug_cell") debugger;
observers.delete(v);
observer._node.remove();
remove();
}
});
}
});

// remove stale variables
[...observers.entries()].forEach(([v, _]) => {
if (!seen.has(v) && observers.has(v)) {
if (v._name === "debug_cell") debugger;
const { observer, remove } = observers.get(v);
observer.remove();
}
});

// sync dom
root.innerHTML = "";
allVariables.forEach((v) => {
if (observers.has(v)) {
const { observer, remove } = observers.get(v);
root.appendChild(observer._node);
}
});
// let current = root.firstChild;
// allVariables.forEach((v) => {
// if (v._name === "debug_cell") debugger;
// if (observers.has(v)) {
// const { observer, remove } = observers.get(v);
// if (current.variable == v) {
// current = current.nextSibling;
// } else {
// debugger;
// current.before(observer._node);
// }
// }
// });
// // remove left overs
// while (current !== null) {
// const toRemove = current;
// current = current.nextSibling;
// toRemove.remove();
// }

return observers;
});

// tidy up resources
[...viewof visualizersToDelete.value].forEach((root) => {
if (syncers.has(root)) {
const { observers } = syncers.get(root);
[...observers].forEach((v) => {
const { observer, remove } = observers.get(v);
observer.remove();
});
}

syncers.delete(root);
inspectors.delete(root);
viewof visualizersToDelete.value.delete(root);
viewof visualizers.value.delete(root);
});
return syncers;
}
Insert cell
Insert cell
Insert cell
Insert cell
lastVariableMoved = toObject(updateEvent.evt.item.variable)
Insert cell
onUpdateAction = {
console.log("onUpdate", updateEvent);
const order = updateEvent.sortable.toArray();
const evt = updateEvent.evt;
const variable = evt.item.variable;
const newIndex = evt.newIndex;
const oldIndex = evt.oldIndex;
const variables = viewof allVariables.value;
// find variable one position after
const displaced =
updateEvent.evt.item.parentElement.children[evt.newIndex + 1]?.variable;
if (displaced !== undefined) {
// move to the position that is before
const current = [...variables].findIndex((v) => v == variable);
let target = [...variables].findIndex((v) => v == displaced);
target -= target > current ? 1 : 0;
console.log("moving to ", target, displaced);
repositionSetElement(variables, variable, target);
} else {
// its last in list
const last =
updateEvent.evt.item.parentElement.children[evt.newIndex - 1].variable;
const target = [...variables].findIndex((v) => v == last);
console.log("moving to ", target, displaced);
repositionSetElement(variables, variable, target);
}

viewof updateEvent.resolve();
return updateEvent;
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
submit_summary // should not be needed if editor worked properly
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