Public
Edited
Oct 5, 2023
7 forks
16 stars
Insert cell
Insert cell
Insert cell
tokenData = ({
hash: '0xcba6cb67751411cd66183e7a262b9d5281712b33804ec743de23332eefb0983b',
// hash: '0x3bdc047430b61c5df747c724fd645a79bf616a8156aa26338aae6b50ba4a4aeb',
// hash: '0xa74ea8042dfdff44f5e1bb9596dd782aa73561b971cbf9a9f4197db1c1c2dc6e',
})
Insert cell
p5(sketch => {
sketch.setup = function setup() {
sketch.createCanvas(width, width);
sketch.noLoop();
};

sketch.draw = function draw() {
initSeed();
const strokeWeight = (ps.strokeWeight * width) / 800,
background = ps.bgHl
? ps.useSecBg
? ps.secCol
: ps.hlCol
: ps.useSubtleBg
? ps.subtleBgcol
: ps.bgcol;
sketch.background(background);
const padding = width * ps.padding * 0.08,
innerWidth = width - 2 * padding,
innerHeight = sketch.height - 2 * padding,
gridStep = innerWidth / ps.gridD,
allPts = [];
if (ps.pAlg === 0) {
pOnGrid(allPts, padding, gridStep);
} else {
pOnRGrid(allPts, padding, padding, innerWidth, innerHeight);
}
if (ps.vRad !== 0) {
allPts.forEach(pt => {
const distToCenter = distance(width / 2, sketch.height / 2, pt.cx, pt.cy);
pt.r *=
rng(0.8, 1) *
(ps.vRad === 1
? 1 / (1 + distToCenter / (width / 5))
: (1 + distToCenter + gridStep) / distance(width / 2, sketch.height / 2, 0, 0));
});
}
const {samples: samplePts, leftOver: leftOverPts} = sampleSize(
allPts,
parseInt(ps.sampleRate * allPts.length),
),
ring = new Ring(samplePts, sketch);
ring.generate(
ps.wrapped,
ps.forceCentroid ? [rng(0, 2 * gridStep), rng(sketch.height / 2, sketch.height)] : false,
);
sketch.noStroke();
const ringFill = ps.fill
? ps.fHl
? ps.useSecForFill
? ps.secCol
: ps.hlCol
: ps.sCol
: ps.bgcol;
sketch.fill(ringFill);
ring.draw();
sketch.stroke(ps.sCol);
sketch.strokeWeight(strokeWeight);
sketch.noFill();
ring.draw();
const highlightPtNum = rngFloor(0, samplePts.length);
samplePts.forEach((pt, curPtNum) => {
const ptBaseFill = ps.fCCol
? pt.isConcave
? ps.bgcol
: ps.sCol
: pt.isConcave
? ps.sCol
: ps.bgcol,
isCurHighlighted = curPtNum === highlightPtNum,
ptFinalFill = isCurHighlighted ? ps.hlCol : ptBaseFill;
sketch.stroke(ps.sCol);
sketch.fill(ptFinalFill);
let radius = 2 * pt.r;
if (ps.shrinkConcavePegs && pt.isConcave && pt.r >= 2 * strokeWeight) {
radius *= 0.4;
}
sketch.circle(pt.cx, pt.cy, radius);
if (ps.concentric) {
sketch.noStroke();
let ringSize,
ringNum = 0,
inverse = ptBaseFill == ps.sCol && !isCurHighlighted;
for (
ringSize = ps.pAlg === 0 ? (curPtNum % 2 == 0 ? 4 : 2) : pt.id % 2 != 0 ? 4 : 2;
radius > strokeWeight && radius - ringSize * strokeWeight > strokeWeight;

) {
radius -= ringSize * strokeWeight;
const brightColor = isCurHighlighted ? ps.hlCol : ps.bgcol;
sketch.fill(
ringNum % 2 == 0 ? (inverse ? brightColor : ps.sCol) : inverse ? ps.sCol : brightColor,
);
sketch.circle(pt.cx, pt.cy, radius);
ringNum++;
}
}
});
if (ps.drawAllPoints) {
const highlightPtNum = rngFloor(0, leftOverPts.length);
leftOverPts.forEach((pt, curPtNum) => {
const hightlightCurPt = curPtNum === highlightPtNum && ps.useSec;
sketch.stroke(
hightlightCurPt && ps.secCol != background && ps.secCol != ringFill ? ps.secCol : ps.sCol,
);
sketch.fill(hightlightCurPt ? ps.secCol : ps.fCCol ? ps.sCol : ps.bgcol);
sketch.circle(pt.cx, pt.cy, 2 * pt.r);
});
}
};
});

Insert cell
a = ["length","827hDDFpO","atan2","395NGXjRB","vRad","hash","fHl","fill","slice","EPSILON","radius","gridD","useSubtleBg","shrinkConcavePegs","oGrSt","sampleRate","238178ybuAjl","start","max","drawAllPoints","oGr","86044danzRE","map","points","7382BnPcfc","end","13rXxsQO","push","strokeWeight","#4381c1","innerWidth","#3b9764","sqrt","sCol","min","156675tJbZva","573caxaaB","useSecBg","forEach","draw","connects","sin","fCCol","getTangents","hlCol","pAlg","98HRQUmz","isConcave","abs","#f2c945","arcs","floor","concentric","bgcol","1OOKojP","secCol","sweep","sort","#c3423f","bgHl","padding","306860tRKCKq"]
Insert cell
rpl = code => code.replace(/\[[a-z]\(([0-9]{3})\)\]/g, function(_, num) {
return '.' + a[num - 128];
})
Insert cell
function N(t) {
return a[t - 128];
}
Insert cell
N(157)
Insert cell
rp = setupPs(tokenData)
Insert cell
seed = ({v: genSeed(tokenData)})
Insert cell
initSeed = () => seed.v = genSeed(tokenData)
Insert cell
function setupPs(t) {
let s = [];
for (let t = 0; t < 32; t++) {
s.push(tokenData.hash.slice(2 + 2 * t, 4 + 2 * t))
};
return s.map(t => parseInt(t, 16));
}
Insert cell
function genSeed(t) {
return parseInt(tokenData.hash.slice(0, 16), 16);
}
Insert cell
function rnd() {
seed.v ^= seed.v << 13;
seed.v ^= seed.v >> 17;
seed.v ^= seed.v << 5;
return ((seed.v < 0 ? 1 + ~seed.v : seed.v) % 1e3) / 1e3;
}
Insert cell
function rng(t, e) {
return rnd() * (e - t) + t;
}
Insert cell
function rngFloor(t, e) {
return Math.floor(rng(t, e));
}
Insert cell
function shuffleA(arr) {
const resArr = arr.slice();
let idx1 = arr.length;
while (idx1) {
const idx2 = Math.floor(rnd() * idx1--);
const temp = resArr[idx1];
resArr[idx1] = resArr[idx2];
resArr[idx2] = temp;
}
return resArr;
}
Insert cell
function distance(t, e, s, r) {
return Math.sqrt((s - t) * (s - t) + (r - e) * (r - e));
}
Insert cell
function sampleSize(t, e) {
let r = shuffleA(t);
return {samples: r.slice(0, e), leftOver: r.slice(e)};
}
Insert cell
function mapd(t, e, s, r, n) {
return ((t - e) / (s - e)) * (n - r) + r;
}
Insert cell
function mapP(t, e, s) {
return mapd(t, 0, 255, e, s);
}
Insert cell
ps = ({
gridD: parseInt(mapP(rp[0], 3, 6)),
radius: mapP(rp[1], 0.5, 0.8),
sampleRate: mapP(rp[2], 0.5, 0.8),
wrapped: rp[3] < 127,
drawAllPoints: rp[4] < 127,
forceCentroid: rp[5] > 200,
fill: rp[6] < 127,
pAlg: rp[7] < 220 ? 0 : 1,
vRad: rp[8] > 200 ? (rp[8] > 227 ? 1 : 2) : 0,
bgHl: rp[9] > 220,
fHl: rp[10] > 220,
useSec: rp[11] < 75,
useSecBg: rp[12] > 170,
concentric:
rp[13] <= 13 ||
(rp[13] > 108 && rp[13] <= 110) ||
69 == rp[13] ||
33 == rp[13] ||
43 == rp[13],
fCCol: rp[14] > 200 && (rp[6] >= 127 || rp[10] > 220),
bgcol: rp[22] < 250 ? '#f5f5f5' : '#2b2b2b',
subtleBgcol: '#f7f7e6',
sCol: rp[22] < 250 ? '#2b2b2b' : '#f5f5f5',
hlCol: rp[22] < 250 ? N(177) : '#2b2b2b',
secCol:
rp[15] >= 19 && rp[15] <= 230 ? N(186) : rp[15] > 230 && rp[15] <= 234 ? N(159) : N(157),
useSecForFill: rp[16] <= 85,
strokeWeight: 8,
padding: rp[17] > 14 ? 1 : 2.66,
shrinkConcavePegs: rp[18] >= 245,
useSubtleBg: rp[19] >= 245,
oGr: rp[7] < 220 && rp[20] > 205,
oGrSt: rp[21] > 127,
})
Insert cell
function pOnGrid(pts, padding, gridStep) {
for (let xi = 0; xi < ps.gridD; xi++) {
const chessMode = ps.oGr && (ps.oGrSt ? xi % 2 !== 0 : xi % 2 === 0) ? 1 : 0;
for (let yi = 0; yi < ps.gridD - chessMode; yi++) {
const x = padding + (xi + 0.5) * gridStep,
y = padding + (yi + 0.5 + chessMode / 2) * gridStep,
r = (gridStep / 2) * ps.radius;
pts.push({cx: x, cy: y, r: r, i: xi, j: yi, id: pts.length});
}
}
}
Insert cell
function pOnRGrid(pts, padX, padY, width, height, recursionLevel = 1) {
const split = parseInt(rng(2, 4)),
subWidth = width / split,
subHeight = height / split;
for (let curPadX = padX; curPadX < padX + width - 1; curPadX += subWidth) {
for (let curPadY = padY; curPadY < padY + height - 1; curPadY += subHeight) {
if (rnd() < 0.5 && recursionLevel < 2) {
pOnRGrid(pts, curPadX, curPadY, subWidth, subHeight, recursionLevel + 1);
} else {
const radius = (ps.radius * Math.min(subWidth, subHeight)) / 2;
pts.push({r: radius, cx: curPadX + subWidth / 2, cy: curPadY + subHeight / 2, id: pts.length});
}
}
}
}
Insert cell
class Ring {
constructor(points, sketch) {
this.sketch = sketch;
this.points = points;
this.arcs = null;
this.connects = null;
}

draw() {
const PI2 = 2 * Math.PI;
this.sketch.beginShape();
this.sketch.vertex(this.arcs[0].start[0], this.arcs[0].start[1]);
for (let arcNum = 0; arcNum < this.arcs.length; arcNum++) {
const curArc = this.arcs[arcNum],
nextConnect = this.connects[arcNum],
angle = Math.atan2(curArc.start[1] - curArc.cy, curArc.start[0] - curArc.cx);
let angleDelta = Math.atan2(curArc.end[1] - curArc.cy, curArc.end[0] - curArc.cx) - angle;
const isSameAngle = Math.abs(angleDelta) < Number.EPSILON;
while (angleDelta < 0) angleDelta += PI2;
while (angleDelta > PI2) angleDelta -= PI2;
if (angleDelta < Number.EPSILON) {
angleDelta = isSameAngle ? 0 : PI2;
}
if (!curArc.sweep && !isSameAngle) {
if (angleDelta === PI2) {
angleDelta = -PI2;
} else {
angleDelta -= PI2;
}
}
if (!isSameAngle) {
let angleCount = (100 * width) / 800;
for (let i = 0; i < angleCount; i += 1) {
let curAngle = angle + (angleDelta * i) / angleCount,
x = curArc.cx + curArc.r * Math.cos(curAngle),
y = curArc.cy + curArc.r * Math.sin(curAngle);
this.sketch.vertex(x, y);
}
}
this.sketch.vertex(nextConnect[2], nextConnect[3]);
}
this.sketch.endShape();
}

generate(isWrapped, forceCentroid) {
this.connects = [];
this.arcs = [];
let centroid;
if (forceCentroid) {
centroid = [forceCentroid[0], forceCentroid[1]];
} else {
centroid = [0, 0];
this.points.forEach(t => {
(centroid[0] += t.cx), (centroid[1] += t.cy);
});
centroid = [centroid[0] / this.points.length, centroid[1] / this.points.length];
}
this.points.sort((pt1, pt2) => {
const angleDelta =
(Math.atan2(pt1.cy - centroid[1], pt1.cx - centroid[0]) -
Math.atan2(pt2.cy - centroid[1], pt2.cx - centroid[0])) %
(2 * Math.PI),
lengthDelta = -(
distance(pt1.cx, pt1.cy, centroid[0], centroid[1]) -
distance(pt2.cx, pt2.cy, centroid[0], centroid[1])
),
sameAngle = Math.abs(angleDelta) < 1e-4,
sameLen = Math.abs(lengthDelta) < 1e-4;
return sameAngle && sameLen ? pt1.id - pt2.id : sameAngle ? lengthDelta : angleDelta;
});
for (let ptNum = 0; ptNum < this.points.length && this.points.length > 1; ptNum += 1) {
const prevPt = this.points[ptNum - 1 < 0 ? this.points.length - 1 : ptNum - 1],
curPt = this.points[ptNum];
curPt.sortedOrder = ptNum;
const nextPt = this.points[(ptNum + 1) % this.points.length],
dxp = prevPt.cx - curPt.cx,
dyp = prevPt.cy - curPt.cy,
dxn = nextPt.cx - curPt.cx,
dyn = nextPt.cy - curPt.cy,
angle = Math.atan2(dxp * dyn - dyp * dxn, dxp * dxn + dyp * dyn);
curPt.isConcave =
(angle < 0 && Math.abs(angle) <= Math.PI) || Math.abs(Math.abs(angle) - Math.PI) < 1e-4;
}
for (let ptNum = 0; ptNum < this.points.length && this.points.length > 1; ptNum += 1) {
const curPt = this.points[ptNum],
nextPt = this.points[(ptNum + 1) % this.points.length],
tangents = this.getTangents(curPt.cx, curPt.cy, curPt.r, nextPt.cx, nextPt.cy, nextPt.r);
isWrapped
? curPt.isConcave && nextPt.isConcave
? this.connects.push(tangents[1])
: curPt.isConcave && !nextPt.isConcave
? this.connects.push(tangents[3])
: !curPt.isConcave && nextPt.isConcave
? this.connects.push(tangents[2])
: this.connects.push(tangents[0])
: this.connects.push(tangents[1]);
}
for (let ptNum = 0; ptNum < this.points.length && this.points.length > 1; ptNum += 1) {
const prevPtNum = ptNum - 1 < 0 ? this.points.length - 1 : ptNum - 1,
curPt = this.points[ptNum],
nextConnect = this.connects[ptNum],
prevConnect = this.connects[prevPtNum],
pc1 = [prevConnect[2], prevConnect[3]],
nc0 = [nextConnect[0], nextConnect[1]];
if (isWrapped) {
const pc0 = [prevConnect[0], prevConnect[1]],
nc1 = [nextConnect[2], nextConnect[3]],
dxp = pc1[0] - pc0[0],
dyp = pc1[1] - pc0[1],
dxn = nc0[0] - nc1[0],
dyn = nc0[1] - nc1[1],
angle = Math.atan2(dxp * dyn - dyp * dxn, dxp * dxn + dyp * dyn),
isLargeArc =
Math.abs(angle) < Math.PI / 2 &&
((angle < 0 && !curPt.isConcave) || (angle > 0 && curPt.isConcave))
? 1
: 0;
this.arcs.push({
start: pc1,
cx: curPt.cx,
cy: curPt.cy,
r: curPt.r,
largeArc: isLargeArc,
sweep: curPt.isConcave ? 1 : 0,
end: nc0,
display: Math.abs(angle) < Math.PI / 1.2,
});
} else {
this.arcs.push({
start: pc1,
cx: curPt.cx,
cy: curPt.cy,
r: curPt.r,
largeArc: curPt.isConcave ? 0 : 1,
sweep: 1,
end: nc0,
display: true,
});
}
}
}

getTangents(cx0, cy0, r0, cx1, cy1, r1) {
const distSq = (cx0 - cx1) * (cx0 - cx1) + (cy0 - cy1) * (cy0 - cy1);
if (distSq <= (r0 - r1) * (r0 - r1)) return [];
const dist = Math.sqrt(distSq),
cos = (cx1 - cx0) / dist,
sin = (cy1 - cy0) / dist,
res = Array(4)
.fill(0)
.map(t => Array(4).fill(0));
let resLen = 0;
for (let sign1 = 1; sign1 >= -1; sign1 -= 2) {
const d = (r0 - sign1 * r1) / dist;
if (d * d > 1) continue;
const dd = Math.sqrt(Math.max(0, 1 - d * d));
for (let sign2 = 1; sign2 >= -1; sign2 -= 2) {
const a = cos * d - sign2 * dd * sin,
b = sin * d + sign2 * dd * cos,
resItem = res[resLen++];
resItem[0] = cx0 + r0 * a;
resItem[1] = cy0 + r0 * b;
resItem[2] = cx1 + sign1 * r1 * a;
resItem[3] = cy1 + sign1 * r1 * b;
}
}
return res.slice(0, resLen);
}
}
Insert cell
import {p5, ParticleSystem} from "@tmcw/p5"
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