Published
Edited
Jan 13, 2020
1 fork
1 star
Insert cell
Insert cell
Insert cell
svgNode = DOM.svg(width, height)
Insert cell
costType = 'tco'
Insert cell
Insert cell
svg = d3.select(svgNode)
Insert cell
update()
Insert cell
update = data => {
let update = data => {
const topHeight = 50;
const duration = 1000;

if (!data) {
data = dataAggregated;
}

// const svg = d3.select('svg');

let nodes = svg.selectAll('.node').data(rootWithLayout.leaves());
let nextDepth = data.depth + 1;

//////////////////////// Group Labels ////////////////////////
// Group labels must be added first, so they stay at the bottom in the svg order.

if (!compact) {
const groupChildren = rootWithLayout.children.flatMap(d => d.children);

/// Black borders ////

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);
}
Insert cell
getTechId = d => {
const infra = d.parent.parent;
const tech = d.parent;

return infra.children.findIndex(e => e.data.name === tech.data.name);
}
Insert cell
rootWithLayout = layout(dataAggregated)
Insert cell
layout = d3
.treemap()
.tile(d3.treemapResquarify)
.size([width - margin.left - margin.right, height])
.padding(compact ? 2 : 3)
.paddingOuter(1)
.paddingTop(compact ? 1 : 12)
Insert cell
greatTotal = dataAggregated.value
Insert cell
dataAggregated = d3
.hierarchy(data)
.sum(d => d.value)
.sort(function(a, b) {
return b.value - a.value;
})
Insert cell
Insert cell
Insert cell
c = d3.scaleOrdinal(d3.schemeCategory10)
Insert cell
y = d3.scaleLinear().domain([0, height]).range([50, height])
Insert cell
x = d3.scaleLinear().domain([0, width]).range([0, width])
Insert cell
width = 954
Insert cell
height = 800
Insert cell
format = d3.format("$,d")
Insert cell
formatPercent = d3.format(".1%")
Insert cell
margin = ({ top: 30, right: 0, bottom: 0, left: 0 })
Insert cell
color = name => {
return colorMap[name];
}
Insert cell
getChildren('RAN')
Insert cell
getChildren = name => {
return rootWithLayout.children.find(d => d.data.name === name).children
.length;
}
Insert cell
colorMap = ({
RAN: d3.scaleSequential([getChildren('RAN') + 2, -2], d3.interpolateBlues),
IMS: d3.scaleSequential([getChildren('IMS') + 2, -2], d3.interpolateGreys),
EPC: d3.scaleSequential([getChildren('EPC') + 2, -2], d3.interpolateOranges),
Routers: d3.scaleSequential(
[getChildren('Routers') + 2, -2],
d3.interpolateGreens
),
'X-Haul': d3.scaleSequential(
[getChildren('X-Haul') + 2, -2],
d3.interpolatePurples
)
})
Insert cell
d3 = require("d3@5")
Insert cell
import { slider, menu, checkbox } from '@jheer/dom-utilities'
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more