Public
Edited
Mar 2, 2023
Insert cell
Insert cell
Insert cell
Insert cell
// save clicks version
chart = {
// clear previous selection
button;
// create the graph html layout
const saveHTML = html`<button class="button_click" id="add">Add a Bar</button>`;
const svgHTML = html`<svg width="${width}" height="${height}"></svg>`;
const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height])
.style("width", "100%")
// .style("background-color", "green")
.style("height", "680")
.attr("font-size", 10)
.attr("font-family", "sans-serif")
.attr("align", "center")
.attr("justify-content", "center")
.attr("id", "chart")
.attr("text-anchor", "middle")
svg.append("style").text(`

current_link_style {
stroke: lightgrey;
stroke-opacity: 1;
}
`);
var background = svg.append("rect")
.attr("width", width)
.attr("height", height)
.attr("fill", "#044B9466")
.attr("fill-opacity", "0.0")
.on("dblclick", remove_textblock)
// .on("click", dblclick)
const g = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")") ;
// define data
const links = data.links.map(d => Object.create(d));
const nodes = data.nodes.map(d => Object.create(d));

// disjoint graph settings
const simulation = d3.forceSimulation(nodes)
.force("link", d3.forceLink(links).id(d => d.id).distance(90))
// .force("center", d3.forceCenter(width / 2, height / 2))
.force("charge", d3.forceManyBody().strength(-400))
.force("x", d3.forceX())
.force("y", d3.forceY());
// zoom options - https://stackoverflow.com/questions/64193193/translate-zoom-after-domain-change
svg.call(d3.zoom()
.extent([[margin.right,margin.top], [width+margin.right, height+margin.top]])
.on("zoom", zoomed)
)
.on("dblclick.zoom", null);
//pressed markers
svg.append("svg:defs").selectAll("marker")
.data(["end"])
.enter().append("svg:marker")
.attr("id", String)
.attr("viewBox", "0 -5 10 10")
.attr("refX", 15)
.attr("refY", 0)
.attr("markerWidth", 4.5)
.attr("markerHeight", 3)
.attr("fill", "lightgrey")
.attr("orient", "auto")
.append("svg:path")
.attr("d", "M0,-5L10,0L0,5");

svg.append("svg:defs").selectAll("marker")
.data(["end2"])
.enter().append("svg:marker")
.attr("id", String)
.attr("viewBox", "0 -5 10 10")
.attr("refX", 17)
.attr("refY", 0)
.attr("markerWidth", 5)
.attr("markerHeight", 4)
.attr("fill", "lightgrey")
.attr("orient", "auto")
.append("svg:path")
.attr("d", "M0,-5L10,0L0,5");
//regular markers
svg.append("svg:defs").selectAll("marker")
// .data(["end3"])
// .enter().append("svg:marker")
.data(types)
.join("marker")
.attr("id", d => `arrow-${d}`)
// .attr("id", "marker_regular")
.attr("viewBox", "0 -5 10 10")
.attr("refX", 17)
.attr("refY", 0)
.attr("markerWidth", 5)
.attr("markerHeight", 4)
.attr("orient", "auto")
.append("svg:path")
// .attr("fill", "red")
.attr("fill", color3)
.attr("d", 'M0,-5L10,0L0,5');

// create text from d.sentence
let current_link = undefined;
let format = d3.format(",d")
let text_blocks_number = 0
let y_indent = 80
// let textblock = svg.selectAll("#details-popup");
let width_default = width/2.5
let link_info_text = '';
function selectOccupation(i,d){
// prevent double selection
if (d3.select(this).style('stroke-width') == "5"){
return;
// current_link = d3.select(this).attr("fill", "none")
// .style("stroke-width", 3)
// .style("stroke", d => color3(d.predicate))
// .attr("marker-end", d => `url(${new URL(`#arrow-${d.predicate}`, location)})`);

} else{

// function for the first entry
if(current_link == undefined){
let textblock = svg.selectAll("#details-popup")
.data([d])
.enter()
.append("g")
.attr("id", "details-popup")
.attr("font-size", 14)
.attr("font-family", "sans-serif")
.attr("text-anchor", "start")
.attr("transform", d => `translate(10, 20)`);
text_blocks_number += 1 ;
// link_info_text = link_info_text + d.sentence
link_info_text = function(d) { return "Item count: " + d.sentence }
// add rectangle around the text
textblock.append("rect")
.attr("x", 0)
.attr("width", wrapping)
.attr("height", height/6+29.5)
.attr("fill", "grey")
.attr("occupacy", "grey")
.attr("fill-opacity", "0.2")
.attr("y", y_indent-70)
textblock.append("text")
.attr("y", y_indent)
.attr("x", 0)
.text(d => text_blocks_number+ ": " + d.sentence)
.call(wrap2, wrapping, 0.35, 1.3, 1, true, true);
current_link = d3.select(this)
.style("stroke", function (d, i) {
return "#d1d1d1";
})
//
current_link.style("stroke-width", "5");
current_link.attr("marker-end", "url(#end)")
.attr("id", "link_all")
current_link.classed("current_link_style", true);
// .attr("class", "current_link_style")
;
// current_link.attr("marker-end", d => `url(${new URL(`#arrow-${d.type}`, location)})`);

// function for the second and subsequent entries
} else{
link_info_text = link_info_text + d.sentence;
//define new selection
let textblock = svg.selectAll("#details-popup");
//define new selection
textblock.data([d])
.enter()
.append("g")
.attr("id", "details-popup")
.attr("font-size", 14)
.attr("font-family", "sans-serif")
.attr("text-anchor", "start")
.attr("transform", d => `translate(10, 20)`);
text_blocks_number += 1 ;
// if (y_indent+ 113*(text_blocks_number-1) -41 - 112.5 >= height-180){
if (text_blocks_number >5){
textblock.append("text")
.attr("y", y_indent-55)
.attr("x", 0 )
.attr("font-size", 14)
.attr("fill", "red")
.text("Please clear links info by double clicking on the background")
}else{
current_link = d3.select(this).style("stroke", "#d1d1d1")
.attr("id", "link_all")
current_link.classed("current_link_style", true)
// .attr("class", "current_link_style");
current_link.style("stroke-width", "5");
// current_link.attr("marker-end", "url(#end)");
textblock.append("rect")
.attr("x", 0)
.attr("width", wrapping)
.attr("height", height/6)
.attr("fill", "grey")
.attr("occupacy", "grey")
.attr("fill-opacity", "0.2")
.attr("y", y_indent+ 113*(text_blocks_number) -41 - 112.5)
textblock.append("text")
.attr("y", y_indent+ 113*(text_blocks_number) -1 - 112.5)
.attr("x", 0)
.text(d => text_blocks_number+ ": " + d.sentence)
// .text("test" + text_blocks_number)
.call(wrap2, wrapping, 0.35, 1.3, 1, true, true);
} }
}
}
links.sort(function(a,b) {
if (a.source > b.source) {return 1;}
else if (a.source < b.source) {return -1;}
else {
if (a.target > b.target) {return 1;}
if (a.target < b.target) {return -1;}
else {return 0;}
}
});
// any links with duplicate source and target get an incremented 'linknum'
for (var i=0; i<links.length; i++) {
if (i != 0 &&
links[i].source == links[i-1].source &&
links[i].target == links[i-1].target) {
links[i].linknum = links[i-1].linknum + 1;
}
else {links[i].linknum = 1;};
};

let link = g.append("g")
.attr("fill", "none")
.attr("stroke-width", 3)
.selectAll("path")
.data(links)
.join("path")
.attr("stroke", d => color3(d.predicate))
//.attr("stroke","lightgrey")
.attr("stroke-opacity","0.8")
.attr("id", "link_all")

// .attr("marker-end", d => `url(${new URL(`#arrow-${d.predicate}`, location)})`)
// .attr("id", "link_all");
// .attr("marker-end", d => "url(#end3)")
// link.on("click", selectOccupation);
link
.on('mouseover', function(d) {
d3.select(this)
.attr("stroke-width", 3)
.style("stroke-opacity", 0.5)
})
.on('mouseout', function(d) {
d3.select(this).style("stroke-opacity", "1")
.attr("stroke-width", 3)
});
// link.append("title")
// .text(d => d.predicate +": " + d.sentence);
// .text(d => d.linknum);
// define node options
const node = g.append("g")
.attr("class", "nodes")
.selectAll("g")
.data(nodes)
.enter().append("g")
.classed("node", true)
.classed("fixed", d => d.fx !== undefined);
node.on("mouseover", node_click)
function node_click (evt, d) {

// d3.selectAll('.current_link_style').style("stroke", "blue")
d3.selectAll("#link_all")
// .style("marker-end", d => "url(#end2)")
.transition().duration(300)
.style("stroke", "lightgrey")
.filter(l => l.source.id === d.id || l.target.id === d.id)
// .style("marker-end", d => `url(${new URL(`#arrow-${d.predicate}`, location)})`)
.style("stroke", "#d5f2bd")
// .style("stroke", l => color3(l.predicate))
d3.selectAll("#current_link")
.style("stroke-width", 3)
.style("stroke","#d62728")
// .attr("marker-end", d => `url(${new URL(`#arrow-${d.predicate}`, location)})`);
}

node.on("mouseout", dblclick)
node.on("dblclick", dblclick)
function dblclick (evt, d) {
// link
// .attr("marker-end", d => `url(${new URL(`#arrow-${d.predicate}`, location)})`)
// .attr("stroke", l => color3(l.predicate))
// ;
d3.selectAll("#link_all")
.transition().duration(300)
// .style("marker-end", d => `url(${new URL(`#arrow-${d.predicate}`, location)})`)
// .style("stroke", l => color3(l.predicate))
.style("stroke", "#d5f2bd")
.filter(".current_link_style")
.style("stroke", "lightgrey")
// .style("stroke", function (l) {
// if (d3.select(this).attr('stroke-width') !== 5) {
// return color3(l.predicate)
// }else {
// return "lightgrey"
// }})
// return color3(d.predicate)
// }else {
// return "lightgrey"}
// })
};

function remove_textblock(){
d3.select("#details-popup").remove()

d3.selectAll("#current_link")
.attr("id","#link_all2")
.style("stroke-width", 3)
.style("stroke", d => color3(d.predicate))
// .attr("marker-end", d => `url(${new URL(`#arrow-${d.predicate}`, location)})`)
//
current_link = undefined;
text_blocks_number = 0
y_indent = 80
d3.selectAll("#link_all").classed("current_link_style", false)
d3.selectAll("#link_all")
.style("stroke-width", 3)
// .attr("stroke", "green")
.style("stroke", d => color3(d.predicate))
// .attr("marker-end", d => `url(${new URL(`#arrow-${d.predicate}`, location)})`);

link
// .attr("marker-end", d => `url(${new URL(`#arrow-${d.predicate}`, location)})`)
.attr("stroke", l => color3(l.predicate))
;

}

// define circles options
const circles = node.append("circle")
// .attr("stroke", "#fff")
// .style("stroke","#d62728")
.style("stroke","darkslategray")
.style("stroke-opacity",0.85)
.attr("stroke-width", d => stroke_width_key(d.key_node))
// .attr("stroke-width", 2)
// .attr("border-width", 1)
// .attr("border-color", "grey")
.attr("r", d => radius_width_group(d.group))
.attr("fill", d =>color(d.group))
.attr("fill-opacity", "0.6")

// yield svg.node();
const drag = d3
.drag()
.on("start", dragstart)
.on("drag", dragged);

node.call(drag)
function click(event, d) {
delete d.fx;
delete d.fy;
d3.select(this).classed("fixed", false);
simulation.alpha(1).restart();
}

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

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

node.append("text")
.text(function(d) {
return d.label;
})
// .attr('font-size', 14)
.style("font-weight", function (font_weigth_width_group2) {
return font_weigth_width_group2.key_node ==1 || font_weigth_width_group2.group == "go_molecular_function"|| font_weigth_width_group2.group =="go_biological_process"|| font_weigth_width_group2.group == "go_cellular_component" ? "700" : "400";})

.style("font-size", function (font_size_width_group2) {
return font_size_width_group2.key_node ==1 || font_size_width_group2.group == "go_molecular_function"|| font_size_width_group2.group =="go_biological_process"|| font_size_width_group2.group == "go_cellular_component" ? 15.5 : 13;})

// .attr("font-weight", d => font_weigth_width_group(d.group))
.attr('x', 13.5)
// .attr('y', -10.5)
.attr("y", function (upper_text) {
return upper_text.group == "go_molecular_function"|| upper_text.group =="go_biological_process"|| upper_text.group == "go_cellular_component" ? -18.7 : -10.5;})
node.append("title")
.text(d => d.id);

simulation.on("tick", () => {
link.attr("d", linkArc);
node.attr("transform", d => `translate(${d.x},${d.y})`); });

function linkArc(d) {
var dx = d.target.x - d.source.x,
dy = d.target.y - d.source.y,
x1 = d.source.x,
y1 = d.source.y,
x2 = d.target.x,
y2 = d.target.y,
dr = (d.linknum !=1)?Math.sqrt(dx * dx + dy * dy):0,
arc = dr*d.linknum/2;
let drx = arc
let dry = arc
let xRotation = 0, // degrees
largeArc = 0, // 1 or 0
sweep = 1; // 1 or 0
// // Self edge.
if ( x1 === x2 && y1 === y2 ) {
// // Fiddle with this angle to get loop oriented.
// xRotation = -45;
// // Needs to be 1.
largeArc = 1;
// // Change sweep to change orientation of loop.
// sweep = 0;
// // Make drx and dry different to get an ellipse
// // instead of a circle.
drx = 33;
dry = 20;
// // For whatever reason the arc collapses to a point if the beginning
// // and ending points of the arc are the same, so kludge it.
x2 = x2 + 1;
y2 = y2 + 1;
}
return "M" + x1 + "," + y1 + "A" + drx + "," + dry + " " + xRotation + "," + largeArc + "," + sweep + " " + x2 + "," + y2;

// return "M" + x1 + "," + y1 +
// "A" + drx + "," + dry + " 0 0,1 " + x2 + "," + y2;
};
// function linkArc(d) {
// var dx = d.target.x - d.source.x,
// dy = d.target.y - d.source.y,
// x1 = d.source.x,
// y1 = d.source.y,
// x2 = d.target.x,
// y2 = d.target.y,
// // dr = (d.linknum !=1)?Math.sqrt(dx * dx + dy * dy +d.linknum*10):0;
// dr = (d.linknum !=1)?Math.sqrt(dx * dx + dy * dy):0,
// arc = dr*d.linknum/2;
// return "M" + d.source.x + "," + d.source.y +
// "A" + arc + "," + arc + " 0 0,1 " + d.target.x + "," + d.target.y;
// };

invalidation.then(() => simulation.stop());
//Zoom functions
function zoomed({transform}) {
g.attr("transform", transform)
}

return svg.node();
}
Insert cell
// margin = ({top: 305, right: 5, bottom: 5, left: 460})
margin = ({top: 0, right: 0, bottom: 0, left: 0})
Insert cell
Insert cell
height = 680
Insert cell
data = FileAttachment("json_G.json").json()
// data = FileAttachment("test.json").json()
Insert cell
types = Array.from(new Set(links.map(d => d.predicate)))
Insert cell
Insert cell
measureWidth = {
const context = document.createElement("canvas").getContext("2d");
return text => context.measureText(text).width;
}
Insert cell
radius_width_group = d3.scaleOrdinal()
.domain(["compound", "gene", "go_molecular_function", "go_biological_process", "go_cellular_component"])
.range(["10", "10", "20","20","20"]);
Insert cell
font_weigth_width_group = d3.scaleOrdinal()
.domain(["compound", "gene", "go_molecular_function", "go_biological_process", "go_cellular_component"])
.range(["400", "400", "700","700","700"]);
Insert cell
stroke_width_key = d3.scaleOrdinal()
.domain(["1", undefined])
.range(["4.5", "0"]);
Insert cell
color3 = d3.scaleOrdinal()
.domain(["Activation", "Inhibition", "IncreaseAmount", "DecreaseAmount", "Associated", "HAS_A_TRIAL"])
.range(["#d5f2bd", "#edc2bb", "#d5f2bd","#edc2bb","#f5d0b0", "#c3c3e3"]);
//"#d5f2bd"

Insert cell
color2 = d3.scaleOrdinal(types, d3.schemeCategory10)
Insert cell
node_types = Array.from(new Set(data.nodes.map(d => d.group)))
Insert cell
color = d3.scaleOrdinal()
.domain(["compound", "gene", "go_molecular_function", "go_biological_process", "go_cellular_component"])
.range(["#ff7f0e","#1f77b4", "#d62728","#d62728", "#d62728"]);
//"#1f77b4"
//,"#2ca02c", "#d62728"
//"#2ca02c","#2ca02c", "#2ca02c"
// const scale = d3.scaleOrdinal(d3.schemeCategory10);
// "chemical", "gene","process" ,"disease"
// return d => scale(d.group);

Insert cell
// d3.selectAll("#link_all")
// .style("marker-end", d => `url(${new URL(`#arrow-${d.predicate}`, location)})`)
// //.style("stroke", l => color3(l.predicate))
// .style("stroke", function (l) {
// if (l=== current_link) {
// return color3(l.predicate)
// }else {
// return "lightgrey"
// }})
Insert cell
d3.schemeCategory10
Insert cell
// drag = simulation => {
// function click(event, d) {
// delete d.fx;
// delete d.fy;
// d3.select(this).classed("fixed", false);
// simulation.alpha(1).restart();
// }

// function dragstart() {
// d3.select(this).classed("fixed", true);
// }

// function dragged(event, d) {
// d.fx = clamp(event.x, 0, width);
// d.fy = clamp(event.y, 0, height);
// simulation.alpha(1).restart();
// }
// }
Insert cell
d3 = require("d3@6")
Insert cell
function clamp(x, lo, hi) {
return x < lo ? lo : x > hi ? hi : x;
}
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