Published
Edited
Dec 16, 2019
Importers
2 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
viewof x = new View(0)
Insert cell
x
Insert cell
bind(html`<input type=range min=1 max:100 step=1>`, viewof x)
Insert cell
bind(html`<input type=number min=10 max=80 step=1>`, viewof x)
Insert cell
import {View, bind} from "@mbostock/synchronized-views"
Insert cell
Insert cell
Insert cell
viewof c = color()
Insert cell
viewof c1 = color({
value: "#0000ff",
title: "Background Color",
description: "This color picker starts out blue"
})
Insert cell
function color(config = {}) {
const {
value = "#000000", title, description, submit, display
} = typeof config === "string" ? {value: config} : config;
const form = input({
type: "color", title, description, submit, display,
attributes: {value}
});
if (title || description) form.input.style.margin = "5px 0";
return form;
}
Insert cell
Insert cell
viewof b = button()
Insert cell
{
b // auto update this block from clicking the button above
return !this;
}
Insert cell
viewof b1 = button({value: "Click me", description: "We use a reference to the button below to record the time you pressed it."})
Insert cell
{
b1;
return new Date(Date.now()).toUTCString()
}
Insert cell
function button(config = {}) {
const {
value = "Ok", title, description, disabled
} = typeof config === "string" ? {value: config} : config;
const form = input({
type: "button", title, description,
attributes: {disabled, value}
});
form.output.remove();
return form;
}
Insert cell
Insert cell
viewof a2 = slider({
min: 0,
max: 1e9,
step: 1000,
value: 3250000,
format: ",",
description:
"Zero to one billion, in steps of one thousand, formatted as a (US) number"
})
Insert cell
viewof a1_1 = slider({
min: 0,
max: 1,
step: 0.01,
format: v => `${Math.round(100 * v)} per cent`,
description: "Zero to one, formatted with a custom function"
})
Insert cell
viewof a1 = slider({
min: 0,
max: 1,
step: 0.01,
format: ".0%",
description: "Zero to one, formatted as a percentage"
})
Insert cell
viewof a3 = slider({
min: 0,
max: 100,
step: 1,
value: 10,
title: "Integers",
description: "Integers from zero through 100"
})
Insert cell
viewof a5 = slider({
min: 0.9,
max: 1.1,
precision: 3,
submit: true,
description: "The same as a4, but only changes value on submit"
})
Insert cell
function slider(config = {}) {
let {
min = 0, max = 1, value = (max + min) / 2, step = "any", precision = 2,
title, description, getValue, format, display, submit,
} = typeof config === "number" ? {value: config} : config;
precision = Math.pow(10, precision);
if (!getValue) getValue = input => Math.round(input.valueAsNumber * precision) / precision;
return input({
type: "range", title, description, submit, format, display,
attributes: {min, max, step, value},
getValue
});
}
Insert cell
function input(config) {
let {
form,
type = "text",
attributes = {},
action,
getValue,
title,
description,
format,
display,
submit,
options
} = config;
const wrapper = html`<div></div>`;
if (!form)
form = html`<form>
<input name=input type=${type} />
</form>`;
Object.keys(attributes).forEach(key => {
const val = attributes[key];
if (val != null) form.input.setAttribute(key, val);
});
if (submit)
form.append(
html`<input name=submit type=submit style="margin: 0 0.75em" value="${
typeof submit == "string" ? submit : "Submit"
}" />`
);
form.append(
html`<output name=output style="font: 14px Menlo, Consolas, monospace; margin-left: 0.5em;"></output>`
);
if (title)
form.prepend(
html`<div style="font: 700 0.9rem sans-serif;">${title}</div>`
);
if (description)
form.append(
html`<div style="font-size: 0.85rem; font-style: italic;">${description}</div>`
);
if (format) format = typeof format === "function" ? format : d3format.format(format);
if (action) {
action(form);
} else {
const verb = submit
? "onsubmit"
: type == "button"
? "onclick"
: type == "checkbox" || type == "radio"
? "onchange"
: "oninput";
form[verb] = e => {
e && e.preventDefault();
const value = getValue ? getValue(form.input) : form.input.value;
if (form.output) {
const out = display ? display(value) : format ? format(value) : value;
if (out instanceof window.Element) {
while (form.output.hasChildNodes()) {
form.output.removeChild(form.output.lastChild);
}
form.output.append(out);
} else {
form.output.value = out;
}
}
form.value = value;
if (verb !== "oninput")
form.dispatchEvent(new CustomEvent("input", { bubbles: true }));
};
if (verb !== "oninput")
wrapper.oninput = e => e && e.stopPropagation() && e.preventDefault();
if (verb !== "onsubmit") form.onsubmit = e => e && e.preventDefault();
form[verb]();
}
while (form.childNodes.length) {
wrapper.appendChild(form.childNodes[0]);
}
form.append(wrapper);
return form;
}
Insert cell
d3format = require("d3-format@1")
Insert cell
Insert cell
DOM.context2d(300, 150).canvas
Insert cell
DOM = ({context2d})
Insert cell
context2d = function(width, height, dpi) {
// https://github.com/observablehq/stdlib/blob/master/src/dom/context2d.js
if (dpi == null) dpi = devicePixelRatio;
var canvas = document.createElement("canvas");
canvas.width = width * dpi;
canvas.height = height * dpi;
canvas.style.width = width + "px";
var context = canvas.getContext("2d");
context.scale(dpi, dpi);
return context;
}
Insert cell
constant = function(x) {
return function() {
return x;
};
}
Insert cell
Insert cell

// import marked from "marked";

md = {
const HL_ROOT =
"https://cdn.jsdelivr.net/npm/@observablehq/highlight.js@2.0.0/";

let md_func = function (require) { // use require because we need it to load libraries
return function() {
return template(
function(string) {
var root = document.createElement("div");
root.innerHTML = marked(string, { langPrefix: "" }).trim();
var code = root.querySelectorAll("pre code[class]");
if (code.length > 0) {
require(HL_ROOT + "highlight.min.js").then(function(hl) {
code.forEach(function(block) {
function done() {
hl.highlightBlock(block);
block.parentNode.classList.add("observablehq--md-pre");
}
if (hl.getLanguage(block.className)) {
done();
} else {
require(HL_ROOT + "async-languages/index.js")
.then(index => {
if (index.has(block.className)) {
return require(HL_ROOT +
"async-languages/" +
index.get(block.className)).then(language => {
hl.registerLanguage(block.className, language);
});
}
})
.then(done, done);
}
});
});
}
return root;
},
function() {
return document.createElement("div");
}
);
};
}
// return md_func;
return md_func(require)();
}
Insert cell
marked = require("marked")
Insert cell
Insert cell
width
Insert cell
width = (function width() {
return observe(function(change) {
var width = change(document.body.clientWidth);
function resized() {
var w = document.body.clientWidth;
if (w !== width) change(width = w);
}
window.addEventListener("resize", resized);
return function() {
window.removeEventListener("resize", resized);
};
});
})()
Insert cell
observe = function(initialize) {
let stale = false;
let value;
let resolve;
const dispose = initialize(change);

function change(x) {
if (resolve) resolve(x), resolve = null;
else stale = true;
return value = x;
}

function next() {
return {done: false, value: stale
? (stale = false, Promise.resolve(value))
: new Promise(_ => (resolve = _))};
}

return {
[Symbol.iterator]: that,
throw: () => ({done: true}),
return: () => (dispose != null && dispose(), {done: true}),
next
};
}
Insert cell
that = function that() {
return this;
}
Insert cell
Insert cell
{
debugger;
html`hello` // check to see whether it is my html or the builtin html
}
Insert cell
html = template(function(string) {
var template = document.createElement("template");
template.innerHTML = string.trim();
return document.importNode(template.content, true);
}, function() {
return document.createElement("span");
});
Insert cell
template = {
return function template(render, wrapper) {
return function(strings) {
var string = strings[0],
parts = [], part,
root = null,
node, nodes,
walker,
i, n, j, m, k = -1;

// Concatenate the text using comments as placeholders.
for (i = 1, n = arguments.length; i < n; ++i) {
part = arguments[i];
if (part instanceof Node) {
parts[++k] = part;
string += "<!--o:" + k + "-->";
} else if (Array.isArray(part)) {
for (j = 0, m = part.length; j < m; ++j) {
node = part[j];
if (node instanceof Node) {
if (root === null) {
parts[++k] = root = document.createDocumentFragment();
string += "<!--o:" + k + "-->";
}
root.appendChild(node);
} else {
root = null;
string += node;
}
}
root = null;
} else {
string += part;
}
string += strings[i];
}

// Render the text.
root = render(string);

// Walk the rendered content to replace comment placeholders.
if (++k > 0) {
nodes = new Array(k);
walker = document.createTreeWalker(root, NodeFilter.SHOW_COMMENT, null, false);
while (walker.nextNode()) {
node = walker.currentNode;
if (/^o:/.test(node.nodeValue)) {
nodes[+node.nodeValue.slice(2)] = node;
}
}
for (i = 0; i < k; ++i) {
if (node = nodes[i]) {
node.parentNode.replaceChild(parts[i], node);
}
}
}

// Is the rendered content
// … a parent of a single child? Detach and return the child.
// … a document fragment? Replace the fragment with an element.
// … some other node? Return it.
return root.childNodes.length === 1 ? root.removeChild(root.firstChild)
: root.nodeType === 11 ? ((node = wrapper()).appendChild(root), node)
: root;
};
}
}
Insert cell
Insert cell
P5 = require('https://unpkg.com/p5@0.6.0/lib/p5.js')
Insert cell
function* p5(sketch) {
const element = DOM.element('div');
// p5.js really likes its target element to already be in the DOM, not just
// floating around detached. So, before we call P5, we yield it, which puts
// in the DOM.
// debugger;
yield element;
// This is ‘instance mode’ in p5 jargon: instead of relying on lots of
// globals, we create a sketch that has its own copy of everything under p5.
const instance = new P5(sketch, element, true);
// inside this construction function, `sketch(this)` is executed, this is why inside sketch closure, sketch refers to p5 too. (debugging and then search sketch inside p5.js source code)
// This is the tricky part: when you run P5(sketch, element), it starts a
// loop that updates the drawing a bunch of times a second. If we were just
// to call P5 repeatedly with different arguments, the loops would all keep
// running, one on top of the other. So what we do is we use this cell
// as a generator, and then when that generator is interrupted, like
// when you update the code in the sketch() method, then we call instance.remove()
// to clean it up.
try {
while (true) { // keep drawing a new div
yield element;
}
} finally {

instance.remove(); // when the sketch-code is changed, this line will be triggered
}
}
Insert cell
Insert cell
d3 = require("https://cdn.jsdelivr.net/npm/d3@5.12.0/dist/d3.js")
Insert cell
d3_min = require("d3@5")
Insert cell
Insert cell
r = require.alias({
react: "react@16/umd/react.development.js",
"react-dom": "react-dom@16/umd/react-dom.development.js"
})
Insert cell
React = r("react")
Insert cell
ReactDOM = r("react-dom")
Insert cell
htm = import('https://unpkg.com/htm@2?module').then(r => r.default.bind(React.createElement)) // no development version is available
Insert cell
md `---
## general utils`
Insert cell
stylesheet=html`
<style>

span, .highlight {
color: hsl(320, 60%, 60%);
font-weight: 400;
background-color: #F7F7F9;
}
</style>
`
Insert cell
html`<span class="highlight">cells</span>`
Insert cell
md`
## Create style

${
html`<span class="highlight">cells</span>`}

\`console.log\`
`
Insert cell
Insert cell
Insert cell
headerLink(4, "https://observablehq.com/@embracelife/tutorial-utilities", "tutorial-utilities")
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
anchorLinkWithin("#anchorlink", "Build anchor link")
Insert cell
Insert cell
webpage = (w, h, url) =>
html`<iframe width=${w} height=${h} src=${url}></iframe>`
Insert cell
webpage(100, 50, "https://javascript.info/promise-chaining#bigger-example-fetch")
Insert cell
Insert cell
video = url =>
html`<video style='max-width: 640px;width:100%;border-radius:4px;border:1px solid #ccc;' controls src='https://d1adp7gr8mqax6.cloudfront.net/${url}.mp4' />`
Insert cell

videoyt = (width, height, url) => {
return html`<iframe width="${width}" height="${height}" src="https://www.youtube-nocookie.com/embed/${url}" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen></iframe>`}
Insert cell
url = "Aznz6oLbuFQ"
Insert cell
videoyt(url, 800, 600)
Insert cell
Insert cell
key = c =>
html`<span style='border-radius:5px;background:#ddf;display:inline-block;padding:0 4px;'>${c}</span>`
Insert cell
key("Ctrl + Enter")
Insert cell
Insert cell
code = c =>
html`<span style='border-radius:5px;background:#F7F7F9;display:inline-block;padding:0 4px; color:hsl(320, 60%, 60%)'>${c}</span>`
Insert cell
code("function")
Insert cell
md`${code('function codeup(){}')}`
Insert cell
Insert cell
pin = html`<svg style='vertical-align:middle' viewBox="0 0 16 16" fill='#266bd9' stroke=none viewBox="0 0 16 16" width=16 height=16>
<path d="M8 1h3v1l-1 1v4l2 .875V9H9v5.125L8 15l-1-.875V9H4V7.875L6 7V3L5 2V1z" />
</svg>`
Insert cell
Insert cell
caret = html`<svg width=8 height=8 class='observablehq--caret'>
<path d='M7 4L1 8V0z' fill='currentColor' />
</svg>`
Insert cell
Insert cell
toggle = html`<svg viewBox="0 0 16 16" fill=none stroke=currentColor viewBox="0 0 12 8" width=16 height=16 stroke-linecap=round>
<path transform='translate(8, 4) rotate(90)' d="M10 6L6 2 2 6" />
</svg>`
Insert cell
step10 = md `### How to embed a menu svg`
Insert cell
Insert cell
Insert cell
signinNote = md`_This tutorial presumes you have a new notebook to work with, and you’ve signed up for Observable. Click <span style='background-color:#b9f3c2;padding:0px 5px; border-radius: 4px;'>Sign in</span> in the top right to join Observable using your GitHub account, and then click <span style='background:#eee;padding:0px 5px;border-radius:4px;'>+ New</span> in the top right to create a new notebook._`
Insert cell
md`${signinNote}`
Insert cell
Insert cell
seriesNavigation = active => {
const svgPath =
"M12 21c-1.108 0-2.068-.261-2.88-.783a5.137 5.137 0 0 1-1.867-2.126 11.821 11.821 0 0 1-.952-2.847A16.523 16.523 0 0 1 6 12c0-.862.052-1.686.157-2.474.104-.787.297-1.587.578-2.399.281-.812.643-1.516 1.084-2.113a4.987 4.987 0 0 1 1.735-1.455C10.27 3.186 11.084 3 12 3c1.108 0 2.068.261 2.88.783a5.137 5.137 0 0 1 1.867 2.126c.434.895.751 1.844.952 2.847.2 1.002.301 2.084.301 3.244 0 .862-.052 1.686-.157 2.474a11.76 11.76 0 0 1-.59 2.399c-.29.812-.65 1.516-1.084 2.113-.434.597-1.008 1.082-1.723 1.455-.715.373-1.53.559-2.446.559zm2.118-6.882A2.888 2.888 0 0 0 15 12c0-.824-.287-1.53-.86-2.118C13.566 9.294 12.853 9 12 9c-.853 0-1.566.294-2.14.882A2.925 2.925 0 0 0 9 12c0 .824.287 1.53.86 2.118.574.588 1.287.882 2.14.882.853 0 1.559-.294 2.118-.882zM12 24c6.627 0 12-5.373 12-12S18.627 0 12 0 0 5.373 0 12s5.373 12 12 12z";
function i(n, url, name) {
return html`<a style='${
n == active ? "background:#cdecff;color:#000;" : ""
} padding:4px 15px 4px 10px;display:inline-flex;' href='https://beta.observablehq.com/@observablehq/${url}'><span style='margin-right:0.5em;font-weight:bold;'>${n}</span> ${name}</a>`;
}
return html`<div style='display:inline-flex;border:1px solid #a9b0bc; align-items: stretch; border-radius:4px;'><svg style="padding:8px 8px;" width=18 height=18 viewBox="0 0 24 24"><path fill="currentColor" d='${svgPath}' /></svg><strong style='padding:4px 0.8em 4px 0.1em'>Tutorials</strong> ${i(
1,
"tutorial-1-lunch-calculator",
"Lunch calculator"
)}
${i(2, "tutorial-2-dog-pictures", "Dog pictures")}
${i(3, "tutorial-3-visualizing-data", "Visualizing data")}
</div>`;
}
Insert cell
seriesNavigation(1)
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