Unlisted
Edited
Sep 5, 2022
Insert cell
Insert cell
// this one works but not clipped
blobs = {
const newCircle = circleGenerator(50) // circle center coordinates generator, max radius 50
// create n number of blobs with: x, y coordinates and (radius, angle) for each blob
let blobs = []
for (let i = 0; i < n_blobs; i++) {
let x_y_coords = newCircle(n_blobs)
blobs.push({
x_coord: x_y_coords[0],
y_coord: x_y_coords[1],
color: colorScale(random.value()),
blob_random: 0.7 + Math.random(),
scaling_factor: 0
})
blobs[i].blob = []
for (let r = 0; r <= n_points_blob; r++) {
blobs[i].blob.push({
radius: (1+ Math.random() * 0.2 * blobs[i].blob_random),
angle: Math.PI * 2 / n_points_blob * r,
})
}
}
// console.log(blobs[0].scaling_factor)
// canvas
const ctx = DOM.context2d(width, height)
// GSAP
gsap.to(blobs, {
scaling_factor: 100,
onUpdate: animate,
duration: 5
})
// console.log(blobs[0].blob)
function animate() {
ctx.globalCompositeOperation = "soft-light"
ctx.clearRect(0, 0, width, height)
// console.log(blobs[0].scaling_factor)
blobs.forEach((d, i) => {
// this is essential and ugly
// but works: animate scaling_factor to change radius
d.blob.forEach((data,index) => {
// console.log(d.blob[index].radius)
d.blob[index].radius += d.blob[index].radius/d.scaling_factor
})
ctx.save();
ctx.translate(d.x_coord, d.y_coord)
ctx.fillStyle = d.color
ctx.beginPath()
line.context(ctx)(d.blob)
ctx.fill()
ctx.closePath()
ctx.restore()
})

const voronoi = d3.voronoi()
.x((d) => d.x_coord)
.y((d) => d.y_coord);
const diagram = voronoi(blobs);
const links = diagram.links();
ctx.globalCompositeOperation = "source-over";
ctx.beginPath();
links.forEach((l) => {

ctx.moveTo(l.source.x_coord, l.source.y_coord);
ctx.lineTo(l.target.x_coord, l.target.y_coord);
});
ctx.strokeStyle = "rgba(255,255,255,0.4)";
ctx.stroke();


// voronoi的特征点
ctx.beginPath();
blobs.forEach((b) => {
// console.log(b)
ctx.moveTo(b.x_coord + 2.5, b.y_coord);
ctx.arc(b.x_coord, b.y_coord, 2.5, 0, Math.PI * 2, false);
});
ctx.fillStyle = "white";
ctx.fill();
}
// why is anything written outside of canvas not working?
return ctx.canvas
}
Insert cell
{
const ctx = DOM.context2d(width, height)
ctx.globalCompositeOperation = "soft-light"
let blobs = []
const newCircle = bestCircleGenerator(blobs, 0.1 * size, 0, containerPath, ctx, size) // circle center coordinates generator, max radius 50
// clip

// create n number of blobs with: x, y coordinates and (radius, angle) for each blob

for (let i = 0; i < n_blobs; i++) {
let x_y_coords = newCircle(n_blobs)
blobs.push({
x_coord: x_y_coords[0],
y_coord: x_y_coords[1],
color: colorScale(random.value()),
blob_random: 0.7 + Math.random(),
scaling_factor: 0
})
blobs[i].blob = []
for (let r = 0; r <= n_points_blob; r++) {
blobs[i].blob.push({
radius: (0.7 + Math.random() * 0.2 * blobs[i].blob_random),
angle: Math.PI * 2 / n_points_blob * r,
})
}
}
// console.log(blobs[0].scaling_factor)
// canvas
ctx.beginPath();
ctx.arc(100, 75, 50, 0, Math.PI * 2);
// GSAP
gsap.to(blobs, {
scaling_factor: 100,
onUpdate: animate,
duration: 1
})
console.log(blobs[0].blob)
function animate() {
ctx.clearRect(0, 0, width, height)
// console.log(blobs[0].scaling_factor)
blobs.forEach((d, i) => {
// this is essential and ugly
// but works. Life savior right here: https://greensock.com/forums/topic/9905-animate-one-property-dependant-on-another/
d.blob.forEach((data,index) => {
// console.log(d.blob[index].radius)
d.blob[index].radius += d.blob[index].radius/15
})
ctx.save();
ctx.translate(d.x_coord, d.y_coord)
ctx.fillStyle = d.color
ctx.beginPath()
line.context(ctx)(d.blob)
ctx.fill()
ctx.closePath()
ctx.restore()
})
}

return ctx.canvas
}
Insert cell
// canvas = {
// // replay;
// random.setSeed(123)
// const ctx = DOM.context2d(width, height);
// // line.context(d.data)
// ctx.clearRect(0, 0, size, size);
// ctx.globalCompositeOperation = "soft-light"
// let blobs = []
// const newCircle = circleGenerator(15)
// for (let i = 0; i < n_blobs; i++) {
// let x_y_coords = newCircle(n_blobs)

// blobs.push({
// x: x_y_coords[0],
// y: x_y_coords[1],
// blob_growth_speed: 10,
// max_radius: size * (random.value() * 0.15 + 0.1),
// color: colorScale(random.value()),
// alive: true,
// })
// }

// // define each blob
// // blobs.forEach(d => {
// // d.blob = [];
// // for (var i = 0; i < n_points_blob; i++) {
// // d.blob.push({
// // radius: d.growth_speed * 30,
// // angle: Math.PI * 2 / n_points_blob * i,
// // // growth_speed: 0.7 + Math.random() * 0.2
// // });
// // }
// // })
// // gsap.to(blobs, {
// // blob_growth_speed: (index, target, targets) => 50, // this is run in one frame
// // // x: (i, target) => console.log(blobs[i]),
// // duration: 5,
// // ease: 'power.3.out',
// // onUpdate: animate // this is run per frame
// // })
// function animate() {
// blobs.forEach(d => {
// random.setSeed(123)
// d.blob = [];
// for (var i = 0; i < n_points_blob; i++) {
// d.blob.push({
// radius: d.blob_growth_speed * (0.7 + random.value() * 0.2) ,
// angle: Math.PI * 2 / n_points_blob * i,
// // point_growth_speed: 0.7 + random.value() * 0.2
// });
// }
// })
// ctx.clearRect(0, 0, width, height);
// blobs.forEach((d) => {
// // console.log(d)
// // console.log(blobs[0].blob_growth_speed)
// ctx.save();
// ctx.translate(d.x, d.y);
// ctx.fillStyle = d.color;
// ctx.beginPath();
// line.context(ctx)(d.blob)
// ctx.fill();
// ctx.closePath();
// ctx.restore();
// })
// }
// animate()
// // const timeline = gsap.timeline()
// // timeline.to(blobProps, {
// // })
// yield ctx.canvas
// }
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
// newCircle = bestCircleGenerator(blobs, 0.1 * size, 0)
// blobs[0].blob
Insert cell
containerPath = new Path2D("M240.438984,75.1180229 C213.906656,13.0479209 138.485199,27.5290591 138.001548,99.5711057 C137.727478,139.139685 173.916719,153.927901 198.010636,169.746443 C221.387137,185.100328 238.004604,206.070503 240.608262,215 C242.841121,206.248285 261.360952,184.667995 282.988245,169.31411 C306.634785,152.521808 343.271403,138.703311 342.997334,99.1387726 C342.485469,26.9189442 265.766213,15.5207045 240.438984,75.1180229 Z");
Insert cell
Insert cell
colorScale = d3.scaleLinear()
.domain([0, 0.25, 0.5, 0.75, 1])
.range(["#79d3cc", "#27ccc1", "#34AA88", "#048072"])
.interpolate(d3.interpolateHcl)
Insert cell
line = d3.lineRadial()
.angle((d) => d.angle)
.radius((d) => d.radius)
.curve(d3.curveBasisClosed)
Insert cell
function bestCircleGenerator(blobs, maxRadius, padding, containerPath, ctx, size) { // size means the size of canvas
var quadtree = d3.quadtree(blobs).extent([[0, 0], [size, size]]),
searchRadius = maxRadius * 2;

return function(k) {
var bestX, bestY, bestDistance = 0;

for (var i = 0; i < k || bestDistance < padding; ++i) {
var x = Math.random() * size;
var y = Math.random() * size;

//TODO Check if point is in the SVG path
if (!ctx.isPointInPath(containerPath, x, y)) {
do {
x = Math.random() * size;
y = Math.random() * size;
} while (!ctx.isPointInPath(containerPath, x, y));
}//if

var rx1 = x - searchRadius,
rx2 = x + searchRadius,
ry1 = y - searchRadius,
ry2 = y + searchRadius,
minDistance = maxRadius; // minimum distance for this candidate

quadtree.visit(function(node, x1, y1, x2, y2) {

if (p = node.data) {
var p,
dx = x - p[0],
dy = y - p[1],
d2 = dx * dx + dy * dy,
r2 = 10;

if (d2 < r2) return minDistance = 0, true; // within a circle
var d = Math.sqrt(d2) - p[2];
if (d < minDistance) minDistance = d;
}
return !minDistance || x1 > rx2 || x2 < rx1 || y1 > ry2 || y2 < ry1; // or outside search radius
});

if (minDistance > bestDistance) bestX = x, bestY = y, bestDistance = minDistance;
}

var best = [bestX, bestY, bestDistance - padding];
quadtree.add(best);
return best;
};
}
Insert cell
Insert cell
d3 = require("d3@5")
Insert cell
Insert cell
Insert cell
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