Public
Edited
Aug 20, 2023
Insert cell
Insert cell
chartBub = {
const width = 1120;
const height = width * 0.6;
const radius = width * 0.034;
const svg = d3.create("svg").attr("width", width).attr("height", height);

var nodes = d3.range(86).map(function (d, i) {
return {
radius: radius
};
});

var circles = svg
.selectAll(".circles")
.data(nodes)
.enter()
.append("circle")
.attr("class", "message")
.attr("r", (d) => randInt(d.radius - 15, d.radius));

const simulation = d3
.forceSimulation(nodes)
// .force("charge", d3.forceManyBody().strength(-150))
// .force("x", d3.forceX().strength(0.04))
// .force("y", d3.forceY().strength(0.04))
.force("boundary", forceBoundary(100, 100, width - 100, height - 100))
.force(
"collide",
d3
.forceCollide()
.radius((d) => d.radius)
.strength(2)
.iterations(4)
)
.on("tick", ticked);

function ticked() {
circles.attr("transform", (d) => {
return `translate(${d.x}, ${d.y})`;
});
}

return svg.node();
}
Insert cell
chart = {
const n = 10;
const width = 600;
const height = 400;

const center = [width / 2, height / 2];
const thetaOffset = 300;
const radius = 2;
const nodes = phyllotaxis(n, {
radius,
center,
thetaOffset
});

console.log(nodes);
const svg = d3.create("svg").attr("viewBox", [0, 0, width, height]);

const scaleColor = d3
.scaleSequential(d3.interpolateTurbo)
.domain([-100, 4000]);

const node = svg
.append("g")
.selectAll("circle")
.data(nodes)
.join("circle")
.attr("r", radius)
.attr("cx", (d) => d.x)
.attr("cy", (d) => d.y)
.attr("fill", (d, i) => scaleColor(i));

return svg.node();
}
Insert cell
function phyllotaxis(
n,

{ radius = 1, center: [cx, cy] = [0, 0], thetaOffset = 0 } = {}
) {
const lim = 5;
const nodetoKeep = []
thetaOffset;
const length = typeof n === "number" ? n : n.length;

const goldenRatio = (1 + Math.sqrt(5)) / 2;
const theta = (2 * Math.PI) / goldenRatio;

const nodes = Array(length)
.fill(undefined)
.map((_, i) => {
const scaledTheta = theta * i;
const scaledRadius = radius * Math.sqrt(scaledTheta + thetaOffset);

const x = Math.cos(scaledTheta) * scaledRadius + cx;
const y = Math.sin(scaledTheta) * scaledRadius + cy;

return { x, y };
});

for (let i = 0; i < nodes.length; ++i) {
// nodes[i]x,y inside circle??
}
return typeof n === "number"
? nodes
: n.map((datum, i) => ({ datum, ...nodes[i] }));
}
Insert cell
Insert cell
Insert cell
function calculateDistance(point1, point2) {
const dx = point2.x - point1.x;
const dy = point2.y - point1.y;
return Math.sqrt(dx * dx + dy * dy);
}
Insert cell
Type JavaScript, then Shift-Enter. Ctrl-space for more options. Arrow ↑/↓ to switch modes.

Insert cell
Insert cell
Insert cell
//stuff = skrytekassa.map((x) => x[2])
Insert cell
stuff = skrytekassa.map((obj) => `${obj[0]}: ${obj[2]} - ${obj[3]}`)
Insert cell
Insert cell
nodes = Array.from(Object.values(nodesRaw))
Insert cell
linksRaw = {
// Hooks up Skryt with person
const linksRaw = skrytekassa.map((arr) => {
// render name and nick filter here
return {
source: arr[2],
target: arr[0].trim(),
renderName: [arr[0].trim(), arr[2], arr[3]]
};
});
return linksRaw;
}
Insert cell
nodesRaw = {
let nodes = {};
linksRaw.forEach(function (link) {
// Skryt
link.source =
nodes[link.source] ||
(nodes[link.source] = {
name: `${link.renderName[0]}: ${link.renderName[1]} - ${link.renderName[2]}`,
radius: Math.floor(
clampMap(
link.source.length,
nodeLengths.shortest,
nodeLengths.longest,
50,
100,
true
)
),
type: "skryt",
target: link.target
});

// Person
link.target =
nodes[link.target] ||
(nodes[link.target] = {
name: link.target,
type: "person",
radius: 100
});
});
//Math.floor(clampMap(110, 100, 200, 0, 200, true))
return nodes;
}
Insert cell
Insert cell
//skrytekassaRaw = FileAttachment("skryt-2022.tsv").tsv({ array: true })
skrytekassaRaw = FileAttachment("three-months@2.tsv").tsv({ array: true })
Insert cell
skrytekassa = skrytekassaRaw.map((obj) => {
return {
...obj, // Copy all the properties of the original object
name: obj[0].trim() // Trim whitespace from the 'name' property
};
})
Insert cell
Insert cell
linearMap = function (n, start1, stop1, start2, stop2) {
return ((n - start1) / (stop1 - start1)) * (stop2 - start2) + start2;
}
Insert cell
clampMap = (n, a, b, c, d, clamped = false) =>
lerp(c, d, inverseLerp(a, b, n, clamped))
Insert cell
clamp = (a, min, max) => Math.max(Math.min(a, max), min)
Insert cell
lerp = (a, b, amount, clamped = false) => {
const t = !clamped ? amount : clamp(amount, 0, 1);
return a * (1 - t) + b * t;
}
Insert cell
inverseLerp = (a, b, value, clamped = false) => {
let out = (value - a) / (b - a);
return !clamped ? out : clamp(out, 0, 1);
}
Insert cell
Type JavaScript, then Shift-Enter. Ctrl-space for more options. Arrow ↑/↓ to switch modes.

Insert cell
shuffle = (arr) => {
const copy = [...arr]; // create a copy of original array
for (let i = copy.length - 1; i; i--) {
const randomIndex = randInt(i + 1);
[copy[i], copy[randomIndex]] = [copy[randomIndex], copy[i]]; // swap
}
return copy;
}
Insert cell
randInt = (a, b) => ~~random(a, b)
Insert cell
Insert cell
height = 880
Insert cell
Insert cell
import { appendFittedText } from "@zechasault/append-fitted-text-to-circle"
Insert cell
import { select, slider, text as textInput } from "@jashkenas/inputs"
Insert cell
forceBoundary = require("d3-force-boundary")
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