Public
Edited
Mar 28, 2023
7 stars
Also listed in…
ML APIs
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
result
Insert cell
Insert cell
Insert cell
Insert cell
formData = {
return {
version: "435061a1b5a4c1e26740464bf786efdfa9cb3a3ac488595a2de23e143fdb0117",
input: {
prompt: form.pos,
n_prompt: form.neg,
ddim_steps: 20,
scale: form.scale,
image_resolution: '512',
image: await render(drawing.strokes, 512).toDataURL()
// image: canvasImage.toDataURL()
}
}
}
Insert cell
Insert cell
predict = {
return submit(submitter)
}
Insert cell
submit = function* (formData) {
if(!formData) return null;
// Observable specific thing
yield null;

yield fetch(`https://corsproxy.io/?` + encodeURIComponent(`https://api.replicate.com/v1/predictions`), {
// yield fetch(`https://api.replicate.com/v1/predictions`, {
method: 'POST',
headers: {
'Authorization': `Token ${apiKey}`,
// DO NOT set content-type, prevents browser from setting an important boundary property
// 'Content-Type': 'multipart/form-data',
'Accept': "application/json",
},
// body: formData
body: JSON.stringify(formData)
})
.then(response => {
if(response.status !== 200) {
return response.json()
}
return response.json()
})
}
Insert cell
Insert cell
result = {
let data = predict

yield null
// request every half second until its completed
while(data && !data.completed_at && data.created_at && !data.error) {
// console.log("in the loop", data)
if(!predict.urls) {
yield Promises.delay(500, {})
continue
}
yield Promises.delay(500, replicatePrediction(predict.urls.get).then(response => {
data = response
return data
}))
}
}
Insert cell
function replicatePrediction(url) {
let predictionOptions = {
method: "get",
headers: {
Authorization: 'Token ' + apiKey,
'Content-Type': 'application/json'
}
}
return d3.json(`https://corsproxy.io/?` + encodeURIComponent(url), predictionOptions)
}
Insert cell
Insert cell
Insert cell
Insert cell
render(drawing.strokes, 512)
Insert cell
function render(strokes, size) {
let w = size;
let h = size;
let canvas = DOM.canvas(w, h);
let context = canvas.getContext("2d");
// we collect our strokes here and dispatch
let strokeWidth = 5;
let scale = size/300

context.lineCap = "round";
context.lineJoin = "round";

context.clearRect(0, 0, canvas.width, canvas.height);
context.lineWidth = strokeWidth * 2;

context.strokeStyle = "black";

let centered = center(strokes,size,size,scale * .85)
centered.forEach((stroke) => {
// draw the dots
// stroke.forEach(p => {
// context.beginPath()
// context.arc(p[0], p[1], strokeWidth, 0, Math.PI * 2)
// context.fill()
// })
// connect the dots
context.beginPath();
context.moveTo(stroke[0].x, stroke[0].y);
stroke.slice(1).forEach((p) => context.lineTo(p.x, p.y));
context.stroke();
});

canvas.style.border = "solid 1px #ccc";
canvas.style.cursor = "crosshair";

return canvas;
}
Insert cell
Insert cell
viewof chosenSet = setChooser(500, 500)
Insert cell
chosenSet
Insert cell
// viewof set = {
function setChooser(tsneWidth, tsneHeight) {
var el = DOM.svg(tsneWidth,tsneHeight)
var svg = d3.select(el)

let xscale = d3.scaleLinear()
.domain([tsneMeta.xextent[0]*1.1, tsneMeta.xextent[1]*1.1])
.range([0, tsneWidth])

let yscale = d3.scaleLinear()
.domain([tsneMeta.yextent[0]*1.1, tsneMeta.yextent[1]*1.1])
.range([0, tsneHeight])
svg.append("image").attr("href", tsneImage)
.attr("width", tsneWidth).attr("height", tsneHeight)

svg.append("text").classed("set", true)
.attr("transform", `translate(${tsneWidth - 200},20)`)
.style("fill", "#fff")
.style("font-family", "Roboto Mono, Courier New")
.style("font-size", 10)
var rw = (xscale(hits[0].r) - xscale(0))*2 - 2
svg.selectAll("rect.hit").data(hits)
.enter().append("rect").classed("hit", true)
// .attr("r", function(d) { return xscale(d.r) - xscale(0) })
.attr("x", function(d) { return xscale(d.x) - rw/2 })
.attr("y", function(d) { return yscale(d.y) - rw/2 })
.attr("width", function(d) { return rw })
.attr("height", function(d) { return rw })
// .attr("cx", function(d) { return xscale(d.x) })
// .attr("cy", function(d) { return yscale(d.y) })
.style("fill", "steelblue")
.style("stroke", "steelblue")
.style("stroke-width", 1)
.style("fill-opacity", 0.2)
.style("stroke-opacity", 0.4)
.style("cursor", "pointer")
.on("click", function(event, d) {
el.value = d.set
el.dispatchEvent(new CustomEvent("input"))
el.dispatchEvent(new CustomEvent("input"))
svg.selectAll("rect.hit")
.style("fill-opacity", 0.2)
.style("stroke-opacity", 0.4)
.style("stroke-width", 1)
.style("stroke", "steelblue")
d3.select(this)
.style("fill-opacity", 0.4)
.style("stroke-opacity", 1)
.style("stroke-width", 2)
.style("stroke", "lightblue")
})
.on("mouseover", function(event, d) {
d3.select("text.set").text(d.set)
})

svg.selectAll("rect.hit").filter(function(d) { return d === hits[0] })
.style("fill-opacity", 0.4)
.style("stroke-opacity", 1)
.style("stroke-width", 1)
.style("stroke", "lightblue")

el.value = hits[0].set
return el
}
Insert cell
Insert cell
// viewof chosen = {
drawingChooser = function(drawings, w) {
let drawingLine = d3.line()
.x(function(d) { return d.x })
.y(function(d) { return d.y })
.curve(d3.curveBasis)
// console.log("filtered", filtered.length)
var size = 40
var cols = Math.floor(w/size) - 1;
var filtered = drawings.slice(0, cols * cols)
var scale = size/300
var h = filtered.length/cols * size + size * 2
var el = DOM.svg(w, h);
var svg = d3.select(el)
var g = svg.append("g").attr("transform", "translate(0, 0)")

var positioned = filtered.map(function(d,i) {
var x = size/2 + (i % cols) * size
var y = size/2 + Math.floor(i/cols) * size
var strokes = center(d.strokes,size,size,scale * 1.15)
strokes.forEach(function(s) {
s.nstrokes = strokes.length
s.recognized = d.recognized
})
return {
x: x,
y: y,
i: i,
data: d,
strokes: strokes
}
})
var strokecolor = function(t) { return d3.interpolatePurples((1 - t) * 0.6 + 0.4) }
var unreccolor = function(t) { return d3.interpolateReds((1 - t) * 0.6 + 0.4) }
var bgs = g//
.selectAll("g.drawing")
.data(positioned)
.enter().append("g").classed("drawing", true)
bgs.attr("transform", function (d, i) {
return "translate(" + [d.x, d.y] + ")";
})
.style("stroke-width", function (d) {
return 0.75;
});
// for collision detection
bgs.append("rect").attr("width", size).attr("height", size).style("fill", "#fff")
.style("fill-opacity", 0)
.style("stroke", "none")

bgs.selectAll("path.stroke").data(function(d) {
return d.strokes
})
.enter().append("path").classed("stroke", true)
.attr("d", drawingLine)
.style("fill", "none")
.style("stroke-linecap", "round")
.style("pointer-events", "all")
.style("stroke", function(d,i) {
if(d.recognized)
return strokecolor(i/d.nstrokes)
return unreccolor(i/d.nstrokes)
})

bgs
.on("click", function(evt, d) {
choose(d.data)
})
.on("mouseover", function(evt, d) {
d3.select(this).style("stroke-width", 1.5)
})
.on("mouseout", function(evt, d) {
d3.select(this).style("stroke-width", 0.75)
})

function choose(d) {
console.log("chose", d)
el.value = d
el.dispatchEvent(new CustomEvent("input"))
el.dispatchEvent(new CustomEvent("input"))
g.selectAll("g.drawing").selectAll("path.stroke")
.style("stroke", function(d,i) {
if(d.recognized)
return strokecolor(i/d.nstrokes)
return unreccolor(i/d.nstrokes)
})
g.selectAll("g.drawing")
.style("stroke-width", 0.75)
.filter(function(f) { return f.data == d })
.selectAll("path.stroke")
.style("stroke", "#26a69a")
.style("stroke-width", 2)
}
choose(drawings[0])
//.filter(function(d) { return !d.recognized })
// .style("stroke", "#444")
return el
}
Insert cell
blobify = (canvas) => {
return new Promise((resolve, reject) => {
canvas.toBlob((blob) => {
resolve(blob)
}, 'image/png')
})
}
Insert cell
Insert cell
Insert cell
sets = (await fetch(`https://storage.googleapis.com/quickdraw-data/words/grid-${word}/files-${word}.json`)).json()
Insert cell
hits = {
return sets.filter(function(d) { return d.indexOf(word) == 0 })
.map(function(d) {
var stripped = d.replace('.json', '').replace(word, "")
return {
set: d,
x: +stripped.split("_")[1],
y: +stripped.split("_")[2],
r: +stripped.split("_")[3]
}
})
}
Insert cell
tsneImage = `https://storage.googleapis.com/quickdraw-data/images/tsne-${word}.png`
Insert cell
tsneMeta = (await fetch(`https://storage.googleapis.com/quickdraw-data/images/tsne-${word}-meta.json`)).json()
Insert cell
function strokifyDrawing(d) {
var drawing = JSON.parse(JSON.stringify(d)) //well that's one way to copy
var strokes = d.drawing.map(function(s) {
var points = []
s[0].forEach(function(x,i) {
points.push({x: x, y: s[1][i] })
})
return points;
})
drawing.strokes = strokes;
return drawing
}
Insert cell
/*
Center and rescale the strokes of a quickdraw drawing
*/
function center(strokes, width, height, scale) {
var minY = Infinity
var maxY = -Infinity
var minX = Infinity
var maxX = -Infinity
var centroidX = 0
var centroidY = 0
var count = 0
var newstrokes = []
strokes.forEach(function(stroke) {
stroke.forEach(function(p) {
centroidX += p.x
centroidY += p.y
count++
})
})
centroidX /= count;
centroidY /= count;
strokes.forEach(function(stroke) {
var newstroke = []
stroke.forEach(function(op) {
var p = {
x: op.x,
y: op.y
}

p.x *= scale
p.y *= scale
p.x += width/2 - centroidX * scale
p.y += height/2 - centroidY * scale
if(p.y < minY) minY = p.y
if(p.y > maxY) maxY = p.y
if(p.x < minX) minX = p.x
if(p.x > maxX) maxX = p.x
newstroke.push(p)
})
newstrokes.push(newstroke)
})
var diffX = minX - (width - maxX)
var diffY = minY - (height - maxY)
newstrokes.forEach(function(stroke) {
stroke.forEach(function(p) {
p.x -= diffX/2
p.y -= diffY/2
})
})
return newstrokes
}
Insert cell
Insert cell
import { loading as spinner } from "@mateh/loading"
Insert cell
import {textcolor} from "@observablehq/text-color-annotations-in-markdown"
Insert cell
// https://talk.observablehq.com/t/when-do-event-listeners-need-to-be-manually-removed/7160
// this lets us cancel a fetch request if our cell's recalculate before the fetch is complete
function toSignal(invalidation) {
const controller = new AbortController;
invalidation.then(() => controller.abort());
return controller.signal;
}
Insert cell
// import { pipe } from "@tophtucker/pipe"
Insert cell
async function pipe(...args) {

// this map will go from child to container
const containers = new Map();

// set up child containers
const container = html`<div class="pipe-container">
${style}
${args.map(arg => {
const node = html`<div>`
containers.set(arg, node)
return node
})}`;

// render children from `index` forward, starting with `value`
// (which will be re-set by each next child)
async function render(index, value) {
for (const arg of args.slice(index)) {
let node;

// if child is a function, call it with the last child's value;
// if it's a DOM node, render it;
// if it's something else, wrap in a DOM node and render
if (typeof arg === "function") {
const v = await arg(value);
if (v instanceof Node) {
node = v
} else {
node = Object.assign(html`<code><em>${arg.label || "f"}</em></code>`, {value: v})
}
} else if (arg instanceof Node) {
node = arg
} else {
node = html`<div>${arg}`;
}

// when a child emits an input event, re-run every child to the right of it with the new value
node.addEventListener("input", e => {
// console.log("event listener", e, args.indexOf(arg) + 1, node.value)
render(args.indexOf(arg) + 1, node.value)
});

// remove the old version of this child, if any, and add the new one
const parent = containers.get(arg)
if (parent.firstChild) parent.removeChild(parent.firstChild);
parent.append(node);

// if this child has some value, pass it along to the next child
// if this child doesnt implement value, pass through last value
if(node.value !== undefined) value = node.value;
}

// set final value on root container and dispatch input event,
// so that the whole thing can participate in observable dataflow
// console.log("new value who dis?", value)
container.dispatchEvent(new CustomEvent("input"));
container.value = value;
}

// initialize
await render(0)
return container
}
Insert cell
style = html`<style>
.pipe-container {
display: flex;
flex-wrap: wrap;
}
.pipe-container > div {
position: relative;
padding-top: 0.5rem;
margin-right: 1rem;
}

</style>`
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