fix(graph): merge edges between the same pair of nodes
This commit is contained in:
parent
2abe6b66c9
commit
80caa7a579
70
src/graph.ts
70
src/graph.ts
|
@ -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)};`);
|
||||||
|
|
Loading…
Reference in New Issue