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

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