Public
Edited
Apr 21
Paused
Importers
7 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
matrixChart = showMatrix ? vl
.markCircle({ tooltip: true })
.encode(
vl
.y()
.fieldO("source.name")
.sort(vl.fieldO("source.cluster"))
.axis(null)
.axis(filteredNetwork.nodes.length > 200 ? null : {}),
vl
.x()
.fieldO("target.name")
.sort(vl.fieldO("source.cluster"))
.axis(filteredNetwork.nodes.length > 100 ? null : {}),
vl.size().fieldQ("value"),
vl.color().fieldN("source.cluster")
)
.data(
filteredNetwork.links.map((l) => ({
...l,
source: dAuthorsId.get(l.source),
target: dAuthorsId.get(l.target)
}))
)
.width(width - 200)
.height(width - 200)
.render() : htl.html`Please enable show matrix`
Insert cell
networkTypes = [
"Co-Authorship",
"Citation",
"Co-Authorship Computed",
]
Insert cell
function clearFilteredAuthors() {
const evt = new Event("input", { bubbles: true });
viewof maxR.value = 10;
viewof maxR.dispatchEvent(evt);
viewof selectedAuthors.value = [];
viewof selectedAuthors.dispatchEvent(evt);
viewof minLinkValue.value = selectedNetwork === "Citation" ? 15 : 0;
viewof minLinkValue.dispatchEvent(evt);
}
Insert cell
function showKhouryVis() {
const evt = new Event("input", { bubbles: true });
viewof selectedAuthors.value = khouryVisProfessors;
viewof selectedAuthors.dispatchEvent(evt);
viewof maxR.value = 20;
viewof maxR.dispatchEvent(evt);
viewof selectedNetwork.value = "Co-Authorship";
viewof selectedNetwork.dispatchEvent(evt);
viewof minLinkValue.value = 0;
viewof minLinkValue.dispatchEvent(evt);
}
Insert cell
function showMostCoauthorship() {
viewof selectedAuthors.value = [];
viewof selectedAuthors.dispatchEvent(new Event("input"));
viewof selectedNetwork.value = "Co-Authorship";
viewof selectedNetwork.dispatchEvent(new Event("input"));
viewof minLinkValue.value = 10;
viewof minLinkValue.dispatchEvent(new Event("input"));
}
Insert cell
function showMostCited() {
viewof selectedAuthors.value = [];
viewof selectedAuthors.dispatchEvent(new Event("input"));
viewof minLinkValue.value = 30;
viewof minLinkValue.dispatchEvent(new Event("input"));
viewof selectedNetwork.value = "Citation";
viewof selectedNetwork.dispatchEvent(new Event("input"));
}
Insert cell
selectedAuthors
Insert cell
khouryVisProfessors = [
"Enrico Bertini",
"John Alexis Guerra Gómez",
"Lace M. K. Padilla",
// "Lace M. Padilla",
// "Lace Padilla",
"Cody Dunne",
"Michelle A. Borkin",
// "Michelle Borkin",
"Michael Correll",
"Melanie Tory",
"Caglar Yildirim",
"Yifan Hu"
]
Insert cell
r = d3
.scaleSqrt()
.domain([0, d3.max(network.nodes.map((d) => d.value))])
.range([1, maxR])
Insert cell
w = d3
.scaleLinear()
.domain([0, d3.max(network.links.map((l) => l.value))])
.range([0.1, 10])
Insert cell
ns = d3
.scaleLinear()
.domain([0, network.nodes.length])
.range([-70, -0.1])
Insert cell
colors = d3.schemeCategory10.concat(d3.schemeAccent)
// d3.quantize(d3.interpolateTurbo, dClusters.size)
Insert cell
dClusters = d3.group(network.nodes, d=> d.cluster)
Insert cell
dAuthorsName = d3.group(network.nodes, d=> d.name)
Insert cell
dAuthorsId = new Map(network.nodes.map((d,i) => [d.index,d]))
Insert cell
network = {
switch (selectedNetwork) {
case "Citation":
return citationNetwork;
case "Co-Authorship Computed":
return networkComputed;
default:
return coAuthorshipNetwork;
}
}
Insert cell
filteredNetwork = filterNetwork(network, {
edgelessNodes,
nodeId: (d) => d.index,
nodesMap: dAuthorsId,
egoDistance,
egoPoint5,
byNodes: (n) =>
selectedClusters.length === 0 || selectedClusters.includes("" + n.cluster),
byLinks: (l) =>
l.value >= minLinkValue &&
(selectedAuthors.length === 0 ||
selectedAuthors.includes(dAuthorsId.get(l.source).name) ||
selectedAuthors.includes(dAuthorsId.get(l.target).name))
})
Insert cell
function filterNetwork(
network,
{
nodeId = (d) => d.id,
byNodes = null,
byLinks = null,
edgelessNodes = false, // Should we allow nodes without edges?
nodesMap,
egoDistance = 1,
egoPoint5 = true // find connections between filtered connections
} = {}
) {
nodesMap = nodesMap
? nodesMap
: new Map(network.nodes.map((d, i, all) => [nodeId(d, i, all), d]));
let res = {
nodes: [...network.nodes],
links: [...network.links]
};
//
function filterNodesFromLinks(res) {
const ids = [...new Set(res.links.map((l) => [l.source, l.target]).flat())];
res.nodes = ids.map((id) => nodesMap.get(id)).filter((d) => d); // undefined when the node is not visible anymore;
return res;
}

function filterLinksFromNodes({ nodes, links }, { filterBoth = true } = {}) {
const ids = new Set(nodes.map(nodeId));
let filteredLinks = links;
links = links
.filter(({ source, target }) =>
filterBoth
? ids.has(source) && ids.has(target)
: ids.has(source) || ids.has(target)
)
.map((l) => ({ ...l }));
return { nodes, links };
}

// Filter nodes
if (byNodes) {
res.nodes = res.nodes.filter(byNodes).map((n) => ({ ...n }));
}

res = filterLinksFromNodes(res, { filterBoth: true });

// Then links
if (byLinks) {
res.links = res.links.filter(byLinks).map((l) => ({
...l
}));
// console.log("Filter Links", res);
// Remove edgeless nodes
if (!edgelessNodes) {
res = filterNodesFromLinks(res, { filterBoth: true });
}
}

if (egoPoint5) {
// 0.5
res = filterLinksFromNodes(
{ nodes: res.nodes, links: network.links },
{ filterBoth: true }
);
res = filterNodesFromLinks(res, { filterBoth: true });
}

for (
let currentDistance = 1;
currentDistance < egoDistance;
currentDistance += 1
) {
res = filterLinksFromNodes(
{ nodes: res.nodes, links: network.links },
{ filterBoth: false }
);
res = filterNodesFromLinks(res, { filterBoth: true });

if (egoPoint5) {
// 0.5
res = filterLinksFromNodes(
{ nodes: res.nodes, links: network.links },
{ filterBoth: true }
);
res = filterNodesFromLinks(res, { filterBoth: true });
}
}

return res;
}
Insert cell
coAuthorshipNetwork = FileAttachment("coauthorshipNetwork2024_full_observable_with_numbers.json")
.json()
.then((res) => {
res.nodes.map((d) => (d.name = d.name.replace(/\d/g, '').trim())); // there is a weird 001 in the data 🤷🏼
return res;
})
Insert cell
citationNetwork = FileAttachment("citationsNetwork2024_full.json")
.json()
.then((res) => {
res.nodes.map((d) => (d.name = d.name.replace(/\d/g, '').trim())); // there is a weird 001 in the data 🤷🏼
return res;
})
Insert cell
viewof selectedPapers = navio(papers)
// selectedPapers = papers
Insert cell
papersRaw = selectedNetwork === "Co-Authorship Computed"
? allPapers
: [{ id: 1 }]
Insert cell
viewof papers = dataInput({value: papersRaw})
Insert cell
import {dataInput} from "@john-guerra/data-input"
Insert cell
viewof unrollAttr = {
const defaultUnrollAttrs = ["AuthorNames-Deduped", "InternalReferences"];
const attrs = Object.keys(papers[0]);
let attrsToSelect = defaultUnrollAttrs;
let selected = defaultUnrollAttrs[0];

if (!attrs.includes(defaultUnrollAttrs[0])) {
attrsToSelect = attrs;
}
return Inputs.select(attrsToSelect, {
label: "Unroll papers by",
value: selected
});
}
Insert cell
viewof unrollSeparator = Inputs.select([";", ",", "\t"], {label: "Unroll character"})
Insert cell
viewof papersUnrolled = aq.from(selectedPapers)
.derive({name: aq.escape(d => op.split(d[unrollAttr], unrollSeparator))})
.unroll("name")
.view()
Insert cell
clusters = d3
.groups(network.nodes, (d) => d.cluster)
.sort((a, b) => d3.descending(a[1].length, b[1].length))
.map((d) => `${d[0]}: ${d[1].map((d) => d.name).join(", ")} `)
Insert cell
selectedClusters = selectedClustersObjs.map(d => d.split(": ")[0])
Insert cell
networkComputed = {
if (selectedNetwork !== "Co-Authorship Computed")
return { nodes: [], links: [] };

const network = tableToNetwork(papersUnrolled.objects(), {
nodesBy: "name",
matchBy: "id"
});
network.nodes.map((n) => {
n.index = n.id;
n.name = n.id;
n.value = n.degree;
n.cluster = 1;
delete n.id;
delete n.degree;
});

network.links.map((l) => {
l.value = l.count;
delete l.count;
});
return network;
}
Insert cell
Insert cell
viewof useClusters = Inputs.toggle({label: "Enable clustering"})
Insert cell
networkComputedClustered = {
if (!useClusters) return null;

const generator = await clusterWorker({
network: networkComputed,
id: "index",
debug: true
});

for await (let res of generator) {
yield {
nodes: res.nodes,
links: res.links.map((l) => ({
...l,
source: l.source.index,
target: l.target.index
}))
};
}
}
Insert cell
allPapers = FileAttachment("IEEE VIS papers 1990-2024 - Main dataset.csv")
.csv({
typed: true
})
.then((res) =>
res.map((row) => {
if (row["AuthorNames-Deduped"]) {
// row["AuthorNames-Deduped"] = row["AuthorNames-Deduped"].replaceAll(
// "JohnAlexis Guerra-Gómez",
// "John Alexis Guerra Gómez"
// );
row["AuthorNames-Deduped"] = row["AuthorNames-Deduped"].replaceAll(
"Michelle Borkin",
"Michelle A. Borkin",
);
}
row.id = row.DOI;
return row;
})
)
Insert cell
height = 600
Insert cell
import {clusterWorker} from "@john-guerra/netclustering-js"
Insert cell
import {OnClickButton, OnClickLink} from "@john-guerra/on-click-button"
Insert cell
import {tableToNetwork} from "@john-guerra/table-to-network"
Insert cell
import {aq, op} from "@uwdata/arquero"
Insert cell
import {navio} from "@john-guerra/navio"
Insert cell
import { ForceGraph } from "@john-guerra/force-directed-graph"
Insert cell
import {searchCheckbox} from "@john-guerra/search-checkbox"
Insert cell
import {multiAutoSelect} from "@john-guerra/multi-auto-select"
Insert cell
import {PersistInput} from "@john-guerra/persist-input"
Insert cell
import {vl} from "@vega/vega-lite-api-v5"
Insert cell
netClustering = require("netclustering")
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