Public
Edited
Dec 2, 2023
Insert cell
Insert cell
Insert cell
img = FileAttachment("Screenshot 2023-04-23 212158.png").image()
Insert cell
Insert cell
Insert cell
{
const surface = d3.create('svg')
.attr('width', img.width)
.attr('height', img.height)
.style('border', '1px solid black')
.style('touch-action', 'none')
.style('transform-origin', '0px 0px 0px')

const img_plane = d3.create('svg')
.attr('width', img.width)
.attr('height', img.height)
.style('border', '1px solid black')
.style('touch-action', 'none')
.style('transform-origin', '0px 0px 0px')
const image = surface.append("image")
//.attr("xlink:href", "https://upload.wikimedia.org/wikipedia/commons/thumb/d/d5/Chess_Board.svg/600px-Chess_Board.svg.png")
.attr("xlink:href", dataurl)//"http://jdh.hamkins.org/wp-content/uploads/2018/08/15-final-chessboard-1.jpg")
.attr("width", img.width)
.attr("height", img.height);

const make_draggable = ({element, update_position}) => {
let start = null
let i = 0
element.call(d3.drag()
.on('start', () => null)
.on('drag', () => {
const curr = {x: d3.event.x, y: d3.event.y}
start = i > 1 ? start : curr
const dx = curr.x - start.x
const dy = curr.y - start.y
mutable log = {dy}

update_position({dx, dy})
i++
})
.on('end', () => (start = null, i = 0)))
}
const add_circle = ({x, y}) => surface.append('circle')
.attr('r', 20)
.attr('cx', x)
.attr('cy', y)
.attr('stroke', 'gray')
.attr('stroke-width', 2.5)
.attr('fill', 'transparent')
surface.append('path').datum(state.centers).attr('stroke', 'black').attr('fill', 'none').attr('d', d3.line().x(d => d.x).y(d => d.y).curve(d3.curveLinearClosed))

state.centers.map((c, i) => make_draggable({
element: add_circle(c),
update_position: ({dx, dy}) => {
mutable state =
produce(state, state => {state.centers[i] = {x: c.x + dx, y: c.y + dy}})
}
}))
const sourcePoints = [[0, 0], [img.width, 0], [img.width, img.height], [0, img.height]]
const targetPoints = state.centers.map(({x, y}) => [x, y])
const matrix = get_matrix(sourcePoints, targetPoints)
img_plane.style('transform', "matrix3d(" + matrix + ")")
return surface.node()
}
Insert cell
dataurl = {
// Step 1: Get the image
var image = img

// Step 2: Create a canvas
var canvas = document.createElement('canvas');
canvas.width = image.width;
canvas.height = image.height;

// Step 3: Draw the image on the canvas
var ctx = canvas.getContext('2d');
ctx.drawImage(image, 0, 0);

// Step 4: Convert the canvas to a data URL
return canvas.toDataURL('image/png');
}
Insert cell
output = () => {
const margin = {left: 0, top: 0}

const svg = d3.create('svg')
.attr('width', img.width) // doesn't work
.attr('height', img.height)
.attr('viewBox', `0 0 ${img.width} ${img.height}`) // doesn't work
.style(`transform-origin`, `${margin.left}px ${margin.top}px 0px`)
.style('overflow', 'hidden')
.attr('overflow', 'hidden')
const img_plane = svg.append('g')
/*.attr('width', '100%') // doesn't work
.attr('height', '100%')*/
.style(`transform-origin`, `${margin.left}px ${margin.top}px 0px`)
.style('overflow', 'hidden !important') // doesn't work.
.attr('overflow', 'hidden')
// Doesn't work.
const clipper = svg.append('g').append('clipPath').attr('id', 'clipper').append('rect').attr('x', 0).attr('y', 0).attr('width', img.width).attr('height', img.height).style('overflow', 'hidden !important')
.attr('overflow', 'hidden')
const image = img_plane.append("image")
//.attr("xlink:href", "https://upload.wikimedia.org/wikipedia/commons/thumb/d/d5/Chess_Board.svg/600px-Chess_Board.svg.png")
.attr("xlink:href", dataurl)//"http://jdh.hamkins.org/wp-content/uploads/2018/08/15-final-chessboard-1.jpg")
.attr('clip-path', 'url(#clipper)') // doesn't work
.attr("width", img.width)
.attr("height", img.height)
.attr('transform', "translate(" + margin.left + "," + margin.top + ")")
.style('overflow', 'hidden !important') //doesn't work
.attr('overflow', 'hidden')

const sourcePoints = state.centers.map(({x, y}) => [x, y])
const targetPoints = [[0, 0], [img.width, 0], [img.width, img.height], [0, img.height]]
const matrix = get_matrix(sourcePoints, targetPoints)
svg.style('transform', "matrix3d(" + matrix + ")")
return svg.node()
}
Insert cell
mutable log = null
Insert cell
async function svg_to_png (svg) {
var svgString = new XMLSerializer().serializeToString(svg);

var canvas = html`<canvas></canvas>`;
var ctx = canvas.getContext("2d");
var DOMURL = self.URL || self.webkitURL || self;
var img = new Image();
var svg = new Blob([svgString], {type: "image/svg+xml;charset=utf-8"});
var url = DOMURL.createObjectURL(svg);
const p = new Promise(resolve =>
img.onload = function() {
ctx.drawImage(img, 0, 0);
var png = canvas.toDataURL("image/png");
resolve(png)//html`<img src="${png}"/>`)
DOMURL.revokeObjectURL(png);
})
img.src = url;
return await p
}
Insert cell
produce = (await require('immer@9.0.5/dist/immer.umd.production.min.js')).produce
Insert cell
d3 = require('d3@5.0.0')
Insert cell
mutable state = ({centers: [{x: 0, y: 0}, {x: img.width, y: 0}, {x: img.width, y: img.height}, {x: 0, y: img.height}]})
Insert cell
get_matrix = (sourcePoints, targetPoints) => {
// Borrowed from https://bl.ocks.org/mbostock/10571478
for (var a = [], b = [], i = 0, n = sourcePoints.length; i < n; ++i) {
var s = sourcePoints[i], t = targetPoints[i];
a.push([s[0], s[1], 1, 0, 0, 0, -s[0] * t[0], -s[1] * t[0]]), b.push(t[0]);
a.push([0, 0, 0, s[0], s[1], 1, -s[0] * t[1], -s[1] * t[1]]), b.push(t[1]);
}

var X = solve(a, b, true), matrix = [
X[0], X[3], 0, X[6],
X[1], X[4], 0, X[7],
0, 0, 1, 0,
X[2], X[5], 0, 1
].map(function(x) {
return x//d3.round(x, 6);
});
return matrix
}
Insert cell
mutable sourcePoints = null
Insert cell
mutable targetPoints = null
Insert cell
mutable matrix = null
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