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

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