Add curvy lines
This commit is contained in:
Родитель
e3e980abd9
Коммит
d01f88c1fa
|
@ -50,12 +50,13 @@ h1 span {
|
||||||
}
|
}
|
||||||
|
|
||||||
.vertex {
|
.vertex {
|
||||||
|
transition: fill .2s;
|
||||||
fill: #0072c6;
|
fill: #0072c6;
|
||||||
r: 8px; /* vertex radius */
|
r: 8px; /* vertex radius */
|
||||||
}
|
}
|
||||||
|
|
||||||
.vertex:hover, .vertex.dragging {
|
.vertex:hover, .vertex.dragging {
|
||||||
transition: .1s;
|
transition: .2s;
|
||||||
fill: #00a2e6;
|
fill: #00a2e6;
|
||||||
r: 10px;
|
r: 10px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,13 @@ const defaultQuery = "g.V()";
|
||||||
const maxNodes = 300;
|
const maxNodes = 300;
|
||||||
const maxEdges = 1000;
|
const maxEdges = 1000;
|
||||||
|
|
||||||
|
const linkDistance = graphWidth / 3;
|
||||||
|
const linkStrength = 0.01; // Reduce rigidity of the links (if < 1, the full linkDistance is relaxed)
|
||||||
|
const charge = -3000;
|
||||||
|
const markerRef = 8;
|
||||||
|
const vertexRadius = 8; // from css
|
||||||
|
const paddingBetweenVertexAndEdge = 3;
|
||||||
|
|
||||||
let htmlElements: {
|
let htmlElements: {
|
||||||
debugLog: HTMLTextAreaElement,
|
debugLog: HTMLTextAreaElement,
|
||||||
graphRadio: HTMLInputElement,
|
graphRadio: HTMLInputElement,
|
||||||
|
@ -84,6 +91,11 @@ interface ForceLink {
|
||||||
target: ForceNode;
|
target: ForceNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface Point2D {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
}
|
||||||
|
|
||||||
export class GraphClient {
|
export class GraphClient {
|
||||||
private _socket: SocketIOClient.Socket;
|
private _socket: SocketIOClient.Socket;
|
||||||
private _force: any;
|
private _force: any;
|
||||||
|
@ -286,6 +298,58 @@ export class GraphClient {
|
||||||
d3.select(htmlElements.graphSection).select("svg").selectAll(".vertex, .edge, .label").remove();
|
d3.select(htmlElements.graphSection).select("svg").selectAll(".vertex, .edge, .label").remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static calculateClosestPIOver2(angle: number): number {
|
||||||
|
const CURVATURE_FACTOR = 40;
|
||||||
|
const result = (Math.atan(CURVATURE_FACTOR * (angle - (Math.PI / 4))) / 2) + (Math.PI / 4);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static calculateClosestPIOver4(angle: number): number {
|
||||||
|
const CURVATURE_FACTOR = 100;
|
||||||
|
const result = (Math.atan(CURVATURE_FACTOR * (angle - (Math.PI / 8))) / 4) + (Math.PI / 8);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static calculateControlPoint(start: Point2D, end: Point2D): Point2D {
|
||||||
|
const alpha = Math.atan2(end.y - start.y, end.x - start.x);
|
||||||
|
const n = Math.floor(alpha / (Math.PI / 2));
|
||||||
|
const reducedAlpha = alpha - (n * Math.PI / 2);
|
||||||
|
const reducedBeta = GraphClient.calculateClosestPIOver2(reducedAlpha);
|
||||||
|
const beta = reducedBeta + (n * Math.PI / 2);
|
||||||
|
|
||||||
|
const length = Math.sqrt((end.y - start.y) * (end.y - start.y) + (end.x - start.x) * (end.x - start.x)) / 2;
|
||||||
|
const result = {
|
||||||
|
x: start.x + Math.cos(beta) * length,
|
||||||
|
y: start.y + Math.sin(beta) * length
|
||||||
|
};
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private positionLink(l: any) {
|
||||||
|
const d1 = GraphClient.calculateControlPoint(l.source, l.target);
|
||||||
|
|
||||||
|
var radius = vertexRadius + paddingBetweenVertexAndEdge;
|
||||||
|
|
||||||
|
// Start
|
||||||
|
var dx = d1.x - l.source.x;
|
||||||
|
var dy = d1.y - l.source.y;
|
||||||
|
var angle = Math.atan2(dy, dx);
|
||||||
|
var tx = l.source.x + (Math.cos(angle) * radius);
|
||||||
|
var ty = l.source.y + (Math.sin(angle) * radius);
|
||||||
|
|
||||||
|
// End
|
||||||
|
dx = l.target.x - d1.x;
|
||||||
|
dy = l.target.y - d1.y;
|
||||||
|
angle = Math.atan2(dy, dx);
|
||||||
|
var ux = l.target.x - (Math.cos(angle) * radius);
|
||||||
|
var uy = l.target.y - (Math.sin(angle) * radius);
|
||||||
|
|
||||||
|
return "M" + tx + "," + ty
|
||||||
|
+ "S" + d1.x + "," + d1.y
|
||||||
|
+ " " + ux + "," + uy;
|
||||||
|
}
|
||||||
|
|
||||||
private displayGraph(vertices: ResultVertex[], edges: ResultEdge[]) {
|
private displayGraph(vertices: ResultVertex[], edges: ResultEdge[]) {
|
||||||
try {
|
try {
|
||||||
this.clearGraph();
|
this.clearGraph();
|
||||||
|
@ -330,9 +394,9 @@ export class GraphClient {
|
||||||
force.gravity(1); // Makes the nodes gravitate toward the center
|
force.gravity(1); // Makes the nodes gravitate toward the center
|
||||||
force.friction(.5);
|
force.friction(.5);
|
||||||
|
|
||||||
force.linkDistance(graphWidth / 3); // edge length
|
force.linkDistance(linkDistance); // edge length
|
||||||
force.linkStrength(0.01); // Reduce rigidity of the links (if < 1, the full linkDistance is relaxed)
|
force.linkStrength(linkStrength);
|
||||||
force.charge(-3000);
|
force.charge(charge);
|
||||||
|
|
||||||
let svg = d3.select(htmlElements.graphSection).select("svg")
|
let svg = d3.select(htmlElements.graphSection).select("svg")
|
||||||
.attr("height", graphHeight);
|
.attr("height", graphHeight);
|
||||||
|
@ -344,29 +408,31 @@ export class GraphClient {
|
||||||
}))
|
}))
|
||||||
.append("g");
|
.append("g");
|
||||||
|
|
||||||
|
// Arrow
|
||||||
|
svg.select('defs').selectAll('marker')
|
||||||
|
.data(['end'])
|
||||||
|
.enter()
|
||||||
|
.append('marker')
|
||||||
|
.attr('id', 'triangle')
|
||||||
|
.attr('viewBox', '0 -5 10 10')
|
||||||
|
.attr('refX', markerRef) // Shift arrow so that we can see it.
|
||||||
|
.attr('refY', 0)
|
||||||
|
.attr('markerWidth', 6)
|
||||||
|
.attr('markerHeight', 6)
|
||||||
|
.attr('orient', 'auto')
|
||||||
|
.attr('markerUnits', 'userSpaceOnUse') // No auto-scaling with stroke width
|
||||||
|
.attr('fill', "yellow").attr('stroke', "purple")
|
||||||
|
.append('path')
|
||||||
|
.attr('d', 'M0,-5L10,0L0,5');
|
||||||
|
|
||||||
// Links before nodes so that links don't get drawn on top of node labels, obscuring them
|
// Links before nodes so that links don't get drawn on top of node labels, obscuring them
|
||||||
let edge = svg.selectAll(".edge")
|
let edge = svg.selectAll(".edge")
|
||||||
.data(links)
|
.data(links)
|
||||||
.enter().append("line")
|
.enter()
|
||||||
.attr("class", "edge")
|
.append("path")
|
||||||
;
|
.attr('class', 'edge')
|
||||||
|
.attr('fill', 'none')
|
||||||
// Arrow
|
.attr('marker-end', 'url(#triangle)');
|
||||||
// svg.select('defs').selectAll('marker')
|
|
||||||
// .data(['end'])
|
|
||||||
// .enter()
|
|
||||||
// .append('marker')
|
|
||||||
// .attr('id', 'triangle')
|
|
||||||
// .attr('viewBox', '0 -5 10 10')
|
|
||||||
// .attr('refX', D3ForceGraph.MARKER_REFX) // Shift arrow so that we can see it.
|
|
||||||
// .attr('refY', 0)
|
|
||||||
// .attr('markerWidth', 6)
|
|
||||||
// .attr('markerHeight', 6)
|
|
||||||
// .attr('orient', 'auto')
|
|
||||||
// .attr('markerUnits', 'userSpaceOnUse') // No auto-scaling with stroke width
|
|
||||||
// .attr('fill', this.graphConfig.linkColor()).attr('stroke', this.graphConfig.linkColor())
|
|
||||||
// .append('path')
|
|
||||||
// .attr('d', 'M0,-5L10,0L0,5');
|
|
||||||
|
|
||||||
// Allow user to drag nodes. Set "dragging" class while dragging.
|
// Allow user to drag nodes. Set "dragging" class while dragging.
|
||||||
let vertexDrag = force.drag().on("dragstart", function () {
|
let vertexDrag = force.drag().on("dragstart", function () {
|
||||||
|
@ -377,6 +443,7 @@ export class GraphClient {
|
||||||
})
|
})
|
||||||
.on("dragend", function () { d3.select(this).classed("dragging", false); });
|
.on("dragend", function () { d3.select(this).classed("dragging", false); });
|
||||||
|
|
||||||
|
// Labels
|
||||||
let label = svg.selectAll(".label")
|
let label = svg.selectAll(".label")
|
||||||
.data(nodes)
|
.data(nodes)
|
||||||
.enter().append("text")
|
.enter().append("text")
|
||||||
|
@ -418,6 +485,8 @@ export class GraphClient {
|
||||||
.attr("y2", (d: ForceLink) => d.target.y)
|
.attr("y2", (d: ForceLink) => d.target.y)
|
||||||
;
|
;
|
||||||
|
|
||||||
|
edge.attr("d", (d: ForceLink) => { return this.positionLink(d); });
|
||||||
|
|
||||||
label
|
label
|
||||||
.transition().ease("linear").duration(animationStepMs)
|
.transition().ease("linear").duration(animationStepMs)
|
||||||
.attr("class", "label")
|
.attr("class", "label")
|
||||||
|
|
Загрузка…
Ссылка в новой задаче