Public
Edited
Mar 28, 2023
7 stars
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