in zeppelin-web/src/app/visualization/builtins/visualization-d3network.js [53:266]
render(networkData) {
if (!('graph' in networkData)) {
console.log('graph not found');
return;
}
if (!networkData.isRendered) {
networkData.isRendered = true;
} else {
return;
}
console.log('Rendering the graph');
if (networkData.graph.edges.length &&
!networkData.isDefaultSet) {
networkData.isDefaultSet = true;
this._setEdgesDefaults(networkData.graph);
}
const transformationConfig = this.transformation.getSetting().scope.config;
console.log('cfg', transformationConfig);
if (transformationConfig && angular.equals({}, transformationConfig.properties)) {
transformationConfig.properties = this.getNetworkProperties(networkData.graph);
}
this.targetEl.empty().append('<svg></svg>');
const width = this.targetEl.width();
const height = this.targetEl.height();
const self = this;
const defaultOpacity = 0;
const nodeSize = 10;
const textOffset = 3;
const linkSize = 10;
const arcPath = (leftHand, d) => {
let start = leftHand ? d.source : d.target;
let end = leftHand ? d.target : d.source;
let dx = end.x - start.x;
let dy = end.y - start.y;
let dr = d.totalCount === 1
? 0 : Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2)) / (1 + (1 / d.totalCount) * (d.count - 1));
let sweep = leftHand ? 0 : 1;
return `M${start.x},${start.y}A${dr},${dr} 0 0,${sweep} ${end.x},${end.y}`;
};
// Use elliptical arc path segments to doubly-encode directionality.
const tick = () => {
// Links
linkPath.attr('d', function(d) {
return arcPath(true, d);
});
textPath.attr('d', function(d) {
return arcPath(d.source.x < d.target.x, d);
});
// Nodes
circle.attr('transform', (d) => `translate(${d.x},${d.y})`);
text.attr('transform', (d) => `translate(${d.x},${d.y})`);
};
const setOpacity = (scale) => {
let opacity = scale >= +transformationConfig.d3Graph.zoom.minScale ? 1 : 0;
this.svg.selectAll('.nodeLabel')
.style('opacity', opacity);
this.svg.selectAll('textPath')
.style('opacity', opacity);
};
const zoom = d3.behavior.zoom()
.scaleExtent([1, 10])
.on('zoom', () => {
console.log('zoom');
setOpacity(d3.event.scale);
container.attr('transform', `translate(${d3.event.translate})scale(${d3.event.scale})`);
});
this.svg = d3.select(`#${this.containerId} svg`)
.attr('width', width)
.attr('height', height)
.call(zoom);
this.force = d3.layout.force()
.charge(transformationConfig.d3Graph.forceLayout.charge)
.linkDistance(transformationConfig.d3Graph.forceLayout.linkDistance)
.on('tick', tick)
.nodes(networkData.graph.nodes)
.links(networkData.graph.edges)
.size([width, height])
.on('start', () => {
console.log('force layout start');
this.$timeout(() => {
this.force.stop();
}, transformationConfig.d3Graph.forceLayout.timeout);
})
.on('end', () => {
console.log('force layout stop');
setOpacity(zoom.scale());
})
.start();
const renderFooterOnClick = (entity, type) => {
const footerId = this.containerId + '_footer';
const obj = {id: entity.id, label: entity.defaultLabel || entity.label, type: type};
let html = [`<li><b>${obj.type}_id:</b> ${obj.id}</li>`];
if (obj.label) {
html.push(`<li><b>${obj.type}_type:</b> ${obj.label}</li>`);
}
html = html.concat(_.map(entity.data, (v, k) => {
return `<li><b>${k}:</b> ${v}</li>`;
}));
angular.element('#' + footerId)
.find('.list-inline')
.empty()
.append(html.join(''));
};
let clickedOnDOMElement;
const drag = d3.behavior.drag()
.origin((d) => d)
.on('dragstart', (d) => {
console.log('dragstart');
d3.event.sourceEvent.stopPropagation();
clickedOnDOMElement = d3.event.sourceEvent.target;
d3.select(clickedOnDOMElement).classed('dragging', true);
self.force.stop();
})
.on('drag', (d) => {
console.log('drag');
d.px += d3.event.dx;
d.py += d3.event.dy;
d.x += d3.event.dx;
d.y += d3.event.dy;
})
.on('dragend', (d) => {
console.log('dragend');
d.fixed = true;
d3.select(clickedOnDOMElement).classed('dragging', false);
self.force.resume();
});
const container = this.svg.append('g');
if (networkData.graph.directed) {
container.append('svg:defs').selectAll('marker')
.data(['arrowMarker-' + this.containerId])
.enter()
.append('svg:marker')
.attr('id', String)
.attr('viewBox', '0 -5 10 10')
.attr('refX', 16)
.attr('refY', 0)
.attr('markerWidth', 4)
.attr('markerHeight', 4)
.attr('orient', 'auto')
.append('svg:path')
.attr('d', 'M0,-5L10,0L0,5');
}
// Links
const link = container.append('svg:g')
.on('click', () => {
renderFooterOnClick(d3.select(d3.event.target).datum(), 'edge');
})
.selectAll('g.link')
.data(self.force.links())
.enter()
.append('g');
const getPathId = (d) => this.containerId + '_' + d.source.index + '_' + d.target.index + '_' + d.count;
const showLabel = (d) => this._showNodeLabel(d);
const linkPath = link.append('svg:path')
.attr('class', 'link')
.attr('size', linkSize)
.attr('marker-end', `url(#arrowMarker-${this.containerId})`);
const textPath = link.append('svg:path')
.attr('id', getPathId)
.attr('class', 'textpath');
container.append('svg:g')
.selectAll('.pathLabel')
.data(self.force.links())
.enter()
.append('svg:text')
.attr('class', 'pathLabel')
.append('svg:textPath')
.attr('startOffset', '50%')
.attr('text-anchor', 'middle')
.attr('xlink:href', (d) => '#' + getPathId(d))
.text((d) => d.label)
.style('opacity', defaultOpacity);
// Nodes
const circle = container.append('svg:g')
.on('click', () => {
renderFooterOnClick(d3.select(d3.event.target).datum(), 'node');
})
.selectAll('circle')
.data(self.force.nodes())
.enter().append('svg:circle')
.attr('r', (d) => nodeSize)
.attr('fill', (d) => networkData.graph.labels && d.label in networkData.graph.labels
? networkData.graph.labels[d.label] : '#000000')
.call(drag);
const text = container.append('svg:g').selectAll('g')
.data(self.force.nodes())
.enter().append('svg:g');
text.append('svg:text')
.attr('x', (d) => nodeSize + textOffset)
.attr('size', nodeSize)
.attr('y', '.31em')
.attr('class', (d) => 'nodeLabel shadow label-' + d.label)
.text(showLabel)
.style('opacity', defaultOpacity);
text.append('svg:text')
.attr('x', (d) => nodeSize + textOffset)
.attr('size', nodeSize)
.attr('y', '.31em')
.attr('class', (d) => 'nodeLabel label-' + d.label)
.text(showLabel)
.style('opacity', defaultOpacity);
}