Public
Edited
Jun 14, 2023
Insert cell
Insert cell
Insert cell
chart = {
const svg = d3
.create("svg")
.attr("viewBox", [-width / 2, -height / 2, width, height]);

const mid = {
x: 0,
y: 0
};

if (phase < 2) {
const gt = svg
.append("text")
.text("Sikt")
.attr("text-anchor", "middle")
.attr("font-size", "30px")
.attr("font-family", "sans-serif")
.attr("fill", "#160363")
.attr("y", -radius - 10);
}

const go = svg
.append("circle")
.attr("r", radius + teamRadius * 2)
.attr("cx", mid.x)
.attr("cy", mid.y)
.attr("fill", "none")
.attr("stroke-width", "3px")
.attr("stroke", "#7351fb");
// Animation loop
function animateCircle() {
// Calculate the opacity based on the sine curve
var opacity = 0.1 + (0.9 * (Math.sin(Date.now() / 400) + 1)) / 2; // Opacity ranges from 0 to 1
// Update the stroke opacity
go.style("stroke-opacity", opacity);

// Request the next animation frame
requestAnimationFrame(animateCircle);
}
// Start the animation
animateCircle();

if (phase === 0) {
const gsources = svg.append("g");
let fromg = gsources.selectAll().data(from).join("g");
fromg
.append("text")
.text((d) => d.label)
.attr("text-anchor", "middle")
.attr("font-size", "20px")
.attr("font-family", "sans-serif")
.attr("fill", (d) => colorFunc(d.color))
.attr("x", (d) => d.x)
.attr("y", (d) => d.y - 10);
fromg
.append("circle")
.attr("r", 6)
.attr("stroke", (d) => colorFunc(d.color))
.attr("fill", (d) => colorFunc(d.color))
.attr("cx", (d) => d.x)
.attr("cy", (d) => d.y);
}

// Arcs som viser støtteteam
if (phase >= 2) {
let fg = siktScheme.map((d) => d.color);
let bg = siktScheme.map((d) => d.font);
// Set up arc generator
const arc = d3
.arc()
.innerRadius(radius + teamRadius * 2 - 70)
.outerRadius(radius + teamRadius * 2 - 10);

const arcs = svg
.selectAll("g.arc")
.data(supportData)
.enter()
.append("g")
.attr("class", "arc")
.attr("transform", "translate(0, 0)"); // Adjust this to move the whole group of arcs

// Append the arcs
let aaaa = arcs
.append("path")
.attr("d", arc)
.attr("fill", (d, i) => fg[i])
.attr("id", function (d, i) {
return "arc" + i; // Unique id for each arc for textPath reference
});

// Append labels to arcs
arcs
.append("text")
.attr("dy", "40px") // Adjust this to move text up/down
.attr("text-anchor", "middle")
.attr("font-size", "28px")
.attr("fill", (d, i) => bg[i])
.attr("font-family", "sans-serif")
.append("textPath")
.attr("xlink:href", function (d, i) {
return "#arc" + i; // Reference to arc ids
})
.attr("side", (d) => (d.mid > Math.PI ? "right" : "left"))
.attr("startOffset", "22.5%") // Center the text
.text((d) => d.label);

function pulse() {
// Current time
let t = Date.now();

aaaa
.transition()
.duration(1000)
.ease(d3.easeLinear)
.attr("fill-opacity", function (d, i) {
// Create a pulsing effect by adjusting the opacity with a sine function
// The +1 shifts the range of Math.sin (which is -1 to 1) to the range 0 to 2
// Dividing by 2 then shifts the range to 0 to 1, which is suitable for opacity
// The division by 1000 slows down the pulse rate
return 0.7 + 0.3 * ((Math.sin((-t - i * 400) / 800) + 1) / 2);
})
.on("end", pulse); // Call the pulse function again when the transition ends
}

// Start the pulse
pulse();
}

if (phase === 4 || phase === 5) {
let subdata = phase === 4 ? fagSub : platonSub;
const arcSub = d3
.arc()
.innerRadius(radius + teamRadius * 2 - 90)
.outerRadius(radius + teamRadius * 2 - 70);

const arcs = svg
.selectAll("g.arcsub")
.data(subdata)
.enter()
.append("g")
.attr("class", "arcsub")
.attr("transform", "translate(0, 0)"); // Adjust this to move the whole group of arcs

// Append the arcs
arcs
.append("path")
.attr("d", arcSub)
.attr("fill", (d, i) => "none")
.attr("stroke", "#aaaaaa")
.attr("id", function (d, i) {
return "arcsub" + i; // Unique id for each arc for textPath reference
});

// Append labels to arcs
arcs
.append("text")
.attr("dy", "15px") // Adjust this to move text up/down
.attr("text-anchor", "middle")
.attr("font-size", "12px")
.attr("fill", "#666666")
.attr("font-family", "sans-serif")
.append("textPath")
.attr("xlink:href", function (d, i) {
return "#arcsub" + i; // Reference to arc ids
})
//.attr("side", (d) => (d.mid > Math.PI ? "right" : "left"))
.attr("startOffset", "22.5%") // Center the text
.text((d) => d.label);
}

const g = svg.append("g");
simulation.on("tick", () => {
g.selectAll("circle:not(.exit)")
.data(simulation.nodes(), (d) => d.id)
.join(
(enter) =>
enter
.append("circle")
.attr("fill", (d) => colorFunc(d.id))
.attr("stroke", (d) => colorFunc(d.id - 0.06))
.attr("stroke-width", 2)
.attr("r", 1)
.transition()
.duration(2000)
.attr("r", (d) => d.radius - 5)
.selection(),
(update) =>
update
.attr("cx", (d) => d.x)
.attr("cy", (d) => d.y)
.attr("r", (d) => d.radius - 5),
(exit) =>
exit
.classed("exit", true)
.transition()
.duration(2000)
.attr("fill", "#eee")
.attr("r", 1)
.attr("opacity", 0)
.remove()
);
});

return svg.node();
}
Insert cell
colorFunc = {
if (phase === 0) {
return d3.interpolateSpectral;
}
return d3.interpolateViridis;
}
// d3.interpolateSinebow
// d3.interpolateSpectral
// d3.interpolateViridis
Insert cell
Insert cell
from = [
{ label: "Uninett", color: 0, x: -width / 3, y: -height / 3 },
{ label: "Unit", color: 0.4, x: -width / 3, y: height / 3 },
{ label: "NSD", color: 0.8, x: width / 3, y: 0 }
]
Insert cell
Insert cell
supportData = {
let x = [];

let data = [
{ label: "Platon", weight: 10 },
{ label: "Datadrevet Sikt", weight: 5 },
{ label: "Designlaben", weight: 6 },
{ label: "Seksjon / fagnettverk", weight: 10 },
{ label: "Designsystem", weight: 5 }
];
if (phase === 3) {
data.push({ label: "AI Lab", weight: 3 });
}
let total = d3.sum(data, (d) => d.weight);
let each = (Math.PI * 2) / total;
let startAt = -0.3;
for (let i in data) {
let p = each * data[i].weight;
let endsAt = startAt + p;
let mid = startAt + p / 2;
x.push({ ...data[i], startAngle: startAt, endAngle: endsAt, mid: mid });
startAt = endsAt;
}

return x;
}
Insert cell
platonSub = getSupportSubData(0, ["Managed VM", "PaaS", "AWS konto"])
Insert cell
fagSub = getSupportSubData(3, ["UU", "Frontend", "Testing", "Maskinlæring"])
Insert cell
getSupportSubData = (parent, inputs) => {
let x = [];
let from = supportData[parent].startAngle;
let to = supportData[parent].endAngle;
let no = inputs.length;
let diff = (to - from) / no;

for (let i in inputs) {
let f = from + diff * i;
let m = f + diff / 2;
let t = f + diff;
x.push({
label: inputs[i],
startAngle: f,
mid: m,
endAngle: t
});
}
return x;
}
Insert cell
siktScheme = [
{
label: "blå - color.surface.info-active",
color: "#0058e6",
font: "#cce0ff"
},
{
label: "gul - olor.surface.warning-active",
color: "#e6a800",
font: "#ffebb3"
},
{
label: "rosa - color.surface.danger-active",
color: "#b30062",
font: "#ffcce8"
},
{
label: "grå - color.text.neutral",
color: "#666666",
font: "#f2f2f2"
},
{
label: "grønn - color.surface.success-active",
color: "#095d41",
font: "#b9f8e3"
},
{
label: "balack white",
color: "#f6f6f6",
font: "#333"
},
{
label: "lilla - color.surface.action-active",
color: "#5f38fa",
font: "#ebe6fe"
}
]
Insert cell
Insert cell
simulation = d3
.forceSimulation()
.force(
"collide",
d3.forceCollide().radius((d) => d.radius)
)
.force("manyBody", d3.forceManyBody().strength(-10))
.force("center", d3.forceCenter(0, 0).strength(0.01))
.force("x", d3.forceX(0))
.force("y", d3.forceY(0))
.alpha(0.1)
.alphaDecay(0)
.alphaMin(0.01)
Insert cell
nodes = {
let noTeamsBase = 32;

const amplitude = 3; // peak value
const period = 25; // period of the wave
let time = 0;
const nodes = [];

let initPos = (id) => {
if (phase === 0) {
let src = Math.floor(id * 3);
return [from[src].x + 5 * (Math.random() - 0.5), from[src].y];
}
return d3.pointRadial(2 * Math.PI * Math.random(), radius);
};

while (true) {
time++;
let noTeams =
noTeamsBase +
Math.round(amplitude * Math.sin((2 * Math.PI * time) / period));
let node = { id: Math.random() };
const [x, y] = initPos(node.id);
node.x = x;
node.y = y;
node.radius = teamRadius + Math.round(10 - Math.random() * 15);
nodes.push(node);

if (nodes.length > noTeams)
nodes.splice(Math.random() * nodes.length, nodes.length - noTeams);

for (let i in nodes) {
nodes[i].radius = updateRadius(nodes[i].radius);
}

simulation.nodes(nodes);
//yield noTeams;
yield nodes;
let pause = 300;
if (phase === 0) {
pause = 1300;
} else {
pause = 100;
if (nodes.length >= noTeams) pause = 2000;
}

await Promises.tick(pause);
}
}
Insert cell
updateRadius = (r) => {
if (r < 5) {
return r + Math.random();
} else if (r > 25) {
return r - 2 * Math.random();
} else {
return r + 2 * Math.random() - 1;
}
return r;
}
Insert cell
Insert cell
teamRadius = 32
Insert cell
radius = 200
Insert cell
height = 800
Insert cell
d3 = require("d3@6")
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