update = data => {
let update = data => {
const topHeight = 50;
const duration = 1000;
if (!data) {
data = dataAggregated;
}
let nodes = svg.selectAll('.node').data(rootWithLayout.leaves());
let nextDepth = data.depth + 1;
if (!compact) {
const groupChildren = rootWithLayout.children.flatMap(d => d.children);
const border = svg.selectAll('.borders').data(groupChildren);
border
.enter()
.append('rect')
.attr('class', 'borders')
.attr('fill', 'transparent')
.style('stroke', 'black')
.style('stroke-width', '0.02em')
.style('stroke-opacity', 1)
.merge(border)
.transition()
.duration(duration)
.attr('opacity', 1)
.attr('x', d => x(d.x0))
.attr('y', d => y(d.y0) + margin.top)
.attr('width', d => x(d.x1) - x(d.x0))
.attr('height', d => y(d.y1) - y(d.y0));
/// Labels ////
const labels = svg.selectAll('.group-label').data(groupChildren);
labels
.enter()
.append('text')
.attr('class', 'group-label')
.attr('font-weight', 'bold')
.merge(labels)
.transition()
.duration(duration)
.attr('opacity', 1)
.attr('transform', d => {
return `translate(${x(d.children[0].x0)}, ${y(d.children[0].y0) +
margin.top -
2})`;
})
.attr('font-size', d => {
if (data.depth === 2) {
return 22;
}
if (data.depth === 1) {
return 18;
}
return 11;
})
.text(d => `${d.data.name} (${formatPercent(d.value / data.value)})`);
} else {
svg.selectAll('.borders').attr('opacity', 0);
svg.selectAll('.group-label').attr('opacity', 0);
}
//////////////////////// Main Nodes ////////////////////////
const nodesEnter = nodes
.enter()
.append('g')
.attr('class', 'node');
nodesEnter
.merge(nodes)
.on("click", d => {
if (nextDepth >= 3) return;
let gZoom = d.parent;
while (gZoom.depth !== nextDepth) {
gZoom = gZoom.parent;
}
x.domain([gZoom.x0, gZoom.x1]);
y.domain([gZoom.y0, gZoom.y1]);
update(gZoom);
})
.transition()
.duration(duration)
.attr("transform", d => `translate(${x(d.x0)}, ${y(d.y0) + margin.top})`);
nodesEnter
.append("rect")
.attr("id", d => (d.leafUid = DOM.uid("leaf")).id)
.attr("class", "data");
nodesEnter
.merge(nodes)
.selectAll('.data')
.transition()
.duration(duration)
.attr("width", d => x(d.x1) - x(d.x0))
.attr("height", d => y(d.y1) - y(d.y0))
.style("fill", d => {
return d.parent
? color(d.parent.parent.data.name)(getTechId(d))
: 'black';
});
//////////////////////// Node Labels ////////////////////////
nodesEnter
.append("clipPath")
.attr("id", d => (d.clipUid = DOM.uid("clip")).id)
.append("use")
.attr("xlink:href", d => d.leafUid.href);
nodesEnter
.append("text")
.attr('class', 'node-label')
.attr('fill', '#fff')
.attr("clip-path", d => d.clipUid);
nodesEnter
.merge(nodes)
.selectAll('.node-label')
.selectAll("tspan")
.data(
d =>
d.data.name
.split(/(?=[A-Z][^A-Z])/g)
.concat(format(d.value))
.concat(formatPercent(d.value / data.value)) // This will show percent below value
)
.join("tspan")
.attr("x", 3)
.attr(
"y",
(d, i, nodes) =>
`${(i === nodes.length - 1 || i === nodes.length - 2) * 0.4 +
1.1 +
i * 1.1}em`
)
// .attr("fill-opacity", (d, i, nodes) =>
// i === nodes.length - 1 ? 0.7 : null
// )
.text(d => d);
nodesEnter
.merge(nodes)
.selectAll('text')
.transition()
.duration(duration)
.attr('font-size', d => {
if (data.depth === 2) {
return 20;
}
if (data.depth === 1) {
return 16;
}
return 12;
});
//////////////////////// Tooltips ////////////////////////
nodesEnter.append("title").text(d => {
const path = d
.ancestors()
.reverse()
.map(d => d.data.name)
.slice();
path.splice(0, 1);
return `${path.join(" | ")}\n${format(d.value)}`;
});
//////////////////////// Top rectangle ////////////////////////
let parent = svg
.selectAll('#parent')
.data([data])
.on("click", () => {
const parent = data.parent || dataAggregated;
x.domain([parent.x0, parent.x1 - 13]);
y.domain([parent.y0, parent.y1]);
update(parent);
});
parent
.enter()
.append('g')
.attr('transform', `translate(${margin.left}, ${margin.top - 5})`)
.attr('id', 'parent')
.append("rect")
.style("fill", "#dedede")
.attr("width", width - margin.right - margin.left - 2)
.attr("height", topHeight);
svg
.selectAll("#parent")
.selectAll('text')
.data([data])
.join('text')
.attr('font-weight', 'normal')
.attr('font-size', 16)
.attr('transform', 'translate(10, 20)')
.text(d => {
const names = [d.data.name];
let parent = d.parent;
while (parent) {
names.push(parent.data.name);
parent = parent.parent;
}
const path = names.reverse();
return `${path.join(" / ")}`;
})
.append('tspan')
.attr('dy', 22)
.attr("x", margin.left)
.attr('font-size', 20)
.text(d => {
console.log('d', d);
if (d.value !== greatTotal) {
return `${format(data.value)} (${formatPercent(
d.value / greatTotal
)})`;
}
return `${format(data.value)}`;
})
.append('tspan')
.attr('dy', -22)
.attr("x", width + margin.left - 130)
.attr('font-size', 14)
.text(`Total Expenses`)
.append('tspan')
.attr('dy', 22)
.attr("x", width + margin.left - 130)
.attr('font-size', 16)
.text(`${format(greatTotal)}`);
//////////////////////// Legends ////////////////////////
const legendGroup = svg.append('g').attr('class', 'legend');
legendGroup
.append('rect')
.attr('class', 'legend-background')
.attr('fill', '#fff')
.attr('transform', `translate(${margin.left}, 0)`)
.attr("width", width - margin.right - margin.left - 2)
.attr("height", margin.top - 2);
const gInfra = legendGroup
.selectAll('.legend-item')
.data(rootWithLayout.children)
.enter()
.append('g')
.attr('class', 'legend-item')
.attr('transform', (d, i) => `translate(${width / 3 + i * 70}, ${5})`);
gInfra
.append('rect')
.attr('fill', d => color(d.data.name)(1))
.attr('width', 15)
.attr('height', 15)
.attr('rx', 3);
gInfra
.append('text')
.attr('font-size', 12)
.attr('transform', (d, i) => `translate(19, 12)`)
.text(d => d.data.name);
};
return update(data);
}