Public
Edited
Apr 20, 2023
Insert cell
Insert cell
data
Insert cell
chart = {
let marginTop =30
let marginLeft = 10;

const score_names = ['initial',
'count'
]
const score_names_true = ['Initial',
'Number of \ncomparisons'
]
// const newColumnsCount = data.name.length - 2;
const newColumnsCount =2
const columnSpacing = 100;
let columns = []

columns = score_names.map((name, index) => {
return {
label: score_names_true[index],
index:index,
key: name,
format: (value, d) => value ? format(value) : "",
x: 950 + (index - 2) * columnSpacing,
fill: (value) => null,
};
});
const container = d3.create("div")
.style("width", "100%") // Set the width of the div
.style("height", "1000px") // Set the width of the div
.style("overflow-x", "scroll"); // Enable horizontal scrolling
const totalWidth = columns.reduce((acc, col) => acc + col.x, 0) + 150;
const svg = d3.create("svg")
.classed("CollapibleIndentedTree",true)
.attr("width", totalWidth) // Set the width of the SVG element
.style("overflow", "visible");
container.append(() => svg.node());

const gLink = svg.append("g")
.attr("name", "link")
.attr("fill", "none")
.attr("stroke", "#999")
.attr("transform", "translate(" + marginLeft + "," +marginTop + ")");
const gIcon = svg.append("g")
.attr("name", "icon")
.attr("fill", "grey")
.attr("stroke", "none")
.attr("transform", "translate(" + marginLeft + "," +marginTop + ")");
const gNode = svg.append("g")
.attr("class", "gNode")
// .attr("transform", `translate(0, ${marginTop})`)
.attr("transform", "translate(" + marginLeft + "," +marginTop + ")");
// .attr("transform", (d) => `translate(0, ${marginTop}, 0, ${marginLeft})`);
;
//put index to each tree node using eachBefore
function indexEachBefore(r){
let i=0;
r.eachBefore(n => {
n.index = i++;
})
return r
}
function sortTree(source) {
if (source.children) {
source.children.sort((a, b) => {
// Sort in decreasing order
return data_attr[b.data.efo_true]["count"] - data_attr[a.data.efo_true]["count"]
});
source.children.forEach(child => sortTree(child));
}
}
function update(source){
const duration = d3.event && d3.event.altKey ? 2500 : 250;
const nodes = source.descendants()
const links = source.links();
indexEachBefore(source);
const height = (nodes.length + 1) * nodeSize
const transition = svg.transition()
.duration(duration)
.attr("viewBox", [0, -nodeSize * 3 / 2, totalWidth, height])
.attr("height", height + marginTop)
.tween("resize", window.ResizeObserver ? null : () => () => svg.dispatch("toggle"));
const node = gNode.selectAll("g")
.data(nodes, d => d.id);
const nodeEnter = node.enter().append("g")
.attr("transform", d => `translate(0,${d.index * nodeSize})`)
.attr("fill-opacity", 0)
.attr("stroke-opacity", 0)
// .attr("cursor", d => _.has(d,'collapsed') ?"pointer":null)
.attr("pointer-events", "all")
// .on("dblclick", d => {
// uncollapseNodesByEfoTrue(d.data.efo_true, root);
// update(root);
// })
.on("click", d => {
const nodesWithSameName = findNodesByName(d.data.efo_true, data);


if (!_.has(d, 'collapsed')) return;
d.collapsed = !d.collapsed;
d.children = d.children ? null : d._children;

if (d.children) {
sortTree(d);
}
update(source);

})
.on('mouseover', function(d) {
d3.select(this)
.attr("fill", "darkblue")
.attr("font-weight", 700)
// .attr("fill-opacity", 0.5)
// .classed('hover', true).style('opacity', 1);
})
.on('mouseout', function(d) {
d3.select(this)
.attr("fill", "")
.attr("font-weight", 300)
// .attr("fill-opacity",1)
// .classed('hover', false).style('opacity', 1)
});

nodeEnter.append("circle")
.attr("cx", d => d.depth * nodeSize)
.attr("r", d => _.has(d,'collapsed')?0:4)
.attr("fill", "grey")

nodeEnter.append("rect")
.attr("dy", "0.32em")
.attr("x", d => d.depth * nodeSize + 6)
.attr("y", "-0.62em")
.attr("width", "100%").attr("height", nodeSize)
.attr("fill", "none")
// .attr("transform", d => `translate(${d.x},${d.y})`); // add this line to update the position of the rectangle

function assignFontWeight(node) {
if (node.depth ==1) {
return 700;
} else {
return 300;
}
}

nodeEnter.append("text")
.attr("dy", "0.32em")
.attr("x", d => d.depth * nodeSize + 8)
.attr("font-weight", d=> assignFontWeight(d))
.text(d => d.data.name)

nodeEnter.append("title")
.text(d => d.data.name);

for (const {label,
key,
x,
fill=()=>null} of columns) {
// console.log(value, fillBy)
nodeEnter.append("rect")
.attr("x", x - columnSpacing-10)
// .attr("x", x - (backgroundWidth / 2))
.attr("y", "-0.62em")
.attr("width", columnSpacing)
.attr("height", nodeSize)
.attr("fill", "lightgrey")
.attr("fill", d => getTextColor(d, key))
// .attr("fill", d => colorScale((data_attr[d.data.efo_true][key])));
// .attr("fill", d => fill(d.data[`${label}`]));
// .attr("opacity", fill_zeros(key))


}
for (const {label,
key,
index,
x,
fill=()=>null } of columns) {

const columnLabels = svg.append("g")
.attr("class", "columnLabels");

const label = columnLabels.selectAll(".label")
.data(columns)
.enter().append("g")
.attr("text-anchor", "start")
.attr("class", "label")
.attr("transform", (d, i, index) => "translate(" + (d.x-30 + 30**d.index) + "," + (-nodeSize - 7) + ") rotate(0)");

label.each(function (d) {
const lines = d.label.split(/\n/);
if (Array.isArray(lines)) {
lines.forEach((line, index) => {
d3.select(this)
.append("text")
.text(line)
.attr("text-anchor", "end")
.attr("fill", "#333")
.attr("dy", index === 0 ? 0 : "1.2em");
});
}
});


let columnText = nodeEnter.append("text");
columnText.attr("dy", "0.32em")
.attr("x", x)
.attr("text-anchor", "end")
.text(d => mapper(d, key))
// .attr("fill", fill_zeros_text(key))
const backgroundWidth = 90;
const middlePoint = x - (backgroundWidth / 2);
columnText
.attr("x", middlePoint) // Set the x position to the middle point
.attr("text-anchor", "middle") // Set the text-anchor to "middle"
}
const nodeUpdate = node.merge(nodeEnter).transition(transition)
.attr("transform", d => `translate(0,${d.index * nodeSize})`)
.attr("cursor", d => _.has(d,'collapsed') ?"pointer":null)
.attr("pointer-events", d => "all")
.attr("fill-opacity", 1)
.attr("stroke-opacity", 1);
const nodeExit = node.exit().transition(transition).remove()
.attr("transform", d => `translate(0,${d.index * nodeSize})`)
.attr("fill-opacity", 0)
.attr("stroke-opacity", 0);
const link = gLink.selectAll("path")
.data(links, d => d.target.id);

// Enter any new links at the parent's previous position.
const xOffset = -2, yOffset=3;
const linkEnter = link.enter().append("path")//.transition(transition)
.attr("d", d => `
M${d.source.depth * nodeSize},${d.source.index * nodeSize+yOffset}
V${d.target.index * nodeSize}
h${nodeSize+xOffset}
`);
link.merge(linkEnter)//.transition(transition)
.attr("d", d => `
M${d.source.depth * nodeSize},${d.source.index * nodeSize+yOffset}
V${d.target.index * nodeSize}
h${nodeSize+xOffset}
`);
link.exit().remove()
.attr("d", d => `
M${d.source.depth * nodeSize},${d.source.index * nodeSize+yOffset}
V${d.target.index * nodeSize}
h${nodeSize+xOffset}
`);

const icon = gIcon.selectAll("path")
.data(source.descendants().filter(d=>_.has(d,'collapsed')), d => d.id);
const iconPathData = d=> _.has(d,'collapsed')?(d.collapsed?"M10 4L1 12V-4z":"M4 7L0 1h8z"):''
const iconEnter = icon.enter().append("path")
// .attr("transform", d => `translate(${d.depth * nodeSize-4},${d.index * nodeSize-3})`)
// .attr("fill", d => d.collapsed?"#999":null)
.attr("d", iconPathData);

// Transition links to their new position.
icon.merge(iconEnter)
.attr("transform", d => `translate(${d.depth * nodeSize-4},${d.index * nodeSize-4})`)
.attr("fill", "grey")
// .attr("fill", d => d.collapsed?("rgb(255" + ("," + Math.floor(Math.random() * 255)).repeat(2) + ")")
// :null)
.attr("cursor", "pointer")
.attr("pointer-events", "all")
.on("click", d => {
if(!_.has(d,'collapsed')) return;
d.collapsed=!d.collapsed;
d.children = d.children ? null : d._children;
update(source);
})
.attr("d", iconPathData);

// Transition exiting nodes to the parent's new position.
icon.exit().remove();
}
let copyOfRoot = root.copy();
const allNodes = root.descendants();
const minScore = d3.min(allNodes, d => d.data.Score);
const maxScore = d3.max(allNodes, d => d.data.Score);

copyOfRoot=identifyDescendants.call(copyOfRoot,copyOfRoot,...postIdentifyFns)
sortTree(copyOfRoot);
update(copyOfRoot);



return container.node();
// return svg.node();
}
Insert cell
Insert cell
function uncollapseNodesByEfoTrue(efoTrue, json) {
const nodes = findNodesByName(efoTrue, json);
nodes.forEach(node => {
node.collapsed = false;
node.children = node._children;
});
}
Insert cell
function collapseNodesByEfoTrue(efoTrue, json) {
const nodes = findNodesByName(efoTrue, json);
nodes.forEach(node => {
node.collapsed = true;
node.children = null;
});
}


Insert cell
function getTextColor(d, key) {
const value = data_attr[d.data.efo_true][key];
let normalizedValue;

if (key === "initial") {
const colorScale = d3.scaleOrdinal(["false", "true"], ["lightgrey", "green"])
return colorScale(value)
} else {
// let normalizedValue;
// normalizedValue = value;
// const colorScale = d3.scaleSequential(normalizedValue)
// .domain([0, 5000])
// .interpolator(d3.interpolateViridis);
// return colorScale(normalizedValue);
const colorScale = d3.scaleOrdinal(["false", "true"], ["lightgrey"])
return colorScale(value)
// return "lighgrey"
}
}
Insert cell
// function fill_zeros_text(key) {
// if (countNonZero_attributes(data_attr, key) === 0) {
// return "lightgrey";
// } else {
// return "black";
// }
// }
Insert cell
function fill_zeros(key) {
if (countNonZero_attributes(data_attr, key) === 0) {
return 0.1;
} else {
return 1;
}
}
Insert cell
countNonZero_attributes(data_attr, "mutation")
Insert cell
function countNonZero_attributes(jsonObj, keys, count = 0) {
if (typeof jsonObj === 'object' && jsonObj !== null) {
if (Array.isArray(jsonObj)) {
jsonObj.forEach(item => {
count = countNonZero_attributes(item, keys, count);
});
} else {
Object.keys(jsonObj).forEach(key => {
if (keys.includes(key) && jsonObj[key] !== 0) {
count += 1;
}
count = countNonZero_attributes(jsonObj[key], keys, count);
});
}
}
return count;
}
Insert cell
countNodesWithEfoTrue(data, "EFO_0004215")
Insert cell
function countNodesWithEfoTrue(jsonObj, efoTrueToCount, count = 0) {
if (typeof jsonObj === 'object' && jsonObj !== null) {
if (jsonObj.hasOwnProperty('efo_true') && jsonObj.efo_true === efoTrueToCount) {
count += 1;
}

if (jsonObj.hasOwnProperty('children') && Array.isArray(jsonObj.children)) {
jsonObj.children.forEach(child => {
count = countNodesWithEfoTrue(child, efoTrueToCount, count);
});
}
}
return count;
}
Insert cell
function mapper(d, key){
const value = data_attr[d.data.efo_true][key];
return value
}
Insert cell
data_attr["EFO_0000408"]["network"]
Insert cell
data_attr = FileAttachment("num_comparisons_pandaomics.json").json()
Insert cell
Insert cell
test_var = 10
Insert cell
Insert cell
mutable cachedColumnData = null
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
data
Insert cell
function toggleNodeVisibility(nodeId, json) {
function findAndUpdateNode(json) {
if (json.id === nodeId) {
if (!_.has(json, 'collapsed')) {
json.collapsed = true;
} else {
json.collapsed = !json.collapsed;
}
json.children = json.collapsed ? null : json._children;
return;
}

if (json.children) {
json.children.forEach(child => {
findAndUpdateNode(child);
});
}
}

findAndUpdateNode(json);
}

// function toggleNodeVisibility(nodeId, json, show = null) {
// function findAndUpdateNode(json) {
// if (json.id === nodeId) {
// // If show is not null, use its value to set the display property
// if (show !== null) {
// json.display = show ? "" : "none";
// }
// // If show is null, toggle the display property
// else {
// json.display = json.display === "none" ? "" : "none";
// }
// return;
// }

// if (json.children) {
// json.children.forEach(child => {
// findAndUpdateNode(child);
// });
// }
// }

// findAndUpdateNode(json);
// // Redraw the tree
// update(root);
// }
Insert cell
toggleNodeVisibility(3, data)
Insert cell
function toggleNodeVisibility3(nodeId, json) {
function findAndUpdateNode(json) {
if (json.id === nodeId) {
if (!_.has(json, 'collapsed')) {
json.collapsed = true;
} else {
json.collapsed = !json.collapsed;
}
json.children = json.collapsed ? null : json._children;
return;
}

if (json.children) {
json.children.forEach(child => {
findAndUpdateNode(child);
});
}
}

findAndUpdateNode(json);
}
Insert cell
function toggleNodeVisibility2(nodeId, json) {
let targetNode;

function findNodeById(json) {
if (json.id === nodeId) {
targetNode = json;
return;
}

if (json.children) {
json.children.some(child => {
findNodeById(child);
return targetNode !== undefined;
});
}
}

findNodeById(json);
return targetNode
if (targetNode) {
if (!_.has(targetNode, 'collapsed')) {
targetNode.collapsed = true;
} else {
targetNode.collapsed = !targetNode.collapsed;
}
targetNode.children = targetNode.children ? null : targetNode._children;
}
}
Insert cell
dat2 = addUniqueIdToNodes(data)
Insert cell
function addUniqueIdToNodes(json, nodeId = 0) {
// Assign a unique ID to the current node
json.id = nodeId++;
// If the current node has children, iterate through them recursively
if (json.children) {
json.children.forEach(child => {
nodeId = addUniqueIdToNodes(child, nodeId);
});
}
return nodeId;
}

Insert cell
data
Insert cell
findAncestors(data, 40886)
Insert cell
function findALLAncestors(json, nodeName) {
if (json.efo_true === nodeName) {
return [{ id: json.id, name: json.efo_true }];
}
if (json.children) {
for (let child of json.children) {
let ancestors = findALLAncestors(child, nodeName);
if (ancestors !== null) {
ancestors.unshift({ id: json.id, name: json.efo_true });
return ancestors;
}
}
}
return null;
}

Insert cell
function findAncestors(json, id) {
if (json.id === id) {
return [];
}
if (json.children) {
for (let child of json.children) {
let ancestors = findAncestors(child, id);
if (ancestors !== null) {
ancestors.push(json.id);
return ancestors;
}
}
}
return null;
}

Insert cell
// findNodesByIndex("EFO_0007450", data)
Insert cell
function NameTo(targetId, json, foundNames = []) {
if (json.id === targetId) {
foundNames.push(json.efo_true);
}

if (json.children) {
json.children.forEach(child => {
findNodesByName(targetId,child, foundNames);
});
}

return foundNames;
}
Insert cell
findNodesByName("EFO_0007450", data)
Insert cell
function findNodesByName(targetId, json, foundNames = []) {
if (json.efo_true === targetId) {
foundNames.push(json.id);
}

if (json.children) {
json.children.forEach(child => {
findNodesByName(targetId,child, foundNames);
});
}

return foundNames;
}

Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
// defaultCollapsed
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
function indexEachBefore2(r, nodes) {
let i = 0;
let y = 0;
r.eachBefore(n => {
if (n.parent && n.parent !== r) {
// If the node has a previous sibling, update the y position based on the previous sibling's totalLines
if (n.previousSibling) {
let nodeHeight = nodeSize * (n.previousSibling.totalLines || 1);
y += nodeHeight;
} else {
n.index = i++;
}
return r
} })}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
//import {DescribeObject} from "@tsenyi/json-object-statistics"
Insert cell
Insert cell
Insert cell
Insert cell
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