blob: 8f0461b0b558ec21b9a0076620fe46bbcdf04e54 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* eslint-disable */
import * as d3 from 'd3';
import d3tip from 'd3-tip';
export default class TraceMap {
constructor(el,row, showSpanModal,smax,smin,cmax,cmin) {
this.type = {
MQ: '#bf99f8',
Http: '#72a5fd',
Database: '#ff6732',
Unknown: '#ffc107',
Cache: '#00bcd4',
RPCFramework: '#ee4395',
};
this.smax = smax;
this.smin = smin;
this.cmax = cmax;
this.cmin = cmin;
this.showSpanModal = showSpanModal;
this.el = el;
this.i = 0;
this.j = 0;
this.width = el.clientWidth;
this.height = (row.length - 1) * 80;
this.body = d3
.select(this.el)
.style('height', this.height + 'px')
.append('svg')
.attr('width', this.width)
.attr('height', this.height);
this.tip = d3tip()
.attr('class', 'd3-tip')
.offset([10, 0])
.html(d => d.data.label);
this.timeTip = d3tip()
.attr('class', 'd3-tip')
.offset([-8, 0])
.html(d => d.data.label);
this.body.call(this.timeTip);
this.body.call(this.tip);
this.treemap = d3.tree().size([this.height * 0.8, this.width]);
}
init(data, row) {
this.row = row;
this.data = data;
this.min = d3.min(this.row.map(i => i.startTime));
this.max = d3.max(this.row.map(i => i.endTime - this.min));
this.list = Array.from(new Set(this.row.map(i => i.serviceCode)));
this.sequentialScale = d3
.scaleSequential()
.domain([0, this.list.length])
.interpolator(d3.interpolateCool);
this.svg = this.body.append('g').attr('transform', d => `translate(0, ${this.row.length * 14})`).append('g');
this.timeGroup = this.body.append('g').attr('class','timeGroup').attr('transform', d => 'translate(5,30)');
this.body.call(this.getZoomBehavior(this.svg));
this.root = d3.hierarchy(this.data, d => d.children);
this.root.x0 = this.height / 2;
this.root.y0 = 0;
}
resize() {
this.body
.select('.xAxis')
.remove();
this.body
.select('.timeGroup')
.remove();
this.width = this.el.clientWidth;
this.body.attr('width', this.width);
this.xScale = d3
.scaleLinear()
.domain([0, this.max])
.range([0, this.width - 10]);
this.xAxis = d3.axisTop(this.xScale).tickFormat(d => {
if (d === 0) return 0;
if (d >= 1000) return d / 1000 + 's';
return d + ' ms';
});
this.body
.append('g')
.attr('class', 'xAxis')
.attr('transform', `translate(5,20)`)
.call(this.xAxis);
this.timeGroup = this.body.append('g').attr('class','timeGroup').attr('transform', d => 'translate(5,30)');
this.updatexAxis(this.root);
}
draw() {
this.xScale = d3
.scaleLinear()
.domain([0, this.max])
.range([0, this.width - 10]);
this.xAxis = d3.axisTop(this.xScale).tickFormat(d => {
if (d === 0) return 0;
if (d >= 1000) return d / 1000 + 's';
return d + ' ms';
});
this.body
.append('g')
.attr('class', 'xAxis')
.attr('transform', `translate(5,20)`)
.call(this.xAxis);
this.updatexAxis(this.root);
this.update(this.root);
}
update(source) {
const that = this;
const links = this.nodes.slice(1);
const node = this.svg.selectAll('g.node').data(this.nodes, d => {
return d.id|| (d.id = ++this.i);
});
// node
const nodeEnter = node
.enter()
.append('g')
.attr('class', 'node')
.attr('transform', `translate(${source.y0},${source.x0})`)
.on('mouseover', function(d, i) {
that.tip.show(d, this);
const _node = that.timeUpdate._groups[0].filter(group => group.__data__.id === (i+1));
if(_node.length){
that.timeTip.show(d, _node[0].children[1]);
}
})
.on('mouseout', function(d, i) {
that.tip.hide(d, this);
const _node = that.timeUpdate._groups[0].filter(group => group.__data__.id === (i+1));
if(_node.length){
that.timeTip.hide(d, _node[0].children[1]);
}
})
.on('click', (d, i) => {
this.showSpanModal(
d.data,
{ width: '100%', top: -10, left: '0' },
d3.select(nodeEnter._groups[0][i]).append('rect')
);
d3.event.stopPropagation();
});
const nodeSelfDur = nodeEnter
.append('g')
.style('opacity', 0)
.attr('class','trace-tree-node-selfdur')
.attr('transform', 'translate(0,-39)')
nodeSelfDur
.append('rect')
.attr('width', 65)
.attr('height', 16)
.attr('rx', 3)
.attr('ry', 3)
.attr('fill', '#333')
nodeSelfDur
.append('text')
.attr('dx', 5)
.attr('dy', 11)
.text(d=> {
return d.data.dur + ' ms'
})
.attr('fill', '#fff');
const nodeSelfChild = nodeEnter
.append('g')
.style('opacity', 0)
.attr('class','trace-tree-node-selfchild')
.attr('transform', 'translate(0,-39)')
nodeSelfChild
.append('rect')
.attr('width', 110)
.attr('height', 16)
.attr('rx', 3)
.attr('ry', 3)
.attr('fill', '#333')
nodeSelfChild
.append('text')
.attr('dx', 5)
.attr('dy', 11)
.text(d=> `children: ${d.data.childrenLength}`)
.attr('fill', '#fff')
nodeEnter
.append('rect')
.attr('class', 'block')
.attr('x', '0')
.attr('y', '-20')
.attr('fill', d => (d.data.isError ? '#ff57221a' : '#f7f7f7'))
.attr('stroke', d => (d.data.isError ? '#ff5722aa' : '#e4e4e4'));
nodeEnter
.append('rect')
.attr('class', 'content')
.attr('stroke', d => d.data.isError ? '#ff5722aa' : '#e4e4e4');
nodeEnter
.append('rect')
.attr('class', 'service')
.attr('x', '-0.5')
.attr('y', '-20.5')
.style('fill', d => `${this.sequentialScale(this.list.indexOf(d.data.serviceCode))}`);
nodeEnter
.append('text')
.attr('dy', 13)
.attr('dx', 10)
.attr('stroke', '#333')
.attr('text-anchor', 'start')
.text(d => d.data.label.length > 19 ? d.data.label.slice(0, 19) : d.data.label);
nodeEnter
.append('text')
.attr('dy', -7)
.attr('dx', 12)
.attr('text-anchor', 'start')
.attr('fill', d => this.type[d.data.layer])
.attr('stroke', d => this.type[d.data.layer])
.text(d => d.data.layer);
nodeEnter
.append('text')
.attr('dy', -7)
.attr('x', 95)
.attr('stroke', '#333')
.attr('text-anchor', 'start')
.text(d => d.data.endTime ? d.data.endTime - d.data.startTime + ' ms' : d.data.traceId);
nodeEnter
.append('circle')
.attr('class', 'node')
.attr('r', 4)
.attr('cx', '158')
.style('fill', d => d._children ? '#8543e0aa' : '#fff')
.on('click', click);
this.nodeUpdate = nodeEnter.merge(node);
this.nodeUpdate
.transition()
.duration(600)
.attr('transform', function(d) {
return 'translate(' + d.y + ',' + d.x + ')';
});
this.nodeUpdate
.select('circle.node')
.attr('r', 4)
.attr('cx', '158')
.style('fill', d => d._children ? '#8543e0aa' : '#fff')
.attr('cursor', 'pointer');
const nodeExit = node
.exit()
.transition()
.duration(600)
.attr('transform', function(d) {
return 'translate(' + source.y + ',' + source.x + ')';
})
.remove();
// link
const link = this.svg.selectAll('path.link').data(links, d => d.id);
const linkEnter = link
.enter()
.insert('path', 'g')
.attr('class', 'link')
.attr('d', function(d) {
const o = { x: source.x0, y: source.y0 };
return diagonal(o, o);
});
const linkUpdate = linkEnter.merge(link);
linkUpdate
.transition()
.duration(600)
.attr('d', function(d) {
return diagonal(d, d.parent);
});
link
.exit()
.transition()
.duration(600)
.attr('d', function(d) {
var o = { x: source.x, y: source.y };
return diagonal(o, o);
})
.remove();
function diagonal(s, d) {
return `M ${s.y} ${s.x}
C ${s.y - 30} ${s.x}, ${d.y + 188} ${d.x},
${d.y + 158} ${d.x}`;
}
function click(d, i) {
// that.tip.hide(d, this);
// that.timeTip.hide(d, that.timeUpdate._groups[0][i]);
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
that.updatexAxis(d);
that.update(d);
d3.event.stopPropagation();
}
}
updatexAxis(source) {
// time
const that = this;
this.nodes = this.treemap(this.root).descendants();
let index = -1;
this.nodes.forEach(function(d) {
d.y = d.depth * 200;
d.timeX = ++index * 12;
d.x0 = d.x;
d.y0 = d.y;
});
const timeNode = this.timeGroup.selectAll('g.time').data(this.nodes, d => {
return d.id|| (d.id = ++this.j);
});
this.timeNode = timeNode;
const timeEnter = timeNode
.enter()
.append('g')
.attr('class', 'time')
.attr('transform', d => `translate(0,${d.timeX})`)
.on('mouseover', function(d, i) {
that.timeTip.show(d, this);
const _node = that.nodeUpdate._groups[0].filter(group => group.__data__.id === (i+1));
if(_node.length){
that.tip.show(d, _node[0]);
}
})
.on('mouseout', function(d, i) {
that.timeTip.hide(d, this);
const _node = that.nodeUpdate._groups[0].filter(group => group.__data__.id === (i+1));
if(_node.length){
that.tip.hide(d, _node[0]);
}
})
.on('click', (d, i) => {
this.showSpanModal(
d.data,
{ width: '100%', top: -10, left: '0' },
d3.select(that.timeUpdate._groups[0][i]).append('rect')
);
d3.event.stopPropagation();
});
timeEnter
.append('rect')
.attr('height', 10)
.attr('width', this.width)
.attr('y', -4)
.attr('class', 'time-bg');
timeEnter
.append('rect')
.attr('class', 'time-inner')
.attr('height', 8)
.attr('width', d => {
if (!d.data.endTime || !d.data.startTime) return 0;
return this.xScale(d.data.endTime - d.data.startTime) + 1;
})
.attr('rx', 2)
.attr('ry', 2)
.attr(
'x',
d => (!d.data.endTime || !d.data.startTime ? 0 : this.xScale(d.data.startTime - this.min))
)
.attr('y', -3)
.style('fill', d => `${this.sequentialScale(this.list.indexOf(d.data.serviceCode))}`);
timeEnter
.append('rect')
.style('opacity',0)
.attr('class', 'time-inner-duration')
.attr('height', 8)
.attr('width', d => {
if (!d.data.dur) return 1;
return this.xScale(d.data.dur) + 1;
})
.attr('rx', 2)
.attr('ry', 2)
.attr(
'x',
d => (!d.data.endTime || !d.data.startTime ? 0 : this.xScale(d.data.startTime - this.min))
)
.attr('y', -3)
.style('fill', d => `${this.sequentialScale(this.list.indexOf(d.data.serviceCode))}`);
this.timeUpdate = timeEnter.merge(timeNode);
this.timeUpdate
.transition()
.duration(600)
.attr('transform', d => `translate(0,${d.timeX})`);
const timeExit = timeNode
.exit()
.transition()
.duration(600)
.attr('transform', 'translate(0 ,10)')
.remove();
}
setDefault() {
d3.selectAll('.time-inner').style('opacity', 1);
d3.selectAll('.time-inner-duration').style('opacity', 0);
d3.selectAll('.trace-tree-node-selfdur').style('opacity', 0);
d3.selectAll('.trace-tree-node-selfchild').style('opacity', 0);
this.nodeUpdate._groups[0].forEach(i => {
d3.select(i).style('opacity', 1);
})
}
topChild() {
d3.selectAll('.time-inner').style('opacity', 1);
d3.selectAll('.time-inner-duration').style('opacity', 0);
d3.selectAll('.trace-tree-node-selfdur').style('opacity', 0);
d3.selectAll('.trace-tree-node-selfchild').style('opacity', 1);
this.nodeUpdate._groups[0].forEach(i => {
d3.select(i).style('opacity', .2);
if(i.__data__.data.childrenLength >= this.cmin && i.__data__.data.childrenLength <= this.cmax){
d3.select(i).style('opacity', 1);
}
})
}
topSlow() {
d3.selectAll('.time-inner').style('opacity', 0);
d3.selectAll('.time-inner-duration').style('opacity', 1);
d3.selectAll('.trace-tree-node-selfchild').style('opacity', 0);
d3.selectAll('.trace-tree-node-selfdur').style('opacity', 1);
this.nodeUpdate._groups[0].forEach(i => {
d3.select(i).style('opacity', .2);
if(i.__data__.data.dur >= this.smin && i.__data__.data.dur <= this.smax){
d3.select(i).style('opacity', 1);
}
})
}
getZoomBehavior(g) {
return d3
.zoom()
.scaleExtent([0.3, 10])
.on('zoom', () => {
g.attr(
'transform',
`translate(${d3.event.transform.x},${d3.event.transform.y})scale(${d3.event.transform.k})`
);
});
}
}