dc.graph
Version:
Graph visualizations integrated with crossfilter and dc.js
284 lines (264 loc) • 11.4 kB
JavaScript
dc_graph.troubleshoot = function() {
var _debugLayer = null;
var _translate, _scale = 1, _xDomain, _yDomain;
function draw(diagram, node, edge, ehover) {
if(!_debugLayer)
_debugLayer = diagram.g().append('g').attr({
class: 'troubleshoot',
'pointer-events': 'none'
});
var centers = node.data().map(function(n) {
return {
x: n.cola.x,
y: n.cola.y
};
});
var crosshairs = _debugLayer.selectAll('path.nodecenter').data(centers);
crosshairs.exit().remove();
crosshairs.enter().append('path').attr('class', 'nodecenter');
crosshairs.attr({
d: function(c) {
return 'M' + (c.x - _mode.xhairWidth()/2) + ',' + c.y + ' h' + _mode.xhairWidth() +
' M' + c.x + ',' + (c.y - _mode.xhairHeight()/2) + ' v' + _mode.xhairHeight();
},
opacity: _mode.xhairOpacity() !== null ? _mode.xhairOpacity() : _mode.opacity(),
stroke: _mode.xhairColor(),
'stroke-width': 1/_scale
});
function cola_point(n) {
return {x: n.cola.x, y: n.cola.y};
}
var colabounds = node.data().map(function(n) {
return boundary(cola_point(n), n.cola.width, n.cola.height);
});
var colaboundary = _debugLayer.selectAll('path.colaboundary').data(colabounds);
draw_corners(colaboundary, 'colaboundary', _mode.boundsColor());
var textbounds = node.data().map(function(n) {
if(!n.bbox || (!n.bbox.width && !n.bbox.height))
return null;
return boundary(cola_point(n), n.bbox.width, n.bbox.height);
}).filter(function(n) { return !!n; });
var textboundary = _debugLayer.selectAll('path.textboundary').data(textbounds);
draw_corners(textboundary, 'textboundary', _mode.boundsColor());
var radiibounds = node.data().map(function(n) {
if(typeof n.dcg_rx !== 'number')
return null;
return boundary(cola_point(n), n.dcg_rx*2, n.dcg_ry*2);
}).filter(function(n) { return !!n; });
var radiiboundary = _debugLayer.selectAll('path.radiiboundary').data(radiibounds);
draw_corners(radiiboundary, 'radiiboundary', _mode.boundsColor());
diagram.addOrRemoveDef('debug-orient-marker-head',
true,
'svg:marker',
orient_marker.bind(null, _mode.arrowHeadColor()));
diagram.addOrRemoveDef('debug-orient-marker-tail',
true,
'svg:marker',
orient_marker.bind(null, _mode.arrowTailColor()));
var heads = _mode.arrowLength() ? edge.data().map(function(e) {
return {pos: e.pos.new.path.points[e.pos.new.path.points.length-1], orient: e.pos.new.orienthead};
}) : [];
var headOrients = _debugLayer.selectAll('line.heads').data(heads);
draw_arrow_orient(headOrients, 'heads', _mode.arrowHeadColor(), '#debug-orient-marker-head');
var tails = _mode.arrowLength() ? edge.data().map(function(e) {
return {pos: e.pos.new.path.points[0], orient: e.pos.new.orienttail};
}) : [];
var tailOrients = _debugLayer.selectAll('line.tails').data(tails);
draw_arrow_orient(tailOrients, 'tails', _mode.arrowTailColor(), '#debug-orient-marker-tail');
var headpts = Array.prototype.concat.apply([], edge.data().map(function(e) {
var arrowSize = diagram.edgeArrowSize.eval(e);
return edge_arrow_points(
diagram.arrows(),
diagram.edgeArrowhead.eval(e),
arrowSize,
diagram.edgeStrokeWidth.eval(e) / arrowSize,
unrad(e.pos.new.orienthead),
e.pos.new.full.points[e.pos.new.full.points.length-1],
diagram.nodeStrokeWidth.eval(e.target)
);
}));
var hp = _debugLayer.selectAll('path.head-point').data(headpts);
draw_x(hp, 'head-point', _mode.arrowHeadColor());
var tailpts = Array.prototype.concat.apply([], edge.data().map(function(e) {
var arrowSize = diagram.edgeArrowSize.eval(e);
return edge_arrow_points(
diagram.arrows(),
diagram.edgeArrowtail.eval(e),
arrowSize,
diagram.edgeStrokeWidth.eval(e) / arrowSize,
unrad(e.pos.new.orienttail),
e.pos.new.full.points[0],
diagram.nodeStrokeWidth.eval(e.source)
);
}));
var tp = _debugLayer.selectAll('path.tail-point').data(tailpts);
draw_x(tp, 'tail-point', _mode.arrowTailColor());
var domain = _debugLayer.selectAll('rect.domain').data([0]);
domain.enter().append('rect');
var xd = _mode.parent().x().domain(), yd = _mode.parent().y().domain();
domain.attr({
class: 'domain',
fill: 'none',
opacity: _mode.domainOpacity(),
stroke: _mode.domainColor(),
'stroke-width': _mode.domainStrokeWidth()/_scale,
x: xd[0],
y: yd[0],
width: xd[1] - xd[0],
height: yd[1] - yd[0]
});
}
function on_zoom(translate, scale, xDomain, yDomain) {
_translate = translate;
_scale = scale;
_xDomain = xDomain;
_yDomain = yDomain;
draw(_mode.parent(), _mode.parent().selectAllNodes(), _mode.parent().selectAllEdges());
}
function boundary(point, wid, hei) {
return {
left: point.x - wid/2,
top: point.y - hei/2,
right: point.x + wid/2,
bottom: point.y + hei/2
};
};
function bound_tick(x, y, dx, dy) {
return 'M' + x + ',' + (y + dy) + ' v' + -dy + ' h' + dx;
}
function corners(bounds) {
return [
bound_tick(bounds.left, bounds.top, _mode.boundsWidth(), _mode.boundsHeight()),
bound_tick(bounds.right, bounds.top, -_mode.boundsWidth(), _mode.boundsHeight()),
bound_tick(bounds.right, bounds.bottom, -_mode.boundsWidth(), -_mode.boundsHeight()),
bound_tick(bounds.left, bounds.bottom, _mode.boundsWidth(), -_mode.boundsHeight()),
].join(' ');
}
function draw_corners(binding, classname, color) {
binding.exit().remove();
binding.enter().append('path').attr('class', classname);
binding.attr({
d: corners,
opacity: _mode.boundsOpacity() !== null ? _mode.boundsOpacity() : _mode.opacity(),
stroke: color,
'stroke-width': 1/_scale,
fill: 'none'
});
}
function unrad(orient) {
return +orient.replace('rad','');
}
function draw_arrow_orient(binding, classname, color, markerUrl) {
binding.exit().remove();
binding.enter().append('line').attr('class', classname);
binding.attr({
x1: function(d) { return d.pos.x; },
y1: function(d) { return d.pos.y; },
x2: function(d) { return d.pos.x - Math.cos(unrad(d.orient))*_mode.arrowLength(); },
y2: function(d) { return d.pos.y - Math.sin(unrad(d.orient))*_mode.arrowLength(); },
stroke: color,
'stroke-width': _mode.arrowStrokeWidth()/_scale,
opacity: _mode.arrowOpacity() !== null ? _mode.arrowOpacity() : _mode.opacity(),
'marker-end': 'url(' + markerUrl + ')'
});
}
function orient_marker(color, markerEnter) {
markerEnter
.attr({
viewBox: '0 -3 3 6',
refX: 3,
refY: 0,
orient: 'auto'
});
markerEnter.append('path')
.attr('stroke', color)
.attr('fill', 'none')
.attr('d', 'M0,3 L3,0 L0,-3');
}
function edge_arrow_points(arrows, defn, arrowSize, stemWidth, orient, endp, strokeWidth) {
var parts = arrow_parts(arrows, defn),
offsets = arrow_offsets(parts, stemWidth),
xunit = [Math.cos(orient), Math.sin(orient)];
endp = [endp.x, endp.y];
if(!parts.length)
return [[endp[0] - xunit[0]*strokeWidth/2,
endp[1] - xunit[1]*strokeWidth/2]];
var globofs = add_points(
[-strokeWidth/arrowSize/2,0],
mult_point(front_ref(parts[0].frontRef), -1));
var pts = offsets.map(function(ofs, i) {
return mult_point([
globofs,
front_ref(parts[i].frontRef),
ofs.offset
].reduce(add_points), arrowSize);
});
pts.push(mult_point([
globofs,
back_ref(parts[parts.length-1].backRef),
offsets[parts.length-1].offset
].reduce(add_points), arrowSize));
return pts.map(function(p) {
return add_points(
endp,
[p[0]*xunit[0] - p[1]*xunit[1], p[0]*xunit[1] + p[1]*xunit[0]]
);
});
}
function draw_x(binding, classname, color) {
var xw = _mode.xWidth()/2, xh = _mode.xHeight()/2;
binding.exit().remove();
binding.enter().append('path').attr('class', classname);
binding.attr({
d: function(pos) {
return [[[-xw,-xh],[xw,xh]], [[xw,-xh], [-xw,xh]]].map(function(seg) {
return 'M' + seg.map(function(p) {
return (pos[0] + p[0]) + ',' + (pos[1] + p[1]);
}).join(' L');
}).join(' ');
},
'stroke-width': 2/_scale,
stroke: color,
opacity: _mode.xOpacity()
});
}
function remove(diagram, node, edge, ehover) {
if(_debugLayer)
_debugLayer.remove();
}
var _mode = dc_graph.mode('highlight-paths', {
laterDraw: true,
draw: draw,
remove: remove,
parent: function(p) {
if(p) {
_translate = p.translate();
_scale = p.scale();
p.on('zoomed.troubleshoot', on_zoom);
}
else if(_mode.parent())
_mode.parent().on('zoomed.troubleshoot', null);
}
});
_mode.opacity = property(0.75);
_mode.xhairOpacity = property(null);
_mode.xhairWidth = property(10);
_mode.xhairHeight = property(10);
_mode.xhairColor = property('blue');
_mode.boundsOpacity = property(null);
_mode.boundsWidth = property(10);
_mode.boundsHeight = property(10);
_mode.boundsColor = property('green');
_mode.arrowOpacity = property(null);
_mode.arrowStrokeWidth = property(3);
_mode.arrowColor = _mode.arrowHeadColor = property('darkorange');
_mode.arrowTailColor = property('red');
_mode.arrowLength = property(100);
_mode.xWidth = property(1);
_mode.xHeight = property(1);
_mode.xOpacity = property(0.8);
_mode.domainOpacity = property(0.6);
_mode.domainColor = property('darkorange');
_mode.domainStrokeWidth = property(4);
return _mode;
};