1
0
Fork 0

fix(graph): merge edges between the same pair of nodes

This commit is contained in:
ssube 2020-08-22 17:51:54 -05:00
parent 2abe6b66c9
commit 80caa7a579
Signed by: ssube
GPG Key ID: 3EED7B957D362AF1
1 changed files with 55 additions and 15 deletions

View File

@ -1,3 +1,5 @@
import { mustExist } from '@apextoaster/js-utils';
import { BaseLabel, FlagLabel, getValueName, StateLabel } from './labels'; import { BaseLabel, FlagLabel, getValueName, StateLabel } from './labels';
import { ChangeVerb } from './resolve'; import { ChangeVerb } from './resolve';
import { defaultTo, defaultUntil } from './utils'; import { defaultTo, defaultUntil } from './utils';
@ -5,6 +7,18 @@ import { defaultTo, defaultUntil } from './utils';
const COLOR_NODE = 'cccccc'; const COLOR_NODE = 'cccccc';
const COLOR_MIDNODE = 'aaaaaa'; const COLOR_MIDNODE = 'aaaaaa';
export enum EdgeType {
/**
* Both directions, arrows on both ends.
*/
BOTH = 'both',
/**
* Source to target, arrow at target.
*/
FORWARD = 'forward',
}
export interface Node { export interface Node {
color: string; color: string;
name: string; name: string;
@ -13,7 +27,8 @@ export interface Node {
export interface Edge { export interface Edge {
source: string; source: string;
target: string; target: string;
type: ChangeVerb; type: EdgeType;
verb: ChangeVerb;
} }
export interface Graph { export interface Graph {
@ -34,7 +49,8 @@ function labelEdges(label: BaseLabel, edges: Array<Edge>) {
edges.push({ edges.push({
source: label.name, source: label.name,
target: add.name, target: add.name,
type: ChangeVerb.CREATED, type: EdgeType.FORWARD,
verb: ChangeVerb.CREATED,
}); });
} }
@ -42,7 +58,8 @@ function labelEdges(label: BaseLabel, edges: Array<Edge>) {
edges.push({ edges.push({
source: label.name, source: label.name,
target: remove.name, target: remove.name,
type: ChangeVerb.REMOVED, type: EdgeType.FORWARD,
verb: ChangeVerb.REMOVED,
}); });
} }
@ -50,11 +67,32 @@ function labelEdges(label: BaseLabel, edges: Array<Edge>) {
edges.push({ edges.push({
source: label.name, source: label.name,
target: require.name, target: require.name,
type: ChangeVerb.REQUIRED, type: EdgeType.FORWARD,
verb: ChangeVerb.REQUIRED,
}); });
} }
} }
function mergeEdges(edges: Array<Edge>): Array<Edge> {
const uniqueEdges = new Map<string, Edge>();
for (const edge of edges) {
const sortedNodes = [edge.source, edge.target].sort();
const dirName = [edge.verb, ...sortedNodes].join(':');
if (uniqueEdges.has(dirName)) {
const prevEdge = mustExist(uniqueEdges.get(dirName));
if (edge.type !== prevEdge.type || edge.source !== prevEdge.source) {
prevEdge.type = EdgeType.BOTH;
}
} else {
uniqueEdges.set(dirName, edge);
}
}
return Array.from(uniqueEdges.values());
}
export function graphLabels(options: GraphOptions): Graph { export function graphLabels(options: GraphOptions): Graph {
const edges: Array<Edge> = []; const edges: Array<Edge> = [];
const nodes: Array<Node> = []; const nodes: Array<Node> = [];
@ -95,7 +133,8 @@ export function graphLabels(options: GraphOptions): Graph {
sub.edges.push({ sub.edges.push({
source: name, source: name,
target: otherName, target: otherName,
type: ChangeVerb.CONFLICTED, type: EdgeType.FORWARD,
verb: ChangeVerb.CONFLICTED,
}); });
} }
} }
@ -112,7 +151,8 @@ export function graphLabels(options: GraphOptions): Graph {
sub.edges.push({ sub.edges.push({
source: name, source: name,
target: matchLabel, target: matchLabel,
type: ChangeVerb.BECAME, type: EdgeType.FORWARD,
verb: ChangeVerb.BECAME,
}); });
labelEdges({ labelEdges({
@ -141,19 +181,19 @@ export function cleanName(name: string): string {
} }
export function edgeStyle(edge: Edge) { export function edgeStyle(edge: Edge) {
switch (edge.type) { switch (edge.verb) {
case ChangeVerb.BECAME: case ChangeVerb.BECAME:
return '[arrowhead="onormal" color="purple"]'; return `[dir="${edge.type}" arrowhead="onormal" color="purple"]`;
case ChangeVerb.CREATED: case ChangeVerb.CREATED:
return '[color="green"]'; return `[dir="${edge.type}" color="green" weight=0.8]`;
case ChangeVerb.EXISTING: case ChangeVerb.EXISTING:
return '[color="gray" weight=0.1]'; return `[dir="${edge.type}" color="gray" weight=0.1]`;
case ChangeVerb.REMOVED: case ChangeVerb.REMOVED:
return '[color="red"]'; return `[dir="${edge.type}" color="red"]`;
case ChangeVerb.CONFLICTED: case ChangeVerb.CONFLICTED:
return '[color="orange" weight=0.1]'; return `[dir="${edge.type}" color="orange" weight=0.1]`;
case ChangeVerb.REQUIRED: case ChangeVerb.REQUIRED:
return '[arrowhead="onormal" color="blue"]'; return `[dir="${edge.type}" arrowhead="onormal" color="blue"]`;
default: default:
return ''; return '';
} }
@ -183,7 +223,7 @@ export function dotGraph(graph: Graph): string {
lines.push(`label = "${subName}";`); lines.push(`label = "${subName}";`);
lines.push('color = gray'); lines.push('color = gray');
for (const edge of sub.edges) { for (const edge of mergeEdges(sub.edges)) {
const source = cleanName(edge.source); const source = cleanName(edge.source);
const target = cleanName(edge.target); const target = cleanName(edge.target);
lines.push(`${source} -> ${target} ${edgeStyle(edge)};`); lines.push(`${source} -> ${target} ${edgeStyle(edge)};`);
@ -198,7 +238,7 @@ export function dotGraph(graph: Graph): string {
} }
// remaining edges // remaining edges
for (const edge of graph.edges) { for (const edge of mergeEdges(graph.edges)) {
const source = cleanName(edge.source); const source = cleanName(edge.source);
const target = cleanName(edge.target); const target = cleanName(edge.target);
lines.push(`${source} -> ${target} ${edgeStyle(edge)};`); lines.push(`${source} -> ${target} ${edgeStyle(edge)};`);