Public
Edited
Sep 12, 2023
Fork of Try 1
Insert cell
Insert cell
data = FileAttachment("generic_n_and_e.json").json()
Insert cell
images = {
let zipped_images = await FileAttachment("demo_icons.zip").zip();

// Select just the SVG files
let filenames = zipped_images.filenames.filter((s) => s.slice(-4) == ".svg");
// Extract the text of the SVG files
let images = await Promise.all(
filenames.map((f) => zipped_images.file(f).text())
);
// Slice off the XML header
images = images.map((s) => s.slice(39));
// Grab the filenames with directory header sliced off.
let names = filenames.map((s) => s.slice(6));
// Return Map for easy retrieval
return new Map(d3.zip(names, images));
}
Insert cell
Insert cell
viewof distance = Inputs.range([0, 1000], {label: "Distance", step: 1})
Insert cell
viewof radius = Inputs.range([0, 100], {label: "Radius", step: 1})
Insert cell
viewof shape = Inputs.select(["Rnd", "Norm", "Rect"], {label: "Icon Shape"})
Insert cell
{
const my_button = "cons/rect-"
const width = 1600
const height = 1000
// The force simulation mutates links and nodes, so create a copy
// so that re-evaluating this cell produces the same result.
const links = data.edges.map(d => ({...d}));
const nodes = data.nodes.map(d => ({...d}));

// Create a simulation with several forces.
const simulation = d3.forceSimulation(nodes)
.force("link", d3.forceLink(links).id(d => d.id).distance(d=>distance))
.force("charge", d3.forceManyBody())
.force("collide", d3.forceCollide(1.2*radius))
.force("center", d3.forceCenter(width / 2, height / 2))
.on("tick", ticked);

// Create the SVG container.
const svg = d3.create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [0, 0, width, height])
.attr("style", "max-width: 100%; height: auto;");
// .call(d3.zoom().on("zoom", function () {svg.attr("transform", d3.event.transform)}));

//appending little triangles, path object, as arrowhead
//The <defs> element is used to store graphical objects that will be used at a later time
//The <marker> element defines the graphic that is to be used for drawing arrowheads or polymarkers on a given <path>, <line>, <polyline> or <polygon> element.
svg.append('defs').append('marker')
.attr("id",'arrowhead')
.attr('viewBox','-0 -5 10 10') //the bound of the SVG viewport for the current SVG fragment. defines a coordinate system 10 wide and 10 high starting on (0,-5)
.attr('refX',radius) // x coordinate for the reference point of the marker. If circle is bigger, this need to be bigger.
.attr('refY',0)
.attr('orient','auto')
.attr('markerWidth',13)
.attr('markerHeight',13)
.attr('xoverflow','visible')
.append('svg:path')
.attr('d', 'M 0,-5 L 10 ,0 L 0,5')
.attr('fill', '#999')
.style('stroke','none');

// setup group for all graph objects
const g = svg.append('g'). attr('class', 'graph');

// Add a line for each link, and a circle for each node.
const link = g.append("g")
.attr("stroke", "#999")
.attr("stroke-opacity", 0.9)
.selectAll("line")
.data(links)
.join("line")
.attr("stroke-width", '1px').attr('marker-end','url(#arrowhead)') //The marker-end attribute defines the arrowhead or polymarker that will be drawn at the final vertex of the given shape.


//The <title> element provides an accessible, short-text description of any SVG container element or graphics element.
//Text in a <title> element is not rendered as part of the graphic, but browsers usually display it as a tooltip.
link.append("title")
.text(d => d.label);

const node = g.append((d) => svg`${images.get(icon_to_image.get(d.label))}`)
.attr("x", -radius)
.attr("y", -radius)
.attr("width", 2*radius)
.attr("height", 2*radius);

node.append("title")
.text(d => d.id);

node.append("text")
.text(function(d) {
return d.icon + ": " + d.label;
})
.style('fill', '#000')
.style('font-size', '12px')
.attr('x', 16)
.attr('y', 13);


// Set the position attributes of links and nodes each time the simulation ticks.
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);
}

// When this cell is re-run, stop the previous simulation. (This doesn’t
// really matter since the target alpha is zero and the simulation will
// stop naturally, but it’s a good practice.)
invalidation.then(() => simulation.stop());

let zoomLvl = 1;
let lastK = 0;

svg.call(d3.zoom()
.extent([[0, 0], [width, height]])
.scaleExtent([1, 80])
.on("zoom", zoomed));

function zoomed() {
let e = d3.event
if(e.transform.k > 2 && lastK != e.transform.k){
lastK = e.transform.k;
console.log("zoomed");
zoomLvl =Math.log2(e.transform.k);
g.attr("stroke-width", 1.5/zoomLvl );
link.attr("stroke-width", d => Math.sqrt(d.value)/(zoomLvl));
}
g.attr("transform", e.transform);
}

return svg.node();
}
Insert cell
d3 = require("d3@5")
Insert cell
drag = simulation => {
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 d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended);
}
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