Public
Edited
Sep 8, 2023
Insert cell
Insert cell
dataOrig = fetch("https://reactome.org/download/current/diagram/187037.json").then((response) => response.json())
Insert cell
Insert cell
function getNodesAndEdges(edges, links) {
const reactionNodes = []
const reactionEdges = []
edges.forEach((edge)=>
{
// the "edge" array contains reactions, a reaction is a node connected with the educts and products
// a graph node is of the form:
// {id: id, schemaClass: see reactom scheme (and reactionNode), position: tuple of x- and y- coords}
reactionNodes.push({id: edge.id, schemaClass: "reactionNode", position: edge.position})
// an edge is of the follwing shape: {source: ID-source, target: ID-target, type: Type of edge}
// TODO:
for (const input in edge.inputs) {
reactionEdges.push({source: edge.inputs[input].id, target: edge.id, type: "reactionEdge"})
}
for (const output in edge.outputs) {
reactionEdges.push({source: edge.id, target: edge.outputs[output].id, type: "reactionEdge"})
}
for (const catalyst in edge.catalysts) {
reactionEdges.push({source: edge.catalysts[catalyst].id, target: edge.id, type: "reactionEdge"})
}
}
)
// besides the edges links also contains edges to be drawn in the node link diagram
// TODO ::
const linksArray = []
links.forEach((link) => {
linksArray.push({source: link.inputs[0].id, target: link.outputs[0].id, type: "link"})
})
return {nodes: [...reactionNodes,...dataOrig.nodes], edges: [...reactionEdges, ...linksArray] };
}
Insert cell
plotData = {
const {nodes, edges} = getNodesAndEdges(dataOrig.edges, dataOrig.links)
return {nodes: nodes, links: edges}
}
Insert cell
Insert cell
schemaClasses = Array.from(new Set(plotData.nodes.map((node) => node.schemaClass))) // TODO extract schema classes
Insert cell
Insert cell
colorScaleNodes = d3.scaleOrdinal().range(d3.schemeSet3).domain(schemaClasses) //TODO generate correct colorscale
Insert cell
Insert cell
Insert cell
// based on example....
chart = {
// Specify the dimensions of the chart.
const width = 800;
const height = 800;
const radius = 5

// The force simulation mutates links and nodes, so create a copy
// so that re-evaluating this cell produces the same result.
const links = structuredClone(plotData.links);
const nodes = structuredClone(plotData.nodes);

// Create a simulation with several forces.
const simulation = d3.forceSimulation(nodes).alphaDecay(0.0001)
.force('boxing', boxingForce)
.force("link", d3.forceLink(links).id(d => d.id).strength(1.5))
.force("charge", d3.forceManyBody().strength(()=>-15).distanceMax(75))
.force("center", d3.forceCenter(width / 2, height / 2))
.force('collision',d3.forceCollide().radius((d) => radius))
.on("tick", ticked);

function blockNodeCoordinates(
radius,
node,
svgSize
) {
// This code is for keeping the nodes within the svg canvas.
// The node.x and node.y variables are set to be within the bounds of the svg canvas +/- the radius of the node.
// todo: bounding force
let x = node.x
let y = node.y

if (y - radius < 0) {
y = radius
} else if (y + radius > svgSize.height) {
y = svgSize.height - radius
}

if (x - radius < 0) {
x = radius
} else if (x + radius > svgSize.width) {
x = svgSize.width - radius
}

node.x = x
node.y = y

return node;
}
function boxingForce() {
nodes.forEach((node) => {
node = blockNodeCoordinates(2*radius, node, {width: width, height: height});
});
}
// Create the SVG container.
const svg = d3.create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [0, 0, width, height])
.attr("style", "width:auto; max-width:100%; background-color: #f0f0f0");

// Add a line for each link, and a circle for each node.
// TODO: add lines
const link = svg.append("g")
.attr("stroke", "#000")
.attr("stroke-width", 1)
.selectAll()
.data(links)
.join("line")

const node = svg.append("g")
.attr("stroke", "#000")
.attr("stroke-width", 1)
.selectAll()
.data(nodes)
.join("circle")
.attr("r", radius)
.attr("fill", d => colorScaleNodes(d.schemaClass)) //TODO;

// Set the position attributes of links and nodes each time the simulation ticks.
function ticked() {
// set the position of the links
link
.attr("x1", d => d.source.x)
.attr("x2", d => d.target.x)
.attr("y1", d => d.source.y)
.attr("y2", d => d.target.y)
node
.attr("cx", d => d.x)
.attr("cy", d => d.y);
}

// add a title to the nodes (titles are the most basic mouseover element for svgs)
//todo:
node.append("title").text(d => d.id)

// BELOW YOU DO NOT NEED TO CHANGE OR ADD ANYTHING! Feel free however to check the code if you can understand what is happening!
// Add a drag behavior.
node.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));

// Reheat the simulation when drag starts, and fix the subject position.
function dragstarted(event) {
if (!event.active) simulation.alphaTarget(0.3).restart();
event.subject.fx = event.subject.x;
event.subject.fy = event.subject.y;
}

// Update the subject (dragged node) position during drag.
function dragged(event) {
event.subject.fx = event.x;
event.subject.fy = event.y;
}

// Restore the target alpha so the simulation cools after dragging ends.
// Unfix the subject position now that it’s no longer being dragged.
function dragended(event) {
if (!event.active) simulation.alphaTarget(0);
event.subject.fx = null;
event.subject.fy = null;
}

// When this cell is re-run, stop the previous simulation. (This doesn’t
// really matter since the target alpha is zero and the simulation will
// stop naturally, but it’s a good practice.)
invalidation.then(() => simulation.stop());

return svg.node();
}
Insert cell
Insert cell
Insert cell
reactomeLayoutChart = {
// Specify the dimensions of the chart.
const width = 3500;
const height = 1653;
const radius = 10

// Create the SVG container.
const svg = d3.create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [0, 0, width, height])
.attr("style", "height: auto; width:auto; max-width:100%; background-color: #f0f0f0");

// Add a line for each link, and a circle for each node.
// TODO add lines nodes at the predefined positions
const link = svg.append("g")
.attr("stroke", "#000")
.attr("stroke-width", 1)
.selectAll()
.data(plotData.links)
.join("line")
.attr("x1", d => plotData.nodes.find((node) => node.id == d.source).position.x)
.attr("x2", d => plotData.nodes.find((node) => node.id == d.target).position.x)
.attr("y1", d => plotData.nodes.find((node) => node.id == d.source).position.y)
.attr("y2", d => plotData.nodes.find((node) => node.id == d.target).position.y)

const node = svg.append("g")
.attr("stroke", "#000")
.attr("stroke-width", 1)
.selectAll()
.data(plotData.nodes)
.join("circle")
.attr("cx", d => d.position.x)
.attr("cy", d => d.position.y)
.attr("r", radius)
.attr("fill", d => colorScaleNodes(d.schemaClass)) //TODO;


return svg.node();
}
Insert cell
Insert cell
Insert cell
// as the nodes are stored in an array, finding a specific node id is very tedious. Thus, we generate a map (of key:value pairs) in order to link the id of a entitiy with the index in the node array
function mapNodeId2Idx(nodes){
const map = new Map();
nodes.forEach( (value, index) => map.set(value.id, parseInt(index)));
return map;
}
Insert cell
Insert cell
adjacenyMatrix = {
const width = 1000;
const height = 1000;
const margin = {top: 50, right: 0, bottom: 0, left: 50};
const svg = d3.create("svg")
.attr("width", width+margin.left+margin.right)
.attr("height", height+margin.top+margin.bottom)
.attr("style", "background-color: white");
// this generates a bandscale mapping indices to width positions
let x = d3.scaleBand()
.range([0, width])
.paddingInner(.2)
.align(0)
.domain(d3.range(plotData.nodes.length));
// this generates a mapping from node to index
let nodeIdMap = mapNodeId2Idx(plotData.nodes);
let matrix = [] //todo fill the matrix such that no adjacency: 0, same node (diagonal) 1, adjacency: 2
let matrixGroup = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");

//this function appends a group for each row and translates it the correct amount in y-direction, using each allows us to execute a function for each row in our matrix
let row = matrixGroup.selectAll('g.row')
.data(matrix)
.enter().append('g')
.attr('class', 'row')
.attr('transform', function (d, i) { return 'translate(0,' + x(i) + ')'; })
.each(makeRow);

function makeRow(rowData) {
let cell = d3.select(this).selectAll('rect.cell')
//todo draw the cells
}
//todo: add text to the axes
return svg.node()
}

Insert cell
Insert cell
import {Swatches} from "@d3/color-legend"
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