Published unlisted
Edited
Jan 26, 2022
1 fork
1 star
Insert cell
Insert cell
visSetting = {
return {
filters:{
filterAction: "noteTypes", // grantType, noteTypes
nodeTypes: [
"Grant", "funder", "organisation",
"person"
],
grantType: "Studentship", // Studentship, Fellowship
},
reflectAmountNode: "organisation",
showNameNodeTypes: ["funder", "organisation"],
nodeSize: 6,
fontSize: 6,
width: 900,
height: 600
}
}
Insert cell
Insert cell
Insert cell
Insert cell
data = FileAttachment("vis_grant_graph_2022@2.json").json()
Insert cell
chart = {
const width = visSetting.width;
const height = visSetting.height;
let dtStartDateThreshold = 1674691200000; //2023
// 1643155200000; //2022-01-26
// 1637792134000
// 1590966000000 // 1637792134000 // 1293840000000; //dateRange;
console.log(dtStartDateThreshold);
const reflectAmountNode = visSetting.reflectAmountNode;
const linkFilter = function(links, removedNodes){
let filtered = [];
let leftNodeIds = [];
for(let i =0;i<links.length; i++){
if (removedNodes.indexOf(links[i].source) >= 0 || removedNodes.indexOf(links[i].target) >= 0){
continue;
}else{
filtered.push(links[i]);
leftNodeIds.push(links[i].source);
leftNodeIds.push(links[i].target);
}
}
return [filtered, leftNodeIds];
}
// define filter functions
const grantTypeFilter = visSetting.filters.grantType;
const isGrantType = function(n) {
if (n.type == "Grant")
return true;
else{
console.log(n.category);
return n.category == grantTypeFilter;
}
}
const nodeTypeFilter = visSetting.filters.nodeTypes;
const isNodeType = function(n){
return nodeTypeFilter.indexOf(n.type) >= 0;
}

const isValidGranByTime = function(n){
if (n.type != "Grant")
return true;
return n.start <= dtStartDateThreshold;
}

//set filter function
let filterFuncImp = null;
if (visSetting.filters.filterAction == "nodeTypes")
filterFuncImp = isNodeType;
else if (visSetting.filters.filterAction == "grantType")
filterFuncImp = isGrantType;
const nodeFilter = function(nodes, filterFunc){
let left = [];
let removed = []
for(let i =0;i<nodes.length; i++){
if (isValidGranByTime(nodes[i]) && (filterFunc == null || filterFunc(nodes[i]))){
left.push(nodes[i]);
}
else
removed.push(nodes[i].id);
}
return [left, removed];
}

const nodeFilterResults = nodeFilter(data.nodes, filterFuncImp);
const linkFilteredResults = linkFilter(data.links, nodeFilterResults[1]);
const leftNodeIds = linkFilteredResults[1];
const filteredLinks = linkFilteredResults[0];

// need to do another filtering to only keep nodes which have at least one link
let leftNodes = [];
let filteredNodes = nodeFilterResults[0];
let totalAmount = 0;
let nums = {"grant": 0, "org": 0}
for (let i=0;i<filteredNodes.length;i++){
if (leftNodeIds.indexOf(filteredNodes[i].id) >= 0){
leftNodes.push(filteredNodes[i]);
if (filteredNodes[i].type == "Grant"){
totalAmount += filteredNodes[i].amount;
nums["grant"] += 1;
}else if(filteredNodes[i].type == "organisation"){
nums["org"] += 1;
}
}
}
jQuery('#messageBoard').html(
"Total £:" + (totalAmount / 1000 / 1000).toFixed(2) + "m" +
" #grants:" + nums["grant"] +
" #org:" + nums["org"]
);
const links = filteredLinks.map(d => Object.create(d));
const nodes = leftNodes.map(d => Object.create(d));

const 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));

const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height]);
const g = svg.append("g");
const link = g
.attr("stroke", "#999")
.attr("stroke-opacity", 0.6)
.selectAll("line")
.data(links)
.join("line")
// .attr("stroke-width", d => d.strength)
// .attr("stroke-width", function (d) {
// if (d.ref == "PI_PER")
// return 1;
// else
// return 0.5;
// }).attr("stroke", function (d) {
// if (d.strength < 1)
// return "green";
// });

const node = g
// svg.append("g")
// .attr("stroke", "#fff")
// .attr("stroke-width", 1.5)
.selectAll("g")
.data(nodes)
.join("g");

const circles = node.append("circle")
.join("circle")
.attr("r", function (d) {
if (d.type == reflectAmountNode){
if (d.amount == 0)
return visSetting.nodeSize;
else
return Math.log(d.amount);
}else{
return visSetting.nodeSize;
}
})
.attr("fill", color)
.call(drag(simulation));

node.append("text")
.attr("y", 3)
.attr("x", 6)
.text(function(d){
if (visSetting.showNameNodeTypes.indexOf(d.type) >= 0)
return d.name;
else
return "";
})
.style("font-size", `${visSetting.fontSize}px`)
.style("font-family", "Verdana, sans-serif");

node.append("title")
.text(function(d) {
let r = d.name
if (d.type == "Grant"){
const dt = new Date(d.start);
r += " started at " + dt.getFullYear();
}
return r;
});

// display the legend
const legend_g = svg.selectAll(".legend")
.data(nodeTypes)
.enter().append("g")
.attr("transform", (d, i) => `translate(${width - 160},${i * 20 + 10})`);

legend_g.append("circle")
.attr("cx", 0)
.attr("cy", 0)
.attr("r", visSetting.nodeSize)
.attr("fill", styColor);

legend_g.append("text")
.attr("x", 10)
.attr("y", 5)
.style("font-size", `16px`)
.style("font-family", "Verdana")
.text(d => d);

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);

// node
// .attr("cx", d => d.x)
// .attr("cy", d => d.y);
node
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
})

});

invalidation.then(() => simulation.stop());

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

function zoomed({transform}) {
g.attr("transform", transform);
}

return svg.node();
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
// data = FileAttachment("microsoft://vis_grant_graph.json").json()
Insert cell
Insert cell
Insert cell
chart202006 = {
const width = visSetting.width;
const height = visSetting.height;
let dtStartDateThreshold = 1577836800000; //dateRange;
console.log(dtStartDateThreshold);
const reflectAmountNode = visSetting.reflectAmountNode;
const linkFilter = function(links, removedNodes){
let filtered = [];
let leftNodeIds = [];
for(let i =0;i<links.length; i++){
if (removedNodes.indexOf(links[i].source) >= 0 || removedNodes.indexOf(links[i].target) >= 0){
continue;
}else{
filtered.push(links[i]);
leftNodeIds.push(links[i].source);
leftNodeIds.push(links[i].target);
}
}
return [filtered, leftNodeIds];
}
// define filter functions
const grantTypeFilter = visSetting.filters.grantType;
const isGrantType = function(n) {
if (n.type != "Grant")
return true;
else{
// console.log(n.category);
return n.category == grantTypeFilter;
}
}
const nodeTypeFilter = visSetting.filters.nodeTypes;
const isNodeType = function(n){
return nodeTypeFilter.indexOf(n.type) >= 0;
}

const isValidGranByTime = function(n){
if (n.type != "Grant")
return true;
return n.start <= dtStartDateThreshold;
}

//set filter function
let filterFuncImp = null;
if (visSetting.filters.filterAction == "nodeTypes")
filterFuncImp = isNodeType;
else if (visSetting.filters.filterAction == "grantType")
filterFuncImp = isGrantType;
const nodeFilter = function(nodes, filterFunc){
let left = [];
let removed = []
for(let i =0;i<nodes.length; i++){
if (isValidGranByTime(nodes[i]) && (filterFunc == null || filterFunc(nodes[i]))){
left.push(nodes[i]);
}
else
removed.push(nodes[i].id);
}
return [left, removed];
}

const nodeFilterResults = nodeFilter(data.nodes, filterFuncImp);
const linkFilteredResults = linkFilter(data.links, nodeFilterResults[1]);
const leftNodeIds = linkFilteredResults[1];
const filteredLinks = linkFilteredResults[0];

// need to do another filtering to only keep nodes which have at least one link
let leftNodes = [];
let filteredNodes = nodeFilterResults[0];
let totalAmount = 0;
let nums = {"grant": 0, "org": 0}
for (let i=0;i<filteredNodes.length;i++){
if (leftNodeIds.indexOf(filteredNodes[i].id) >= 0){
leftNodes.push(filteredNodes[i]);
if (filteredNodes[i].type == "Grant"){
totalAmount += filteredNodes[i].amount;
nums["grant"] += 1;
}else if(filteredNodes[i].type == "organisation"){
nums["org"] += 1;
}
}
}
jQuery('#messageBoard').html(
"Total £:" + (totalAmount / 1000 / 1000).toFixed(2) + "m" +
" #grants:" + nums["grant"] +
" #org:" + nums["org"]
);
const links = filteredLinks.map(d => Object.create(d));
const nodes = leftNodes.map(d => Object.create(d));

const 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));

const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height]);
const g = svg.append("g");
const link = g
.attr("stroke", "#999")
.attr("stroke-opacity", 0.6)
.selectAll("line")
.data(links)
.join("line")
// .attr("stroke-width", d => d.strength)
// .attr("stroke-width", function (d) {
// if (d.ref == "PI_PER")
// return 1;
// else
// return 0.5;
// }).attr("stroke", function (d) {
// if (d.strength < 1)
// return "green";
// });

const node = g
// svg.append("g")
// .attr("stroke", "#fff")
// .attr("stroke-width", 1.5)
.selectAll("g")
.data(nodes)
.join("g");

const circles = node.append("circle")
.join("circle")
.attr("r", function (d) {
if (d.type == reflectAmountNode){
if (d.amount == 0)
return visSetting.nodeSize;
else
return Math.log(d.amount);
}else{
return visSetting.nodeSize;
}
})
.attr("fill", color)
.call(drag(simulation));

node.append("text")
.attr("y", 3)
.attr("x", 6)
.text(function(d){
if (visSetting.showNameNodeTypes.indexOf(d.type) >= 0)
return d.name;
else
return "";
})
.style("font-size", `${visSetting.fontSize}px`)
.style("font-family", "Verdana, sans-serif");

node.append("title")
.text(function(d) {
let r = d.name
if (d.type == "Grant"){
const dt = new Date(d.start);
r += " started at " + dt.getFullYear();
}
return r;
});

// display the legend
const legend_g = svg.selectAll(".legend")
.data(nodeTypes)
.enter().append("g")
.attr("transform", (d, i) => `translate(${width - 160},${i * 20 + 10})`);

legend_g.append("circle")
.attr("cx", 0)
.attr("cy", 0)
.attr("r", visSetting.nodeSize)
.attr("fill", styColor);

legend_g.append("text")
.attr("x", 10)
.attr("y", 5)
.style("font-size", `${visSetting.fontSize}px`)
.style("font-family", "Verdana")
.text(d => d);

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);

// node
// .attr("cx", d => d.x)
// .attr("cy", d => d.y);
node
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
})

});

invalidation.then(() => simulation.stop());

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

function zoomed({transform}) {
g.attr("transform", transform);
}

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