Public
Edited
Dec 26, 2023
1 star
Insert cell
Insert cell
Insert cell
viewof COLLIDE_RADIUS = Inputs.range([0, 30], {value: 13, step: 1, label: "COLLIDE_RADIUS"})

Insert cell
viewof CHARGE_STRENGTH = Inputs.range([-1000, 1000], {value: -100, step: 10, label: "CHARGE_STRENGTH"})
Insert cell
viewof CENTER_STRENGTH = Inputs.range([0.00, 1], {value: 1, step: 0.01, label: "CENTER_STRENGTH"})
Insert cell
uniqueGroups = [...new Set(data.nodes.map(d => d.group))]
Insert cell
viewof SELECTED_GROUPS = Inputs.select(uniqueGroups, {multiple: true, value: ["Authors", "Article Title", "Keywords Plus","Publication Year"], label: "SELECTED_GROUPS"})
Insert cell
viewof SELECTED_RATIO = Inputs.range([0, 100], {value: 10, step: 10, label: "SELECTED_RATIO %"})
Insert cell
viewof adjustment_factor = Inputs.range([0.01, 1], {value: 0.05, step: 0.01, label: "adjustment_factor"})
Insert cell
viewof FONT_SIZE = Inputs.range([10, 100], {value: 10, step: 2, label: "FONT_SIZE"})
Insert cell
viewof SCALE_TO = Inputs.range([0.1, 10], {value: 0.9, step: 0.01, label: "SCALE_TO"})
Insert cell
Insert cell
Insert cell
viewof SAMPLE_RATIO = Inputs.range([0, 100], {value: 5, step: 0.1, label: "SAMPLE_RATIO %"})
Insert cell
Insert cell
backgroundColor = '#061024';
Insert cell
Insert cell
WIDTH = 1745;
Insert cell
HEIGHT = 1668;
Insert cell
function getXPercentOfArrayFromGroup(nodes, limit) {
// let rand1 = 0.5 + Math.random() * 0.1;
// let rand2 = 0.5 - Math.random() * 0.1;
let nodesWithRands = assignRandomNumbers(nodes);
// Randomly select percent of group A nodes
const selectedNodes = nodesWithRands
.sort(
(a, b) => {
// let rand = seededRandom(seed++);
// // get more keywords filtered
// if (d.group === "Keywords Plus")
// return 10;
// if (["Publisher", "Publication Year", "Research Areas" ].includes(d.group))
// return 0; // do not filter
// else
// return rand - 0.1;

return a.rand - b.rand;
}
) // Shuffle the group A nodes randomly
.slice(0, limit);

console.log('>>> selectedNodes: ', selectedNodes.length)
return selectedNodes;
}

Insert cell
function assignRandomNumbers(nodes) {
let seed = Date.now();
const newArray = nodes.map(node => ({
...node,
rand: seededRandom(seed++)
}));

return newArray;
}
Insert cell
function seededRandom(seed) {
var x = Math.sin(seed) * 10000;
x = x - Math.floor(x);

// console.log(">>>", seed, x);
return x;
}
Insert cell
simple_config = () => {
return {
"limit": 1500,
"scale_to": 1,
"friction": 0.3,
"default_color": "#307AB2",
"authors_color": "#7763AB",
"articles_color": "#58C6C7",
"year_color": "#FF0000",
"publisher_color": "#65525F",
"area_color": "#8799A7",
"default_radius": 600,
"authors_radius": 650, 
"articles_radius": 700,
"year_radius": 400,
"publisher_radius": 200,
"charge_strength": -100,
"center_strength": 1,
"collide_radius": 13,
"font_size": 10,
"font_color": "#31ffee",
"font_opacity": 0.8,
"link_distance": 80,
"link_opacity": 0.2,
"link_opacity_keywords": 0.1,
"link_width": 0.2,
"link_color": "#31ffee",
"node_size": 2,
"node_size_max": 10,
"selected_groups": [
"Authors",
"Article Title",
"Keywords Plus",
"Publication Year", "Publisher"
],
"width": 1745,
"height": 1700
};
}
Insert cell
function initConfigFromURL() {

// default configs
let charge_strength = CHARGE_STRENGTH;
let center_strength = CENTER_STRENGTH;
let collide_radius = COLLIDE_RADIUS;
let font_size = FONT_SIZE;
let scale_to = SCALE_TO;
let sample_ratio = SAMPLE_RATIO;
let link_distance = LINK_DISTANCE;
let link_opacity = LINK_OPACITY;
let link_width = LINK_WIDTH;
let link_color = LINK_COLOR;
let selected_groups = SELECTED_GROUPS;
let width = WIDTH;
let height = HEIGHT;

// ref: https://observablehq.com/@pac02/how-to-passing-arguments-in-the-url-of-the-notebook
const urlParams = new URL(document.URL).searchParams;;

console.log('>>> urlParams: ', urlParams)

if (urlParams.has("charge_strength")) {
const paramValue = parseInt(urlParams.get("charge_strength"));
if (!isNaN(paramValue)) {
charge_strength = paramValue;
}
}

if (urlParams.has("center_strength")) {
const paramValue = parseInt(urlParams.get("center_strength"));
if (!isNaN(paramValue)) {
center_strength = paramValue;
}
}

if (urlParams.has("collide_radius")) {
const paramValue = parseInt(urlParams.get("collide_radius"));
if (!isNaN(paramValue)) {
collide_radius = paramValue;
}
}

if (urlParams.has("font_size")) {
const paramValue = parseInt(urlParams.get("font_size"));
if (!isNaN(paramValue)) {
font_size = paramValue;
}
}

if (urlParams.has("scale_to")) {
const paramValue = parseFloat(urlParams.get("scale_to"));
if (!isNaN(paramValue)) {
scale_to = paramValue;
}
}

if (urlParams.has("sample_ratio")) {
const paramValue = parseInt(urlParams.get("sample_ratio"));
if (!isNaN(paramValue)) {
sample_ratio = paramValue;
}
}

if (urlParams.has("link_distance")) {
const paramValue = parseInt(urlParams.get("link_distance"));
if (!isNaN(paramValue)) {
link_distance = paramValue;
}
}

if (urlParams.has("link_opacity")) {
const paramValue = parseFloat(urlParams.get("link_opacity"));
if (!isNaN(paramValue)) {
link_opacity = paramValue;
}
}

if (urlParams.has("link_width")) {
const paramValue = parseFloat(urlParams.get("link_width"));
if (!isNaN(paramValue)) {
link_width = paramValue;
}
}

if (urlParams.has("link_color")) {
link_color = urlParams.get("link_color");
}

if (urlParams.has("selected_groups")) {
selected_groups = urlParams.get("selected_groups").split(",");
}

if (urlParams.has("width")) {
const paramValue = parseInt(urlParams.get("width"));
if (!isNaN(paramValue)) {
width = paramValue;
}
}

if (urlParams.has("height")) {
const paramValue = parseInt(urlParams.get("height"));
if (!isNaN(paramValue)) {
height = paramValue;
}
}

return {
'charge_strength' : charge_strength,
'center_strength' : center_strength,
'collide_radius': collide_radius,
'font_size': font_size,
'scale_to': scale_to,
'sample_ratio': sample_ratio,
'link_distance': link_distance,
'link_opacity': link_opacity,
'link_width': link_width,
'link_color': link_color,
'selected_groups': selected_groups,
'width': width,
'height': height,
};
}
Insert cell
Insert cell
function mergeNodes(nodes1, nodes2) {
const mergedNodes = nodes1.concat(nodes2).reduce((acc, node) => {
const existingNode = acc.find(n => n.id === node.id);
if (!existingNode) {
acc.push(node);
}
return acc;
}, []);

return mergedNodes;
}
Insert cell
function stats(nodes) {
const groups = ["Authors", "Article Title", "Keywords Plus", "Publisher", "Publication Year", "Research Areas"];

console.log(">>> stats");

for (const group of groups) {
let count = nodes.filter(d => d.group === group).length;
console.log(" ", group, count)
}
}
Insert cell
function dispatchClickedEvent(nodeClicked, data) {
console.log(">>> dispatchClickedEvent");
console.log(data);
console.log(nodeClicked);

let authors = [];
let article_title = [];
let cited_count = [];
let publication_year = [];
let publisher = [];
let keywords_plus = [];

if (["Authors", "Article Title"].includes(nodeClicked.group)) {

let linkedNodes = findLindedNodes(nodeClicked.id);
console.log(">>>", linkedNodes);

if (nodeClicked.cited_count > 0)
cited_count.push(nodeClicked.cited_count);
else
cited_count.push("N/A");

linkedNodes.forEach(node => {
if (node.group === "Authors") {
authors.push(node.name);
}
});

linkedNodes.forEach(node => {
if (node.group === "Article Title") {
article_title.push(node.name);
}
});

linkedNodes.forEach(node => {
if (node.group === "Publication Year") {
publication_year.push(node.name);
}
});

linkedNodes.forEach(node => {
if (node.group === "Publisher") {
publisher.push(node.name);
}
});

linkedNodes.forEach(node => {
if (node.group === "Keywords Plus") {
keywords_plus.push(node.name);
}
});
}
else {

authors = ["Click an Author or Article to see more"];
article_title = ["Click an Author or Article to see more"];
cited_count = ["Click an Author or Article to see more"];
publication_year = ["Click an Author or Article to see more"];
publisher = ["Click an Author or Article to see more"];
keywords_plus = ["Click an Author or Article to see more"];

if (nodeClicked.group === "Publication Year") {
publication_year = [];
publication_year.push(nodeClicked.name);
}

if (nodeClicked.group === "Publisher") {
publisher = [];
publisher.push(nodeClicked.name);
}

if (nodeClicked.group === "Keywords Plus") {
keywords_plus = []
keywords_plus.push(nodeClicked.name);
}
}

// display modal
d3.select("#authors").text(authors.join("; "));
d3.select("#article_title").text(article_title.join(", "));
d3.select("#cited_count").text(cited_count.join(", "));
d3.select("#publication_year").text(publication_year.join(", "));
d3.select("#publisher").text(publisher.join(", "));
d3.select("#keywords_plus").text(keywords_plus.join(", "));
d3.select("#modal").classed("modal is-active", true);
}
Insert cell
function findLindedNodes(nodeId) {
const linkedNodes = [];

linkedNodes.push(data.nodes.find(node => node.id === nodeId));

data.links.forEach(link => {
if (link.source === nodeId) {
linkedNodes.push(data.nodes.find(node => node.id === link.target));
} else if (link.target === nodeId) {
linkedNodes.push(data.nodes.find(node => node.id === link.source));
}
});

return linkedNodes;
}
Insert cell
// data = FileAttachment("output-v2-20684L-FULL.json").json()
data = await d3.json("https://concora.ml-mastery.com/concora/output-v2-20684L-FULL.json")
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