Published
Edited
Dec 8, 2021
Insert cell
Insert cell
tmpnodes = d3.hierarchy(rollupData).descendants()
Insert cell
showColor(tmpnodes[0])
Insert cell
d3.color(z(tmpnodes[0].data["学校"]))
Insert cell
showSize(tmpnodes[14])
Insert cell
sz(0)
Insert cell
Object.keys(tmpnodes[2])
Insert cell
Insert cell
tmplinks.map(d=>d.index)
Insert cell
d3.forceLink(tmplinks).links().map(d=>d.index)
Insert cell
screen.height
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
chart = {
const root = d3.hierarchy(rollupData);
const links = root.links();
const nodes = root.descendants();
let MouseOutEvent = null;
const delay = 1000;

const svgR = d3.create("svg")
.attr("viewBox", [0, 0, width, height])
.property("value", [1]);
const svg = svgR.append("g");
const link = svg.append("g")
.style("stroke", "#999")
.style("stroke-opacity", 0.6)
.style("stroke-width", 1.5)
.selectAll("line")
.data(links)
.join("line")
.classed("link", true);

const node = svg.append("g")
.selectAll("circle")
.data(nodes)
.join("circle")
.classed("node", true)
.classed("fixed", d => d.fx !== undefined)
.attr("r", showSize)
.attr("fill", showFill)
.attr("stroke", showStroke);
const simulation = d3.forceSimulation()
.nodes(nodes)
.force("charge", d3.forceManyBody().strength(-30).distanceMax(61000 / nodes.length))
.force("center", d3.forceCenter(width / 2, height / 2))
.force("link", d3.forceLink(links).distance(30).strength(2))
.force("collide", d3.forceCollide(10))
.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);
node.attr("cx", d => d.x)
.attr("cy", d => d.y);
});
// modify the leaf node.
const leaf = svg.selectAll("circle")
.filter(d=>!d.children)
.style("pointer-events", "all")
.style("cursor", "pointer")
.on("click", (event, d)=>{window.open(d.data["主页链接"], "_blank")})
.on("mouseover", function(event, d){
if(MouseOutEvent){
clearTimeout(MouseOutEvent);
d3.select(".mouseOver").attr("fill", showFill).classed("mouseOver", false);
d3.select("#label-group").remove();
d3.select("#school-group").remove();
d3.select("#contri-group").remove();
}
d3.select(this).attr("fill", showColor).style("cursor", "pointer").classed("mouseOver", true);
let sim = d.data["相似推荐"].split(" ").map(d=>+d);
svgR.property("value", d3.select(this).attr("r")).dispatch("input");
svg.selectAll("circle")
.filter(dd=>!dd.children)
.filter(dd=>sim.indexOf(+dd.data["序号"] - 1) >= 0)
.filter(dd=>(+dd.data["序号"] != +d.data["序号"]))
.attr("fill", showColor)
.attr("r", function(dd){
let curR = +d3.select(this).attr("r");
// svgR.property("value", curR).dispatch("input");
return sim.indexOf(+dd.data["序号"] - 1) / 20 * (13 - curR) + curR;
})
.classed("sim", true);
var labelItem = [{x: 10, y: 50, text: `姓名:${d.data["姓名"]}`},
// {x: 10, y: 75, text: `收集负责人:${d.data["收集负责人"]}`},
{x: 10, y: 75, text: `研究兴趣/方向:${d.data["研究兴趣/方向"] || "未知"}`}];
svg.append("g").attr("id", "label-group");
const labelDOM = document.getElementById("label-group")
labelItem.forEach(function(d){
labelDOM.appendChild(createSVGtext(Object.assign(d, {maxCharsPerLine: 20})));
});
svg.select("#label-group")
.selectAll("text")
.attr("id", (d, i)=>`node-${i}`)
.style("font-family", "SemHei")
.style("font-weight", "bold")
.style("font-size", "1em")
.style("text-anchor", "start");
var schoolItem = [{x: width - 250, y: 50, text: `${d.data["学校"]}`},
{x: width - 250, y: 75, text: `- ${d.data["一级单位(学院/系)"]}`},
{x: width - 250, y: 100, text: `-- ${d.data["二级单位(系所/教研室/研究中心/学科/专业/方向)"]}`},
{x: width - 250, y: 125, text: `本科:${d.data["本科毕业学校"] || "未知"},${d.data["本科毕业年份"] || "未知"}`},
{x: width - 250, y: 150, text: `硕士:${d.data["硕士毕业学校"] || "未知"},${d.data["硕士毕业年份"] || "未知"}`},
{x: width - 250, y: 175, text: `博士:${d.data["博士毕业学校"] || "未知"},${d.data["博士毕业年份"] || "未知"}`}];
svg.append("g").attr("id", "school-group");
const schoolDOM = document.getElementById("school-group")
schoolItem.forEach(function(d){
schoolDOM.appendChild(createSVGtext(Object.assign(d, {maxCharsPerLine: 20})));
});
svg.select("#school-group")
.selectAll("text")
.attr("id", (d, i)=>`node-${i}`)
.style("font-family", "SemHei")
.style("font-weight", "bold")
.style("font-size", "1em")
.style("text-anchor", "start");
var contriItem = [{x: width - 250, y: height - 50, text: `收集负责人:${d.data["收集负责人"]}`}];
svg.append("g").attr("id", "contri-group");
const contriDOM = document.getElementById("contri-group")
contriItem.forEach(function(d){
contriDOM.appendChild(createSVGtext(Object.assign(d, {maxCharsPerLine: 20})));
});
svg.select("#contri-group")
.selectAll("text")
.attr("id", (d, i)=>`node-${i}`)
.style("font-family", "SemHei")
.style("font-weight", "bold")
.style("font-size", "1em")
.style("text-anchor", "start");
})
.on("mouseout", function(d, i){
d3.select(this).style("cursor", null);
d3.selectAll(".sim").attr("fill", showFill).attr("r", showSize).style("cursor", "default").classed("sim", false);
MouseOutEvent = setTimeout(()=>{
d3.select(this).attr("fill", showFill).classed("mouseOver", false);
d3.select("#label-group").remove();
d3.select("#school-group").remove();
d3.select("#contri-group").remove();
}, delay);
});

// leaf.append("title")
// .text(d=>`点击查看${d.data["姓名"]}的个人主页`);

// modify the internal nodes.
const internal = svg.selectAll("circle")
.filter(d=>d.children)
// .style("cursor", "zoom-in");
.style("cursor", "move")
.call(d3.drag().on("start", dragstart).on("drag", dragged));
// internal.on("click", click)
internal.append("title")
.text(showText);
// event definitions.
function click(event, d) {
delete d.fx;
delete d.fy;
d3.select(this).classed("fixed", false).attr("fill", showFill);
simulation.alpha(1).restart();
}

function dragstart() {
d3.select(this).classed("fixed", true).attr("fill", "#f00");
}

function dragged(event, d) {
d.fx = clamp(event.x, 0, width);
d.fy = clamp(event.y, 0, height);
simulation.alpha(1).restart();
}

return svgR.node();
}
Insert cell
function clamp(x, lo, hi) {
return x < lo ? lo : x > hi ? hi : x;
}
Insert cell
!isNaN("") && Number("")>0
Insert cell
function showColor(d){
var col = d3.color(z(d.data["学校"]));
return d3.rgb(255 - col.r, 255 - col.g, 255 - col.b).hex();
}
Insert cell
function showSize(d){
if(!isNaN(d.data["本科毕业年份"]) && Number(d.data["本科毕业年份"]) > 0) return sz(Number(d.data["本科毕业年份"]));
else if(!isNaN(d.data["硕士毕业年份"]) && Number(d.data["硕士毕业年份"]) > 0) return sz(Number(d.data["硕士毕业年份"]));
else if(!isNaN(d.data["博士毕业年份"]) && Number(d.data["博士毕业年份"]) > 0) return sz(Number(d.data["博士毕业年份"]));
else return 4;
return 4;
}
Insert cell
function showFill(d){
if(d.depth == 0) return "#FFFFFF"; // 学校组
else if(d.depth == 1) return "#DCDCDC"; // 学校
else if(d.depth == 2) return "#696969"; // 一级学科
else if(d.depth == 3) return "000000"; // 二级学科
else return z(d.data["学校"]); // 教师
}
Insert cell
function showStroke(d){
return d.children ? "#000" : "#fff";
}
Insert cell
function showText(d){
if(d.depth == 0) return `对比组: ${groupby.toString().replace(/,/g, '\n ')}`;
else if(d.depth == 1) return `学校:${d.data[0]}`;
else if(d.depth == 2) return `学校: ${d.parent.data[0]}\n一级单位:${d.data[0]}`;
else if(d.depth == 3) return `学校:${d.parent.parent.data[0]}\n一级单位:${d.parent.data[0]}\n二级单位:${d.data[0]}`;
else return `姓名:${d.data["姓名"]}\n研究兴趣/方向:${d.data["研究兴趣/方向"] || "未知" }`;
}
Insert cell
a03data = FileAttachment("datavis_a03_data_sim.csv").csv()
Insert cell
major = d3.map(a03data, d=>d["研究兴趣/方向"])
Insert cell
function filt(a){
var filtSub = d3.filter(a03data, d=>subject.indexOf(d["方向"]) >= 0);
return d3.filter(filtSub, d=>d["学校"]==a).length;
}
Insert cell
function stayFilt(d){
if(stay == "所有人员") return true;
if(d["学校"] == d["本科毕业学校"]) return true;
if(d["学校"] == d["硕士毕业学校"]) return true;
if(d["学校"] == d["博士毕业学校"]) return true;
return false;
}
Insert cell
names = Array.from(new Set(a03data.map(d=>d["学校"]))).sort((a, b)=>(filt(b) - filt(a)))
Insert cell
rollupData = d3.rollup(d3.filter(d3.filter(d3.filter(a03data, stayFilt), d=>subject.indexOf(d["方向"]) >= 0), d=>groupby.indexOf(d["学校"]) >= 0), d=>d, d=>d["学校"], d=>d["一级单位(学院/系)"], d=>d["二级单位(系所/教研室/研究中心/学科/专业/方向)"])
Insert cell
height = 600
Insert cell
drag = simulation => {
function dragstarted(event, d) {
if (!event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(event, d) {
d.fx = clamp(event.x, 0, width);
d.fy = clamp(event.y, 0, height);
}
function dragended(event, d) {
if (!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
sz.domain()
Insert cell
sz = d3.scaleLinear()
.domain([d3.min(a03data.map(d=>+d["本科毕业年份"] || 3000)), d3.max(a03data.map(d=>+d["博士毕业年份"] || 0))])
.range([12, 4])
Insert cell
z = d3.scaleOrdinal()
.domain(names)
.range(d3.quantize(d3.interpolateRainbow, names.length))
// .range(d3.schemeSpectral[names.length])
// z = d3.scaleOrdinal(d3.quantize(d3.interpolateRainbow, names.length))
Insert cell
// deprecated code but may be useful in future
//add brush.
// // const brush = d3.brush().on("start brush end", brushed);
// const circle = svg.selectAll("circle")
// .filter(d=>!d.children);
// // svg.call(brush);
// function brushed({selection}) {
// let value = [];
// if (selection) {
// const [[x0, y0], [x1, y1]] = selection;
// value = circle
// .style("stroke", "gray")
// .filter((d, i, nodes) => x0 <= nodes[i].cx && nodes[i].cx < x1 && y0 <= nodes[i].cy && nodes[i].cy < y1)
// .style("stroke", "steelblue")
// .style("fill", "steelblue")
// .data();
// } else {
// circle.style("stroke", "steelblue");
// }
// svg.property("value", value); // set the value so we can see it in the console. But what's the difference between property and attr?
// }
Insert cell
import {createSVGtext} from "@jxrjxrjxr/svg-chinese-word-wrap"
Insert cell
import { wrap_text, wrap_text_nchar } from "@ben-tanen/svg-text-and-tspan-word-wrapping"
Insert cell
// import {height, fullscreen} from "@fil/height"
Insert cell
import {swatches} from "@d3/color-legend"
Insert cell
import {slider} from "@jashkenas/inputs"
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