Published
Edited
Aug 22, 2020
6 stars
Insert cell
Insert cell
embed1 = embedWithSource({
id: "test",
nodes: [
{
id: 0,
pinned: false,
value: "md`I'm an unpinned cell! Click grey line below to expand and see my source.`"
},
{
id: 1,
pinned: true,
value: "md`I'm a pinned cell! My source is always visible.`"
}
]
})
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
radio0 === 'div' ?
embedWithSource(notebook, 600) : // embed in div
embedWithSourceIframe(notebook, 600) // embed in iframe
Insert cell
Insert cell
Insert cell
Insert cell
moduleDiv = importModule('https://api.observablehq.com/@randomfractals/notebook-info@250.js')
Insert cell
Insert cell
nbDiv = fromV1(moduleDiv)
Insert cell
Insert cell
embedWithSource({id:'@randomfractals/notebook-info', version:'250', nodes:nbDiv}, 800)
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
// old documentation for Runtime.load is at:
// https://github.com/observablehq/runtime/blob/98905d3ad7d6850152e4604faf96f8c8218e93ce/README.md
// This function is modified from:
// https://github.com/observablehq/runtime/blob/9019bfef749255b5ae14d8dad8dfc2d91f88bad9/src/load.js#L4
// See LICENSE cell
function loadWithSource(notebook, library, observer) {
if (typeof library == "function") observer = library, library = null;
if (typeof observer !== "function") throw new Error("invalid observer");
if (library == null) library = new observable.Library();

const {modules, id, nodes} = notebook;
const map = new Map;
const runtime = new observable.Runtime(library);
const main = runtime_module(id);

function runtime_module(id) {
let module = map.get(id);
if (!module) map.set(id, module = runtime.module());
return module;
}

for (const m of modules) {
const module = runtime_module(m.id);
for (let i = 0; i < m.variables.length; i++) {
const v = m.variables[i];
if (module === main) // attach node object to variable
v.node = nodes.find(({id}) => id === v.node_id);
if (v.from) { // import cell
module.import(v.remote, v.name, runtime_module(v.from));
if (module === main) { // imitate Observable's output for an import with a fake cell
const importedVars = [v];
const i0 = i;
// only show this fake cell once for each import
while (i+1 < m.variables.length && m.variables[i+1].from === v.from) {
const nextImport = m.variables[++i];
importedVars.push(nextImport);
module.import(nextImport.remote, nextImport.name, runtime_module(v.from));
}
const nbURL = 'https://observablehq.com' + (v.from.match(/\//) ? '/' : '/d/') + v.from;
module.variable(observer(v, i0, m.variables))
.define(`import ${v.name}`, ["html"],
(function(html){return(html`
<pre>import {${importedVars.map(
({name, remote}) => {
return `<a href="${nbURL}#${remote}">${remote}</a>` + (name === remote ? '' : ' as ' + name);
}
).join(', ')}} from <a href="${nbURL}">${v.from}
`)}));
}
}
else if (module === main) { // only attach observers to variables in the main module
if (v.node === undefined) console.log(`missing node object at variable ${i}: ${JSON.stringify(v)}`);
// comment-only cell: (observer is actually an observer factory)
if (!v.value) module.variable(observer(v, i, m.variables))
.define(null, ["md"], (function(md){return(md`\`\`\`js
${v.node.value}
\`\`\``)}));
else {
if (v.name && v.name.startsWith('initial ')) { // mutable cell
// (assumes variables come in the order initial Y -> mutable Y -> Y)
module.variable().define(v.name, v.inputs, v.value); // initial Y
const v1 = m.variables[++i]; // mutable Y
module.variable().define(v1.name, v1.inputs, v1.value);
const v2 = m.variables[++i]; // Y
v2.node = v.node;
module.variable(observer(v2, i-2, m.variables)).define(v2.name, v2.inputs, v2.value);
}
else {
module.variable(observer(v, i, m.variables)).define(v.name, v.inputs, v.value);
if (v.name && v.name.startsWith('viewof ')) { // viewof cell
// (assumes variables come in the order viewof X -> X)
const v1 = m.variables[++i]; // X
v1.node = v.node;
module.variable().define(v1.name, v1.inputs, v1.value);
}
}
}
}
else module.define(v.name, v.inputs, v.value); // dependencies in imported modules
}
}
return runtime;
}
Insert cell
// This function is modified from Inspector.into:
// https://github.com/observablehq/inspector/blob/115a96dd78d41f7fbebc0260155aa9ec90cccca3/src/index.js#L51
// See LICENSE cell
function intoWithSource(container, prefix="notebook", md, reverse=false) {
if (typeof container === "string") {
container = document.querySelector(container);
if (container == null) throw new Error("container not found");
}
return function(v) { // supplied inputs are variable, array index, array of all variables
const newContainer = container.appendChild(document.createElement("div"));
const div = document.createElement("div");
// makes links to cells work
if (v.name) {
const name = v.name.split(' ');
if (name[0] === "viewof" || name[0] === "initial") name.shift();
if (name.length === 1 && !document.querySelector(name[0])) div.setAttribute("id", name[0]);
}
const newdiv = newContainer.appendChild(div);
if (v.node !== undefined) { // if node not attached, this'll just act like the default inspector
newContainer.setAttribute("id", `${prefix}-node-${v.node.id}`)
newdiv.setAttribute("class", v.node.pinned ?
`${prefix}-cell-pinned${reverse ? ` ${prefix}-reverse` : ''}` :
`${prefix}-cell-unpinned${reverse ? ` ${prefix}-reverse` : ''}`);
if (v.from || v.value) { // show source if not comment-only cell
const sourcediv = reverse ? newContainer.insertBefore(document.createElement("div"), newdiv) :
newContainer.appendChild(document.createElement("div"));
sourcediv.setAttribute(
"class",
`${prefix}-cell-source ${v.node.pinned ?
`${prefix}-cell-source-pinned` :
`${prefix}-cell-source-unpinned`}`);
if (v.node.pinned) sourcediv.appendChild(md`\`\`\`js
${v.node.value}
\`\`\``);
else sourcediv.addEventListener('click', event => {
if (sourcediv.firstChild) sourcediv.firstChild.remove();
else sourcediv.appendChild(md`\`\`\`js
${v.node.value}
\`\`\``);
});
}
}
return new observable.Inspector(newdiv);
};
}
Insert cell
Insert cell
async function embedWithSource(nb, h=null, prefix='notebook', reverse=false) {
const v1script = await toV1(nb, true);
const module = await importModule(`data:text/javascript;base64,${utoa(v1script)}`);
const hello = html`<div style="${h !== null ?
`height:${h}px; overflow:scroll; ` :
''}border: thin solid #eee; padding: 0px; resize: vertical">`;
loadWithSource(module.default, lib, intoWithSource(hello, prefix, md_, reverse));
return hello;
}
Insert cell
async function embedWithSourceIframe(nb, h=null, prefix='notebook', reverse=false) {
const v1script = await toV1(nb, true);
const iFrame = html`<iframe title="${nb.title}" id="${nb.id}"
style="height:100%; width:100%; border: none; border-width:0px; padding:0px;">`;
iFrame.srcdoc = indexhtml(`data:text/javascript;base64,${utoa(v1script)}`, nb.title, prefix, reverse);
return html`<div style="height:${h}px; resize:vertical; overflow:hidden;
border: thin solid #555; padding:0px;">${iFrame}`;
}
Insert cell
// https://css-tricks.com/snippets/css/make-pre-text-wrap/
function styleCells(prefix='notebook') {
return html`style for cells in preview, class prefix: <code>${prefix}</code>
${styleString(prefix)}
`;
}
Insert cell
styleString = (prefix) => `<style>
.${prefix}-cell-pinned {
border: 1px solid #aaa;
margin: 10px 0px 0px 0px;
}
.${prefix}-cell-unpinned {
border: 1px solid #eee;
margin: 10px 0px 0px 0px;
}
.${prefix}-cell-pinned.${prefix}-reverse, .${prefix}-cell-unpinned.${prefix}-reverse {
margin: 0px 0px 10px 0px;
}
.${prefix}-cell-source, .${prefix}-cell-source pre {
margin: 0px;
overflow-wrap: break-word;
white-space: pre-wrap; /* css-3 */
white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
white-space: -pre-wrap; /* Opera 4-6 */
white-space: -o-pre-wrap; /* Opera 7 */
word-wrap: break-word; /* Internet Explorer 5.5+ */
}
.${prefix}-cell-source {
padding: 5px 10px 5px 10px;
}
.${prefix}-cell-source-pinned, .${prefix}-cell-source-pinned .hljs {
background-color: #f7f7ff;
color: #220;
}
.${prefix}-cell-source-unpinned, .${prefix}-cell-source-unpinned .hljs {
cursor: pointer;
background-color: #eee;
color: #333;
}
</style>`
Insert cell
Insert cell
Insert cell
Insert cell
zipBlob = {
const zip = new JSZip();
zip.file("notebook.js", script);
zip.file("index.html", ihtml);
return zip.generateAsync({type:"blob"});
}
Insert cell
Insert cell
// see the Warning in this notebook for why we need to use require_ and md_ here
import {toV1, utoa, observable, importModule, lib, require_, md_} from '@bryangingechen/from-raw-notebook-source-to-observable-runtime-v1-modules'
Insert cell
Insert cell
Insert cell
Insert cell
import {getNotebookByUrl} from '@randomfractals/notebooks'
Insert cell
styles = html`style for markdown bash from <a href="https://observablehq.com/@observablehq/how-to-embed-a-notebook-in-a-react-app">🤔 How to: Embed a Notebook in a React App</a>.
<style>
code.bash {
display: block;
background: #333;
padding: 12px;
}
code.bash, code.bash span {
color: #eee;
}
</style>`
Insert cell
Insert cell
Insert cell
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