Public
Edited
Feb 4, 2023
1 star
Insert cell
Insert cell
Insert cell
Insert cell
chart = {
// Obtain links and nodes from json file.
var links = data.links.map(d => Object.create(d));
var nodes = data.nodes.map(d => Object.create(d));
// Force Diagram
var simulation = d3.forceSimulation(nodes)
.force("link", d3.forceLink(links).id(d => d.id))
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(width / 2, height / 2));

// SVG
const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height]);

//Create marker arrows
var marker = svg
.append("svg:defs")
.selectAll("marker")
.data(["end"])
.enter()
.append("svg:marker")
.attr("id", String)
.attr("viewBox", "0 -5 10 10")
.attr("refX", 0)
.attr("refY", 0)
.attr("markerWidth", 3)
.attr("markerHeight", 2)
.attr("orient", "auto")
.append("svg:path")
.attr("d", "M0,-5L10,0L0,5");
// Create links
/*const link = svg
.append("g")
.attr("stroke", "Gray")
.selectAll("line")
.data(links)
.join("line")
.attr("stroke-width",1)
.attr("marker-mid", "url(#end)")*/
var link = svg
.selectAll(".link")
.data(links)
.enter()
.append("polyline") //Create as polyline for arrows
.attr("stroke", "Gray")
.attr("stroke-width",3) // Set default stroke width
.attr("marker-mid", "url(#end)") // Add Marker
// Create nodes
var node = svg
.append("g")
.attr("stroke", "White")
.selectAll("circle")
.data(nodes)
.join("circle")
.attr("r", d=>5*Math.sqrt(d.value))
//.attr("fill", function(d) { return color(d.group); })
.attr("fill", function(d) { return mycolor(d.group); })
//.attr("fill", function(d) { return "pink"; })
.call(drag(simulation)).attr('class', 'node');
var nodeLinkStatus = {};
links.forEach(d => {
nodeLinkStatus[`${d.source.index},${d.target.index}`] = 1;
});

function isConnected(a, b) {
return nodeLinkStatus[`${a.index},${b.index}`] || nodeLinkStatus[`${b.index},${a.index}`] || a.index === b.index;
}

// Node interactibility
node.on('mouseover',function (d) {
node.style('stroke-opacity', function (o) {
var thisOpacity = 0;
if(isConnected(d, o)){
thisOpacity = 1;
} else{
thisOpacity = 0.3;
}
this.setAttribute('fill-opacity', thisOpacity);
return thisOpacity;
});
link.style('opacity', function(l) {
if (d === l.source || d === l.target){
return 1;
}

else{
return 0.2;
}
});
link.style('stroke-width', function(l) {
if (d === l.source || d === l.target){
return 5;
}else{
return 3;
}
});
var xpos =d.x;
var ypos = d.y;
var tgrp = svg.append("g")
.attr("id", "tooltip")
.attr("transform", (d, i) => `translate(${xpos+10},${ypos})`);
tgrp.append("rect")
.attr("width", "40px")
.attr("height", "24px")
.attr("fill", "gray")
tgrp.append("text")
.attr("x", 5)
.attr("y", 14)
.attr("text-anchor", "left")
.attr("font-family", "sans-serif")
.attr("font-size", "110px")
.attr("font-weight", "bold")
.attr("fill", "white")
.text(`${d.name}`);
});
node.on('mouseout',function (d) {
node.style('stroke-opacity', function (o) {
this.setAttribute('fill-opacity', 1);
return 1;
});
link.style('opacity',1);
link.style('stroke-width',3);
d3.select("#tooltip").remove();
});
// Legend
var allGroupNames = ['Targaryen','Stark','Lannister','Greyjoy','Baratheon','Snow','Other']
var groupHouses = (color.domain()).slice(0,7)

var colorLegend = svg
.selectAll('.colorLegend')
.data(groupHouses)
.enter().append("g") //下面是图标的位置
.attr('transform', function(d, i) {
return 'translate('+(10)+',' + (10+(25*i)) +')';
})
colorLegend.append('rect')
//这里对应的是左上角图标的颜色
.attr('fill', function(d) { return color(d); })
//.attr('fill', function(d) { return "black"; })
.attr('width', 20)
.attr('height', 20);
colorLegend.append("text")
.attr('x', 25)
.attr('y', 25/2)
.text(function(d) {return allGroupNames[d-1]; });

// Simulation tick
simulation.on("tick", () => {
/*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);*/
link.attr("points", function(d) {
return d.source.x + "," + d.source.y + " " +
(d.source.x + d.target.x)/2 + "," + (d.source.y + d.target.y)/2 + " " +
d.target.x + "," + d.target.y; });
node
.attr("cx", d => d.x)
.attr("cy", d => d.y);
});

// Event listener for edge coloring
function update() {
link.style('stroke', function(l) {
var query = viewof order.value
if (query.includes("sibling")){
if (l.relationship === "sibling" || l.relationship === "siblings"){
return '#DC143C';
}
}
else if (l.relationship === query){
return '#DC143C';
}

else{
return 'gray';
}
})
}
function changeFunction() {
svg.selectAll("*").remove();

var targaryen = document.getElementById('1').checked;
var stark = document.getElementById('2').checked;
var lannister = document.getElementById('3').checked;
var grayjoy = document.getElementById('4').checked;
var baratheon = document.getElementById('7').checked;
var snow = document.getElementById('6').checked;
var other = document.getElementById('5').checked;
var houses = [targaryen,stark,lannister,grayjoy,baratheon,snow,other];
var validNodes = [];
var validLinks = [];
var i;
for (i=0;i<data.nodes.length;i++){
if(houses[(data.nodes[i].group)-1] == true){
validNodes.push(data.nodes[i])
};
}

var j;
for(j=0;j<data.links.length;j++){
var k;
for(k=0;k<validNodes.length;k++){
if(validNodes[k].name === data.links[j].source){
var m;
for(m=0;m<validNodes.length;m++){
if(validNodes[m].name === data.links[j].target){
validLinks.push(data.links[j]);
continue;
}
};
};
}
}
nodes = validNodes.map(d => Object.create(d));
links = validLinks.map(d => Object.create(d));
// Force Diagram
simulation = d3.forceSimulation(nodes)
.force("link", d3.forceLink(links).id(d => d.id))
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(width / 2, height / 2));


//Create marker arrows
marker = svg
.append("svg:defs")
.selectAll("marker")
.data(["end"])
.enter()
.append("svg:marker")
.attr("id", String)
.attr("viewBox", "0 -5 10 10")
.attr("refX", 0)
.attr("refY", 0)
.attr("markerWidth", 3)
.attr("markerHeight", 2)
.attr("orient", "auto")
.append("svg:path")
.attr("d", "M0,-5L10,0L0,5");
link = svg
.selectAll(".link")
.data(links)
.enter()
.append("polyline") //Create as polyline for arrows
.attr("stroke", "Gray")
.attr("stroke-width",3) // Set default stroke width
.attr("marker-mid", "url(#end)") // Add Marker
// Create nodes
node = svg
.append("g")
.attr("stroke", "White")
.selectAll("circle")
.data(nodes)
.join("circle")
.attr("r", d=>5*Math.sqrt(d.value))
.attr("fill", function(d) { return color(d.group); })
//.attr("fill", function(d) { return "black"; })
.call(drag(simulation)).attr('class', 'node');
nodeLinkStatus = {};
links.forEach(d => {
nodeLinkStatus[`${d.source.index},${d.target.index}`] = 1;
});

// Node interactibility
node.on('mouseover',function (d) {
node.style('stroke-opacity', function (o) {
var thisOpacity = 0;
if(isConnected(d, o)){
thisOpacity = 1;
} else{
thisOpacity = 0.3;
}
this.setAttribute('fill-opacity', thisOpacity);
return thisOpacity;
});
link.style('opacity', function(l) {
if (d === l.source || d === l.target){
return 1;
}

else{
return 0.2;
}
});
link.style('stroke-width', function(l) {
if (d === l.source || d === l.target){
return 5;
}else{
return 3;
}
});
var xpos =d.x;
var ypos = d.y;
var tgrp = svg.append("g")
.attr("id", "tooltip")
.attr("transform", (d, i) => `translate(${xpos+10},${ypos})`);
tgrp.append("rect")
.attr("width", "40px")
.attr("height", "24px")
.attr("fill", "gray")
tgrp.append("text")
.attr("x", 5)
.attr("y", 14)
.attr("text-anchor", "left")
.attr("font-family", "sans-serif")
.attr("font-size", "11px")
.attr("font-weight", "bold")
.attr("fill", "white")
.text(`${d.name}`);
});
node.on('mouseout',function (d) {
node.style('stroke-opacity', function (o) {
this.setAttribute('fill-opacity', 1);
return 1;
});
link.style('opacity',1);
link.style('stroke-width',3);
d3.select("#tooltip").remove();
});

colorLegend = svg.selectAll('.colorLegend')
.data(groupHouses)
.enter().append("g")
.attr('transform', function(d, i) {
return 'translate('+(10)+',' + (10+(25*i)) +')';
})
colorLegend.append('rect')
//.attr('fill', function(d) { return color(d); })
.attr('fill', function(d) { return "black"; })
.attr('width', 20)
.attr('height', 20);
colorLegend.append("text")
.attr('x', 25)
.attr('y', 25/2)
.text(function(d) {return allGroupNames[d-1]; });

// Simulation tick
simulation.on("tick", () => {
/*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);*/
link.attr("points", function(d) {
return d.source.x + "," + d.source.y + " " +
(d.source.x + d.target.x)/2 + "," + (d.source.y + d.target.y)/2 + " " +
d.target.x + "," + d.target.y; });
node
.attr("cx", d => d.x)
.attr("cy", d => d.y);
});


}

viewof order.addEventListener("input", update);
invalidation.then(() => viewof order.removeEventListener("input", update));
viewof orderHouse.addEventListener("input", changeFunction);
invalidation.then(() => viewof orderHouse.removeEventListener("input",changeFunction));

return svg.node();
}
Insert cell
Insert cell
mycolor = i => {
let cc ;
cc = color(i)
if(i==7){
cc="black"
}
return cc;
}
Insert cell
// Assign colors to groups
// Each group is assigned during preprocessing
color = {
var allGroups = data.nodes.map(function(d){return d.group})
allGroups = [...new Set(allGroups)]
const scale = d3.scaleOrdinal().domain(allGroups).range(d3.schemeSet2);
return scale;
}
Insert cell
// Drag Events
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
Insert cell
gotR = FileAttachment("got-r.json").json()
Insert cell
Insert cell
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