Published
Edited
Sep 11, 2018
7 stars
Insert cell
Insert cell
viewof pathomTrace = {
const svg = d3.select(DOM.svg("100%", vizHeight)).property("value", []).attr('class', 'myviz')
const zoom = d3.zoom()
.on("zoom", applyZoom)
svg.call(zoom);
const axisX = d3.axisBottom(x)
.tickFormat(d => d + " ms")
.tickSize(vizHeight)
.ticks(10);
const axisNodes = svg.append("g")
.attr("class", "pathom-axis")
.attr("transform", "translate(0,0)")
function updateScale() {
axisNodes.call(axisX)
axisNodes
.select(".domain")
.remove()

axisNodes
.selectAll("text")
.attr("y", 0)
.attr("x", -5)
.style("text-anchor", "end")
}
updateScale()
function applyZoom() {
const new_xScale = d3.event.transform.rescaleX(x);
axisX.scale(new_xScale)
updateScale()
mainGroup.attr("transform", d3.event.transform);
}
const mainGroup = svg.append('g')
const nodesContainer = mainGroup.append('g').attr('class', 'pathom-view-nodes-container')
const layout = function(node) {
node.x0 = x(0)
node.y0 = y(0);
node.x1 = x(node.data.duration);
node.y1 = node.y0 + 3;

const positionNode = function (node, pos) {
node.x0 = x(node.data.start);
node.x1 = x(node.data.start + node.data.duration);
node.y0 = pos.y;
node.y1 = pos.y += barSize;
pos.y++;

const nextPos = {y: pos.y};

if (node.data.children) {
if (!node.data.skipChildren) {
node.children.forEach(n => positionNode(n, nextPos));
}
node.y2 = nextPos.y;
}
pos.y = nextPos.y;

return node;
};

const pos = {y: node.y1 + 1};
node.children.forEach(n => positionNode(n, pos));

return node;
}
function renderTrace(selection, {data}) {
const nodeRoots = selection
.selectAll('g.pathom-attr-group')
.data(layout(data).descendants())
const nodesEnter = nodeRoots
.enter().append('g')
const nodes = nodesEnter
.attr('class', 'pathom-attr-group')
.attr('transform', function(d) {return 'translate(' + [d.x0, d.y0] + ')'})
.style('opacity', 0)
.on('mouseover', function (d, i) { d3.select(this.childNodes[0]).style('visibility', 'visible'); } )
.on('mouseout', function (d, i) { d3.select(this.childNodes[0]).style('visibility', 'hidden'); } )
.on('click', d => {
if (d.children && d.children.length) {
d.data.skipChildren = true;
d._children = d.children;
d.children = null;
renderTrace(selection, {data});
} else if (d._children) {
d.data.skipChildren = false;
d.children = d._children;
renderTrace(selection, {data});
}
})
nodes
.merge(nodeRoots)
.transition().duration(300)
.attr('transform', function(d) {return 'translate(' + [d.x0, d.y0] + ')'})
.style('opacity', 1)
nodeRoots
.exit()
.transition().duration(300)
.style('opacity', 0)
.remove()
registerTooltip(nodes, (d, target) => {
const label = d.data.name || d.data.hint;
const xv = x.invert(d3.mouse(target)[0]);
const details = d.data.details.filter(detail => {
if (xv < detail.rts) return false;
if (xv > (detail.rts + (detail.duration || x.invert(1)))) return false;

return true;
}).map(d => d.duration + ' ms ' + d.event);

return [d.data.duration + ' ms ' + label].concat(details).join("<br>");
});

const boundNodes = nodesEnter.append('rect')
.attr('class', 'pathom-attribute-bounds')
.attr('width', d => d.x1 - d.x0 + 1)
.merge(nodeRoots.select('rect.pathom-attribute-bounds'))
.transition().duration(300)
.attr('height', d => d.y2 ? d.y2 - d.y0 + 1 : 0)

const attributeNodes = nodesEnter.append('rect')
.attr('class', 'pathom-attribute')
.attr('width', d => d.x1 - d.x0)
.attr('height', d => d.y1 - d.y0)

const detailsNodes = nodesEnter.append('g')
.selectAll('rect.pathom-detail-marker')
.data(d => {
d.data.details.forEach((dt) => {
dt.x0 = d.x0, dt.x1 = d.x1, dt.y0 = d.y0, dt.y1 = d.y1
dt.rts = dt.start - d.data.start
})
return d.data.details;
})
.enter()
.append('rect')
.attr('class', d => 'pathom-detail-marker ' + 'pathom-event-' + d.event + (d.error ? ' pathom-event-error' : ''))
.attr('fill', d => '#c00')
.attr('width', d => Math.max(1, x(d.duration)))
.attr('height', function(d) { return d.y1 - d.y0; })
.attr('transform', function(d) {return 'translate(' + [x(d.rts), 0] + ')'})

// labels
nodesEnter
.append('text')
.attr('class', 'pathom-label-text')
.attr('dx', 2)
.attr('dy', 13)
.style('font-family', d => d.children ? "monospace" : "")
.text(function(d) {if (d.data.name) return d.data.name;})
}
const dataTree = d3.hierarchy(currentData, d => d.skipChildren ? null : d.children)
dataTree.sum(d => d.name ? 1 : 0);
renderTrace(svg.select('.pathom-view-nodes-container'), {data: dataTree})
const vruler = svg
.append('line')
.attr('class', 'pathom-vruler')
.attr('y1', 0)
.attr('y2', vizHeight)
svg
.on('mousemove.pathom-rule', function() {
const x = d3.mouse(this)[0];
vruler.attr('x1', x).attr('x2', x);
})
d3.select('body')
.on('keydown.pathom-rule', e => { if(d3.event.keyCode == 16) vruler.style('visibility', 'visible') })
.on('keyup.pathom-rule', e => { if(d3.event.keyCode == 16) vruler.style('visibility', 'hidden') })

return svg.node();
}
Insert cell
Insert cell
Insert cell
currentData = data
Insert cell
Insert cell
Insert cell
Insert cell
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