Public
Edited
Feb 10, 2023
Insert cell
Insert cell
Insert cell
{
const svg = d3.create('svg')
.attr('viewBox', [0, -10, w, h])
.attr('width', 300)
svg.append('g')
// .attr('transform', `translate(${0}, ${0})`)
.selectAll('.hexagon')
.data(hexbin(colours)
.filter(d => d[0].rgb !== 'rgb(0,0,0)')
// .map(d => {d.x = d.x.toFixed(1); d.y = d.y.toFixed(1); return d})
)
.enter().append('path')
.attr('class', 'hexagon')
.attr('transform', d => `translate(${d.x}, ${d.y})`)
.attr('d', hexbin.hexagon())
// .attr('stroke', '#fff')
// .attr('stroke-opacity', '0.5')
// .attr('stroke-width', '1px')
// .attr("fill", d => color(d.length))
.style('fill', d => d[0].rgb)
// .style('fill', d => d[0].rgb === 'rgb(0,0,0)' ? 'rgb(255,255,255)' : d[0].rgb ) // take the colour from the canvas mapping > colours = rgbData(img)

// svg.append("g")
// .selectAll("path")
// .data(contours)
// .join("path")
// .attr("d", path)
// .attr("fill", d => colours(d.value))
// .attr("fill-opacity", 0.8)
// .append("title")
// .text(d => d.value);
return svg.node()
}
Insert cell
contours = d3.tricontour()(imgData)
Insert cell
hexbin(colours).map(d => d)
Insert cell
canvas = DOM.canvas(w, h);
Insert cell
image = {
const canvas = DOM.context2d(w, h);

// render the image on the canvas.
context.drawImage(img, 0, 0, w, h)

// return canvas not context.canvas which will produce
// the wrong imageData scaled up by the devicePixelRatio
return canvas
}
Insert cell
hexbin = hexbinObj.hexbin()
.x(d => d.x.toFixed(1))
.y(d => d.y.toFixed(1))
.radius(size)
.extent([[0, 0], [w, h]]);
Insert cell
context = canvas.getContext('2d')
Insert cell
w = 500 // img.naturalWidth
Insert cell
h = 500 // img.naturalHeight
Insert cell
colours = rgbData(img)
Insert cell
rgbData = async function(img) {

const data = rgbValues({
image: img,
size: size,
context: context
})

return data
}
Insert cell
Insert cell
imgData = getImgData({
image: img,
size: size,
context: context
})
Insert cell
function getImgData (props) {
props = {
image: null,
size: 10,
context: null,
...props
}
const {image, size, context} = props
// number of squares left-to-right and top-to-bottom.
const xSquares = image.naturalWidth / size
const ySquares = image.naturalHeight / size
let data = []
for (let x = 0; x < xSquares; x++) {
for (let y = 0; y < ySquares; y++) {
const rgba = context.getImageData(x * size, y * size, size, size).data
const len = rgba.length
const num = len / 4
let r = 0
let g = 0
let b = 0
// let a = 0
// Sum the color values.
for (let i = 0; i < len; i += 4) {
r += rgba[i]
g += rgba[i + 1]
b += rgba[i + 2]
// a += rgba[i + 3]
}
// save the position and average color value.
data.push([
x * size,
y * size,
Math.round(r / num) +
Math.round(g / num) +
Math.round(b / num),
// Math.round(a / num),
])
}
}
return data
}
Insert cell
Insert cell
color = d3.scaleSequential(d3.interpolateBuPu)(0)

Insert cell
img = FileAttachment("test-poincare-png.png").image()
Insert cell
hexbinObj = require("d3-hexbin@0.2")
Insert cell
d3 = require("d3@7", "d3-delaunay@6", "d3-tricontour@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