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

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more