Public
Edited
Sep 27, 2023
Insert cell
Insert cell
Type JavaScript, then Shift-Enter. Ctrl-space for more options. Arrow ↑/↓ to switch modes.

Insert cell
graph = ({
maxLength: 3,
nodes: [
{
id: 0,
rBounding: 40,
weight: 10,
isSelected: false,
content: [0, 1, 2],
},
{
id: 1,
rBounding: 40,
weight: 10,
isSelected: false,
content: [0, 2],
},
{
id: 2,
rBounding: 40,
weight: 10,
isSelected: false,
content: [2, 0],
},
{
id: 3,
rBounding: 40,
weight: 10,
isSelected: false,
content: [0, 1, 2],
},
{ id: 4, rBounding: 40, weight: 10, isSelected: false, content: [0] },
{ id: 5, rBounding: 40, weight: 10, isSelected: false, content: [1] },
{
id: 6,
rBounding: 40,
weight: 10,
isSelected: false,
content: [0, 1, 2],
},
{ id: 7, rBounding: 40, weight: 10, isSelected: false, content: [2] },
{
id: 8,
rBounding: 40,
weight: 10,
isSelected: false,
content: [0, 1, 2],
},
{
id: 9,
rBounding: 40,
weight: 10,
isSelected: false,
content: [1, 2],
},
{
id: 10,
rBounding: 40,
weight: 10,
isSelected: false,
content: [2, 1],
},
{
id: 11,
rBounding: 40,
weight: 10,
isSelected: false,
content: [0, 2, 1],
},
{
id: 12,
rBounding: 40,
weight: 10,
isSelected: false,
content: [0, 1, 2],
},
],
links: [
{ source: 0, target: 1 },
{ source: 1, target: 2 },
{ source: 2, target: 0 },
{ source: 1, target: 3 },
{ source: 3, target: 2 },
{ source: 3, target: 4 },
{ source: 4, target: 5 },
{ source: 5, target: 6 },
{ source: 5, target: 7 },
{ source: 6, target: 7 },
{ source: 6, target: 8 },
{ source: 7, target: 8 },
{ source: 9, target: 4 },
{ source: 9, target: 11 },
{ source: 9, target: 10 },
{ source: 10, target: 11 },
{ source: 11, target: 12 },
{ source: 12, target: 10 },
],
})
Insert cell
chart = {
const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height])
.attr("stroke-width", 2);

const bounds = [0, 0, width, height];

const simulation = d3.forceSimulation(graph.nodes)
.force('link', d3.forceLink(graph.links)
.id(function(d) { return d.id; })
.strength(0)
)
.force("charge", d3.forceManyBody())
.force('center', d3.forceCenter(width/2, height/2)) // 让视图位于区域中心
.force('collision', d3.forceCollide(d => d.rBounding)) // 让大circle之间不重叠
.force("walls", wallForce) // 让节点不超出区域边界,因为边界的限制,会让节点之间重叠
.on('tick', update)
.on("end", moveToCentroid) // 此时点的位置已经生成了

let cells = graph.nodes.map(function(s){ return []; }); // stores, for each site, each cell's verteces

let voronoi = d3.weightedVoronoi() // set the weight accessor
.clip([[0,0], [0,height], [width, height], [width,0]]);

const cellLiner = d3.line()

computeAllCells();
redrawAllCells();

var boundingCircle;
boundingCircle = svg.append("g")
.selectAll('circle')
.data(graph.nodes)
.join("circle")
.attr("class", 'boundingCircle')
.attr("id", (d, i) => 'boundingCircle-' + i.toString())
.attr("r", d => d.rBounding)
.attr('fill', 'rgba(255,0,0,0.5)')
const link = svg
.selectAll(".link")
.data(graph.links)
.join("line")
.classed("link", true)
const circle = svg.append("g")
.selectAll("circle")
.data(graph.nodes)
.join("circle")
.attr("cx", d => d.x)
.attr("cy", d => d.y)
.attr("r", radius)
.attr("fill", (d, i) => {
if(d.draggable){
return d3.schemeCategory10[i % 10]
} else {
return "rgba(0,0,0,0.3)";
}
})
.call(d3.drag()
.on("start", dragstarted )
.on("drag", dragged)
.on("end", dragended)
.on("start.update drag.update end.update", update))
.on("click", click);

function click(event, d) {
d.isSelected = !d.isSelected
if (d.isSelected) {
d.weight = openWeight;
d.rBounding = 200;
circle.filter((p) => p === d).attr("stroke", "black");
} else {
d.weight = 0;
d.rBounding = 40;
circle.filter((p) => p === d).attr("stroke", "");
}
update();
}
function update() {
circle.attr("cx", d => d.x).attr("cy", d => d.y);
boundingCircle
.attr('cx', d => d.x)
.attr('cy', d => d.y)
.attr('r', d => d.rBounding)
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)
simulation.force('collision', d3.forceCollide(d => d.rBounding))
computeAllCells();
redrawAllCells();
}


function dragstarted(event, d){
if (!event.active) simulation.alphaTarget(0.3).restart();
circle.filter(p => p === d).attr("stroke", "black")
}
function dragged(event, d){
d.x = event.x, d.y = event.y
}
function dragended(event, d){
if (!event.active) simulation.alphaTarget(0);
d.weight = 0;
circle.filter(p => p === d).attr("stroke", null)
}

function computeAllCells() {
cells = voronoi(graph.nodes);
}

function redrawAllCells() {
var cellSelection = svg.selectAll(".cell")
.data(cells, function(c){ return c.site.originalObject.index; });

cellSelection.enter()
.append("path")
.attr("fill", "none")
.attr("pointer-events", "all")
.attr("stroke", "#ccc")
.attr("stroke-width", 3)
.classed("cell", true)
.attr("id", function(d,i){ return "cell-"+d.site.originalObject.index; })
.attr("weight", function(d,i){ return d.site.originalObject.weight; })
.merge(cellSelection)
.attr("d", function(d){ return cellLiner(d)+"z"; });

cellSelection.exit().remove();
}

// 在初次渲染完成之后将点至于区域的中心
function moveToCentroid(){
console.log("渲染完成")
var points = graph.nodes.map((d) => { return [d.x, d.y] })
var polygons = [...d3.Delaunay.from(points).voronoi([0, 0, width, height]).cellPolygons()]
polygons.forEach((d, i) => {
console.log("JINLAILE", d3.select(circle._groups[0][i]), circle._groups[0][i])
let centroid = d3.polygonCentroid(d)
// circle.attr("transform", "translate(" + centroid[0] + "," + centroid[1] + ")")
d3.select(circle._groups[0][i]).attr('cx', centroid[0]).attr('cy', centroid[1])
d3.select( boundingCircle._groups[0][i]).attr('cx', centroid[0]).attr('cy', centroid[1])
});
}

return svg.node();
}
Insert cell
Insert cell
width = 1540
Insert cell
height = 540
Insert cell
radius = 20
Insert cell
openWeight =40000
Insert cell
wallForce = d3.forceLimit()
.radius(node => node.rBounding)
.x0(0)
.x1(width)
.y0(0)
.y1(height)
Insert cell
d3 = require("d3@7", "d3-weighted-voronoi", "d3-voronoi-map", "d3-voronoi-treemap", "d3-force-limit")
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