Published
Edited
Sep 11, 2019
Insert cell
Insert cell
Insert cell
chart = {
let root = partition(createMinValSubtree(data));
console.log("r", root)
let focus = root;

const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height])
.style("font", "10px sans-serif");

const cell = svg
.selectAll("g")
.data(root.descendants())
.attr('class','main')
.join("g")
.attr("transform", d => `translate(${d.y0},${d.x0})`);

const rect = cell.append("rect")
.attr("width", d => d.y1 - d.y0 - 1)
.attr("height", d => rectHeight(d))
.attr("fill-opacity", (d) => {
if (d.data.name == 'unknown') return 0.2;
return 0.6;
})
.attr("fill", d => {
if (!d.depth) return "#ccc";
while (d.depth > 1) d = d.parent;
return color(d.data.name);
})
.style("cursor", "pointer")
.on("click", clicked)

const text = cell.append("text")
.style("user-select", "none")
.attr("pointer-events", "none")
.attr("x", 4)
.attr("y", 13)
.attr("fill-opacity", d => +labelVisible(d));

text.append("tspan")
.style('font-weight','bold')
.text(d => {
// noting that the value is out of scale
const prefix = calcTotalWithUnknowns(d) == d.value ? '' : '* ';
// BUG --> might be a small bug here...should not be applied to DeFi
// As it is not a direct child of `root` --> check
const count = d.children ? " (" + d.children.length + ")" : ''
if (d.data.name != 'unknown') return prefix + d.data.name + count
return "Unknown (hover for details)"
});
const tspan = text.append("tspan")
.attr("x",0)
.attr("dy","1.2em")
.attr("fill-opacity", d => labelVisible(d, true) * 0.7) // visibility condition on space
.text(d => {
if (d.hasOwnProperty('children')) {
const total = calcTotalWithUnknowns(d)
return ` ${format(d.data.sum)} / ${format(total)}`
}
return ` ${format(d.data.value[dataType])}` // for leafs
});
// This is for the tooltip
cell.append("title")
.text(d => {
let data = d.data
let lineage = d.ancestors().map(d => d.data.name).reverse().join("/");
if (data.name != 'unknown') {
if ('children' in data) {
let c = data.children
.filter(d => d.sum || ('value' in d ? d.value[dataType] : 0))
.map((d) => {
return `\t${d.name} - ${format(d.sum || d.value[dataType])}`
}).join("\n")
return `${lineage}\n${c}\n${format(d.value)}`
}
// for non unknown leafs
return `${lineage}\n${format(d.value)}`
}
// For unknown items
let unknownItems = data.unknown.sort().map(d => " - " + d).join("\n")
return `${lineage}\n${unknownItems}\n${format(data.value[dataType])}`
});
// Dynamic scaling fits somewhere into this here....
// Either call partition() again on the dataset with relevant data using newSum...
// --> but then does the children value have to change too? Insert a hidden item?..
// OR is each column independent...probably not
// At least dynamically updating each subtree isn't that hard....
function clicked(p) {
focus = focus === p ? p = p.parent : p;
console.log("p", p, data)
// Skip findSubtree if leaf
// Is creating a new subtree the way to go...or should we try to update the entire tree?
const subtree = findSubtree(p.data.name, data)
// console.log("new", partition(createMinValSubtree(subtree))) // Partition
// Look here -- http://jsfiddle.net/4v62v9g3/
// How to bind data properly?
// d3.selectAll("g")
// .data(partition(createMinValSubtree(subtree)).descendants())
// .transition()
console.log("select",
d3.selectAll("g"),
partition(createMinValSubtree(subtree)).descendants())
// This iterates through all nodes
// If d.value is updated here, does it propogate to x0,x1,y0,y1? or does scaling need to occur manually...
// Try to dynamically recalculate height based on the new values...
// root.each((d) => {
// console.log(d)
// })
root.each(d => d.target = {
x0: (d.x0 - p.x0) / (p.x1 - p.x0) * height,
x1: (d.x1 - p.x0) / (p.x1 - p.x0) * height,
y0: d.y0 - p.y0,
y1: d.y1 - p.y0
});

const t = cell.transition().duration(750)
.attr("transform", d => `translate(${d.target.y0},${d.target.x0})`);

rect.transition(t).attr("height", d => rectHeight(d.target));
text.transition(t).attr("fill-opacity", d => +labelVisible(d.target));
tspan.transition(t).attr("fill-opacity", d => labelVisible(d.target, true) * 0.7);
}
function rectHeight(d) {
return d.x1 - d.x0 - Math.min(1, (d.x1 - d.x0) / 2);
}

function labelVisible(d, override) {
if (override) return d.y1 <= width && d.y0 >= 0 && d.x1 - d.x0 > 28;
return d.y1 <= width && d.y0 >= 0 && d.x1 - d.x0 > 16;
}
return svg.node();
}
Insert cell
function createMinValSubtree(subtree) {
// If not parents of leaf nodes
let values = subtree.children.filter((d) => d.sum == 0 ? 0 : 1).map((d) => d.sum)
// If parent of leaf nodes
if (subtree.children[0].children == undefined) {
values = subtree.children.filter((d) => d.value[dataType] == 0 ? 0 : 1).map((d) => d.value[dataType])
}
// Check if below is necessary....
// THE SCALE VALUE should be based on UPPER BOUNDS NOT LOWER BOUNDS...
const total = d3.sum(values)
const minHeightPercent = 0.02
const minHeight = minHeightPercent * total
const countSubMinHeight = values.filter(d => d <= minHeight).length
const sumSufficientHeight = d3.sum(values.filter(d => d > minHeight))
const newMinHeight = minHeightPercent * sumSufficientHeight / (1 - countSubMinHeight * minHeightPercent)
const newChildren = subtree.children.map((d) => {
let childVal = 0;
if ('sum' in d) childVal = d.sum
else {
childVal = ('original_' + dataType in d.value) ? d.value['original_' + dataType] : d.value[dataType]
}
d.newSum = 0;
if (childVal != 0) {
d.newSum = childVal < minHeight ? newMinHeight : childVal
d.scaleVal = d.newSum / childVal
d = scaleChildren(d, d.scaleVal)
}
return d
})
subtree.children = newChildren
return subtree
}
Insert cell
// only need to scale the leaf nodes?
function scaleChildren(node, scaleVal) {
if ('children' in node) {
node.children = node.children.map(c => scaleChildren(c, scaleVal))
return node
}
if (!('original_' + dataType in node.value)) {
node.value['original_' + dataType] = node.value[dataType]
}
node.value[dataType] = node.value['original_' + dataType] * scaleVal;
return node

}
Insert cell
Insert cell
data = addUnknownLeaf({}, buildHierarchy(base.map((d) => {
// d.market_cap = Math.sqrt(Math.sqrt(d.market_cap))
return d
})), dataType)
Insert cell
base = d3.csv("https://raw.githubusercontent.com/GauntletNetworks/tam-data/master/data.csv")
Insert cell
Insert cell
// Maybe should try a different approach instead of iterative BFS
// How to find the total without the scaled value....
function calcTotalWithUnknowns(subtree) {
var queue = [];
queue.push(subtree);

let total = 0
while (queue.length > 0) {
var node = queue[0];

if (node.children) {
queue = queue.concat(node.children)
}
if ("value" in node.data) {
total += node.data.value["original_" + dataType]
}
queue.shift()
}
return total
}
Insert cell
Insert cell
// Seems that only 1 piece of information can be encoded at a time
partition = data => {
function e(v,k) {
return (typeof v == 'object' ? v[k] : 0)
}
const root = d3.hierarchy(data)
.sum(d => e(d.value,dataType))
.sort((a, b) => b.height - a.height || e(b.value,dataType) - e(a.value,dataType));
return d3.partition()
.size([height, (root.height + 1) * width / 5]) // change this to affect the number of columns
(root);
}
Insert cell
Insert cell
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