Public
Edited
Sep 1, 2023
Fork of Simple D3
Insert cell
Insert cell
chart = {
const width = 928; // uncomment for responsive width
const height = 600;

const edges = data.edges.map(d => ({...d}));
const nodes = data.nodes.map(d => ({...d}));

const colors = d3.scaleLinear()
.domain([0, 2])
.range(['#000', '#afa']);

const simulation = d3.forceSimulation(nodes)
.force("edge", d3.forceLink(edges).id(d => d.id))
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(width / 2, height / 2))
.on("tick", ticked);
const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height])
.attr("width", width)
.attr("height", height)
.attr("style", "max-width: 100%; height: auto;");

const link = svg.append("g")
.attr("stroke", "#999")
.attr("stroke-opacity", 0.6)
.selectAll()
.data(edges)
.join("line")
.attr("stroke-width", d => Math.sqrt(d.value));

const node = svg.append("g")
.attr("stroke", "#fff")
.attr("stroke-width", 1.5)
.selectAll()
.data(nodes)
.join("circle")
.attr("r", 5)
.attr("fill", d => colors(d.walkTimes.length))
// svg
// .selectAll("circle")
// .data(d3.range(18))
// .join("circle")
// .attr("cx", d => 30 + d * 50)
// .attr("cy", height / 2)
// .attr("r", d => Math.random() * 20)
// .attr("fill", "#001b42");

function ticked() {
link
.attr("x1", d => d.source.x)
.attr("y1", d => d.source.y)
.attr("x2", d => d.target.x)
.attr("y2", d => d.target.y);

node
.attr("cx", d => d.x)
.attr("cy", d => d.y);
};
return svg.node();
}
Insert cell
function pick(items, n) {
return d3.shuffle(items).slice(0, Math.min(items.length, n));
}
Insert cell
data = {
let g = generateGraph(20, {linkMean: 1.2});
let walk = randomWalk(g, 0, 4);
return applyWalk(g, walk);
}
Insert cell
function generateNodes(numNodes, opts) {
opts = opts || { linkMean: 1 };
let linkMean = opts.linkMean || 1;
let indices = d3.range(numNodes);

let nodes = [];

for (let i = 0; i < numNodes; i++) {
let N = d3.randomPoisson(linkMean)();
let neighbors = pick(indices, N);

nodes.push({
id: i,
neighbors: neighbors,
walkTimes: []
});
}

return nodes;
}
Insert cell
function buildEdgeList(nodes) {
return nodes.flatMap((node) => node.neighbors.map((otherId) => ({
source: node.id,
target: otherId,
value: 1
})));
}
Insert cell
function randomWalk(graph, start, maxLength) {

const {nodes, edges} = graph;

let visits = [start];
for (let i = 0; i < maxLength; i++) {
const currentNode = nodes[start];
if (currentNode.neighbors.length === 0) {
return visits
}
const nextNode = pick(currentNode.neighbors, 1)[0];
visits.push([start, nextNode]);
start = nextNode;
}

return visits;
}
Insert cell
// 'abcdefg'.split('').filter()
Insert cell
function applyWalk(graph, walk) {
const {nodes, edges} = graph;
const newNodes = nodes.map((el, nodeId) => ({
...el,
walkTimes: walk
.map((walkId, stepNum) => walkId[0] === nodeId ? stepNum : -1)
.filter(x => x > 0)
}));

const newEdges = edges.map((edge,

return { nodes: newNodes, edges };
}
Insert cell
{
let g = generateGraph(10, {linkMean: 2});
let walk = randomWalk(g, 0, 4);
return applyWalk(g, walk);
}
Insert cell
function generateGraph(numNodes, opts) {
//let words = ["Beer", "Pizza", "Coffee"];
const nodes = generateNodes(numNodes, opts);
const edges = buildEdgeList(nodes);
return {
nodes,
edges
};
};
Insert cell
Insert cell
Insert cell
Insert cell
{
let height = 600;
let width = 900;
var svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height])
.attr("width", height)
.attr("height", width);

let color = d3.scaleOrdinal(d3.schemeCategory10);


var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) { return d.id; }))
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(width / 2, height / 2));

d3.json("https://gist.githubusercontent.com/mbostock/4062045/raw/5916d145c8c048a6e3086915a6be464467391c62/miserables.json", function(error, graph) {
if (error) throw error;

var link = svg.append("g")
.attr("class", "links")
.selectAll("line")
.data(graph.links)
.enter().append("line")
.attr("stroke-width", function(d) { return Math.sqrt(d.value); })
.style('stroke', '#999')
.style('stroke-opacity', '0.6')
.style('opacity', '0.5')
.style('stroke-dasharray', '10, 4');

var node = svg.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(graph.nodes)
.enter().append("circle")
.attr("r", 5)
.attr("fill", function(d) { return '#abc'; })
.style('stroke', '#fff')
.style('stroke-width', '1.5px')
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));

node.append("title")
.text(function(d) { return d.id; });

simulation
.nodes(graph.nodes)
.on("tick", ticked);

simulation.force("link")
.links(graph.links);

var lines = d3.selectAll('line');

var offset = 1;
setInterval(function() {
lines.style('stroke-dashoffset', offset);
offset += 1;
}, 50);

function ticked() {
link
.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });

node
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
}
});

function dragstarted(d) {
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}

function dragged(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
}

function dragended(d) {
if (!d3.event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}

return svg.node()
}
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