Published
Edited
Jun 15, 2022
1 fork
1 star
Insert cell
Insert cell
data2 = FileAttachment("STM@1.json").json()
Insert cell
data = await d3.json(url);
Insert cell
viewof range = slider({
min: 1,
max: 200,
//max: d3.format(".0f")(d3.max(links, d => d.MR)),
step: 1,
value: 200,
//value: d3.format(".0f")(d3.max(links, d => d.MR)),
theme: "default-round",
background: {
type: "progress",
colors: ["#4361ee", "#4cc9f0", "#93959F"]
},
format: ",",
title: "MR threshold"
})
Insert cell
buffer
Insert cell
Insert cell
chart = {
//const links = data.links.map(d => Object.create(d));
//const nodes = data.nodes.map(d => Object.create(d));
const buffer = fetchProgress("https://raw.githubusercontent.com/datadesk/cpi/d582252c644b90d48918a4cba951baf3c03c9629/cpi/cpi.db", {mode: "cors"})
nodes.forEach( function(element) {
element.Degree = findDegree(flinks).find(o => o.key === element.id)
});
let gnodes = (range+1)? nodes.filter(d=> d.Degree.value > 2 ) : nodes


const simulation = d3.forceSimulation(gnodes)
.force("link", d3.forceLink(glinks).id(d => d.id)
.strength(function (d) {return (4/d.MR);})
//.distance(5)
.iterations(1)
)
.force("charge", d3.forceManyBody()
.strength(- 200 * d3_array.count(nodes, d => d.index))
)
.force("collide", d3.forceCollide(d3_array.count(nodes, d => d.index))
.strength(1)
.iterations(1)
//.radius(1)
)
//.force("center", d3.forceCenter()
// .x(width / 2)
// .y(height / 2))
.force("x", d3.forceX()
.strength(0.1)
.x(width / 2))
.force("y", d3.forceY()
.strength(0.1)
.y(height / 2));

const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height]);
const g = svg.append("g").attr("transform", "1")
const link = g.append("g")
.attr("stroke", "#999")
.attr("stroke-opacity", 1)
.selectAll("line")
.data(glinks)
.join("line")
.attr("stroke-width", d => (15/Math.sqrt(d.MR)))
.style("stroke", color_link);
const globalNode = g.append("g")
//.attr("stroke", "#fff")
//.attr("stroke-width", 0.05 * (d3_array.count(nodes, d => d.index)));
const tooltip = d3tip()
.style('color', 'white')
//.style('border', 'solid 2px black')
.style('background-color', 'rgba(0,0,0,0.5)')
.style('border-radius', '4px')
.style("padding", "6px")
.style('float', 'left')
.style('font-family', 'Nunito')
.style("font-size", "14px")
.offset([-10,50])
.html(d => `<div style='float: right'>
Gene Name: ${d.full_name} <br/>
AGI Locus: ${d.id} <br/>
GO: ${d.GO.map(a => `<li>${a}`)}<br/>
</div>`)
svg.call(tooltip)
const node = globalNode
.selectAll("circle")
.data(gnodes)
.join("circle")
//.attr("r", d => 0.3 * Math.sqrt(d.connectivity -1) * (d3_array.count(nodes, d => d.index)))
.attr("r", d => (d3_array.count(nodes, d => d.index)))
.attr("fill", function(o) { console.log(o.GO.indexOf(dropdown))
if (o.GO.indexOf(dropdown) != -1){return GO_json[dropdown].Color}else{return "white"}})
.attr("stroke", color_border)
.attr("stroke-width", 0.2 * (d3_array.count(nodes, d => d.index)))
.call(drag(simulation))
.on('mouseover.fade', fade(0.1))
.on('mouseover', tooltip.show)
.on('mouseout.fade', fade(1))
.on('mouseout', tooltip.hide)
.on('dblclick', go_to_node);

function go_to_node(d) {
//window.location = "http://plantregeneration.snu.ac.kr/regenomics/single-gene-coex-ajax/?dataset_B=all&query_B=";
const dataset = $('#dataset-B').val(); // get the selected subject ID from the HTML dropdown list \
const species = new URL(location.href).searchParams.get('species')
console.log(dataset)
$.ajax({ // initialize an AJAX request
type: "GET",
url: "dblclick_single_gene_coex_ajax",
data: {
'dataset_B': dataset,
'query_B' : d.id,
'species' : species
},
success: function (data) { // `data` is from view function
$('#single-coex-network').html(data);
}
})
}

const textElements = g.append('g')
.selectAll('text')
.data(gnodes)
.enter().append('text')
.text(node => node.alias)
.attr('font-size', d => 1 * (d3_array.count(nodes, d => d.index)))
.attr("font-family", "Nunito")
.attr("font-weight", "bold")
.attr("fill", "#000000")
//.attr('dx', d => -0.5 * Math.sqrt(d.connectivity -1) * (d3_array.count(nodes, d => d.index)))
.attr('dy', d => 2 * (d3_array.count(nodes, d => d.index)))
.attr("text-anchor", "middle")
.attr("pointer-events", "none");

function fade(opacity) {
return d => {
node.style('opacity', function (o) { return isConnected(d, o) ? 1 : opacity
});
textElements.style('opacity', function (o) { return isConnected(d, o) ? 1 : opacity
});
link.style('stroke-opacity', o => (o.source === d || o.target === d ? 1 : opacity));
if(opacity === 1){
node.style('opacity', 1)

link.style('stroke-opacity', 1)
}
};
}


const linkedByIndex = {};
flinks.forEach(d => {
linkedByIndex[`${d.source},${d.target}`] = true;
});
console.log(linkedByIndex)

function isConnected(a, b) {
return linkedByIndex[`${a.id},${b.id}`] || linkedByIndex[`${b.id},${a.id}`] || a.index === b.index;
}

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);
textElements
.attr("x", d => d.x)
.attr("y", d => d.y);
node
.attr("cx", d => d.x)
.attr("cy", d => d.y);
});

invalidation.then(() => simulation.stop());


let zoomLvl = 0.5;
let lastK = 0;
svg.call(d3.zoom()
.scaleExtent([0.5,10])
.on("zoom", zoomed))
.on("dblclick.zoom", null);

function zoomed() {
let e = d3.event
if(Math.log2(e.transform.k) >= 0.5 && lastK != e.transform.k){
lastK = e.transform.k;
console.log("zoomed");
zoomLvl =Math.log2(e.transform.k);
console.log(zoomLvl)
globalNode.attr("stroke-width", zoomLvl );
link.attr("stroke-width", d => (15/Math.sqrt(d.MR)) * zoomLvl);
}
g.attr("transform", e.transform);
}
return svg.node();
}
Insert cell
Insert cell
viewof buffer = fetchProgress("https://raw.githubusercontent.com/datadesk/cpi/d582252c644b90d48918a4cba951baf3c03c9629/cpi/cpi.db", {mode: "cors"})
Insert cell
Insert cell
drag = simulation => {

function dragstarted(d) {
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
// simulation.stop();
d.fx = d.x;
d.fy = d.y;
}

function dragged(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
}

function dragended(d) {
if (!d3.event.active) simulation.alphaTarget(0);
// simulation.resume();
// d.fx = null;
//d.fy = null;
d.fixed = false;


}

return d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended);
}
Insert cell
legend_nodes = swatches({
colour: d3.scaleOrdinal(['GO: ' + dropdown], [GO_json[dropdown].Color]),
labelFont: "bold 15px Nunito",
swatchRadius: 10,
})
Insert cell
legend_nodes_query = swatches_stroke({
colour: d3.scaleOrdinal(['Query gene', 'Co-expressed gene'], ['white', 'white']),
border: d3.scaleOrdinal(['Query gene', 'Co-expressed gene'], ['#DB161F', 'black']),
labelFont: "bold 15px Nunito",
swatchRadius: 10,
})
Insert cell
data.nodes
Insert cell
Insert cell
GO_json = FileAttachment("GOSLIM_color@10.json").json()
Insert cell
GO_array = data.nodes.map(a => a.GO).concat();
Insert cell
GO_merged = [].concat.apply([], GO_array);
Insert cell
GO_unique = [...new Set(GO_merged)].sort()
Insert cell
data.nodes[0].GO
Insert cell
color_border = {
const scale = d3.scaleOrdinal()
.domain(["Query", "Coex"])
.range(["#DB161F", "black"]);
return d => scale(d.group);
}
Insert cell
Insert cell
Insert cell
d3.format
Insert cell
DOM.download(serialize(chart), data.nodes[0].id , "Download as SVG")
Insert cell
download = DOM.download(() => rasterize(chart), undefined, "Save as PNG")
Insert cell
nodes = data.nodes.map(d => Object.create(d));
Insert cell
fnodes = {
let nodes = data.nodes.map(d => Object.create(d));
// createO = (d) => Object.create(d); // refactoring the above

nodes = (range)? nodes.filter(d=> d.connectivity >= 1 ) : nodes
return nodes;
}
Insert cell
node_count = d3_array.count(nodes, d => d.index)
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
url = "https://gist.githubusercontent.com/bumbeishvili/5ecd2e6066065980d7171aab23f8752b/raw/9ca96478bc6755e7894a809be9b810aa18a2545f/example_output.json"
Insert cell
height = 1000
Insert cell
width = 1200
Insert cell
function rasterize(svg) {
let resolve, reject;
const promise = new Promise((y, n) => (resolve = y, reject = n));
const image = new Image;
image.onerror = reject;
image.onload = () => {
const rect = svg.getBoundingClientRect();
const context = DOM.context2d(rect.width, rect.height);
context.drawImage(image, 0, 0, rect.width, rect.height);
context.canvas.toBlob(resolve);
};
image.src = URL.createObjectURL(serialize(svg));
return promise;
}
Insert cell
serialize = {
const xmlns = "http://www.w3.org/2000/xmlns/";
const xlinkns = "http://www.w3.org/1999/xlink";
const svgns = "http://www.w3.org/2000/svg";
return function serialize(svg) {
svg = svg.cloneNode(true);
const fragment = window.location.href + "#";
const walker = document.createTreeWalker(
svg,
NodeFilter.SHOW_ELEMENT,
null,
false
);
while (walker.nextNode()) {
for (const attr of walker.currentNode.attributes) {
if (attr.value.includes(fragment)) {
attr.value = attr.value.replace(fragment, "#");
}
}
}
svg.setAttributeNS(xmlns, "xmlns", svgns);
svg.setAttributeNS(xmlns, "xmlns:xlink", xlinkns);
const serializer = new window.XMLSerializer();
const string = serializer.serializeToString(svg);
return new Blob([string], { type: "image/svg+xml" });
};
}
Insert cell
d3 = require("d3@5")
Insert cell
d3tip = require('d3-tip')
Insert cell
import {legend} from "@d3/color-legend"
Insert cell
import {swatches} from "@bayre/svg-swatches"
Insert cell
import {getLabelLength} from "@bayre/svg-swatches"
Insert cell
function cumsum(values) {
let sum = 0;
return Float64Array.from(values, v => sum += v);
}
Insert cell
//modified from "@bayre/svg-swatches"

function swatches_stroke({
colour,
border,
swatchRadius = 6,
swatchPadding = swatchRadius * (2/3),
labelFont = "12px sans-serif",
labelFormat = x => x,
labelPadding = swatchRadius * 1.5,
marginLeft = 0
} = {}) {
const spacing = colour
.domain()
.map(d => labelFormat(d))
.map(d => getLabelLength(d, labelFont) + (swatchRadius * 2) + swatchPadding + labelPadding)
.map((_, i, g) => cumsum(g)[i] + marginLeft)
const width = d3.max(spacing)
const height = swatchRadius * 2 + swatchPadding * 2
const svg = d3.create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [0, 0, width, height])
.style("overflow", "visible")
.style("display", "block");
const g = svg
.append("g")
.attr("transform", `translate(0, ${height / 2})`)
.selectAll("g")
.data(colour.domain())
.join("g")
.attr("transform", (d, i) => `translate(${spacing[i - 1] || marginLeft}, 0)`);
g.append("circle")
.attr("fill", colour)
.attr("stroke", border)
.attr("stroke-width", swatchRadius/5)
.attr("r", swatchRadius)
.attr("cx", swatchRadius)
.attr("cy", 0);
g.append("text")
.attr("x", swatchRadius * 2 + swatchPadding)
.attr("y", 0)
.attr("dominant-baseline", "central")
.style("font", labelFont)
.text(d => labelFormat(d));
return svg.node()
}
Insert cell
import {fetchProgress} from "@mbostock/fetch-progress"
Insert cell
import {slider} from '@bartok32/diy-inputs'
//import {slider} from '@jashkenas/inputs'
Insert cell
import {rangeSlider} from '@mootari/range-slider'
Insert cell
import {ramp} from "@d3/color-schemes"
Insert cell
d3_array = require("d3-array@>=2.1")
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