Skip to content

Commit

Permalink
[Ordering] add bias constraints to graph opts
Browse files Browse the repository at this point in the history
Add ability to pass in a list of graph constraints (list of high node vs
low node relationships) to influence the ordering of the graph. Useful
for picking which way edges go and for making a layout more 'stable'
(the onesignal use case is making all branch edges of the same type go
the same way).

This is essentially this commit by izhan
izhan@4b40097
in this PR dagrejs#302
with a couple changes - high/low over left/right plus some type
overrides (alternatively could fork definitelytyped and put the changes
there)
  • Loading branch information
collingreen committed Jan 28, 2022
1 parent c8bb4a1 commit f98fecb
Show file tree
Hide file tree
Showing 4 changed files with 195 additions and 10 deletions.
176 changes: 176 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
// Type definitions for dagre 0.7
// Project: https://github.com/dagrejs/dagre
// Definitions by: Qinfeng Chen <https://github.com/qinfchen>
// Pete Vilter <https://github.com/vilterp>
// David Newell <https://github.com/rustedgrail>
// Graham Lea <https://github.com/GrahamLea>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
// TypeScript Version: 2.2

// export as namespace dagre;

declare module "dagre" {
export namespace graphlib {
class Graph<T = {}> {
constructor(opt?: {
directed?: boolean | undefined;
multigraph?: boolean | undefined;
compound?: boolean | undefined;
});

graph(): GraphLabel;
isDirected(): boolean;
isMultiGraph(): boolean;
setGraph(label: GraphLabel): Graph<T>;

edge(edgeObj: Edge): GraphEdge;
edge(outNodeName: string, inNodeName: string, name?: string): GraphEdge;
edgeCount(): number;
edges(): Edge[];
hasEdge(edgeObj: Edge): boolean;
hasEdge(outNodeName: string, inNodeName: string, name?: string): boolean;
inEdges(inNodeName: string, outNodeName?: string): Edge[] | undefined;
outEdges(outNodeName: string, inNodeName?: string): Edge[] | undefined;
removeEdge(outNodeName: string, inNodeName: string): Graph<T>;
setDefaultEdgeLabel(
callback:
| string
| ((v: string, w: string, name?: string) => string | Label)
): Graph<T>;
setEdge(params: Edge, value?: string | { [key: string]: any }): Graph<T>;
setEdge(
sourceId: string,
targetId: string,
value?: string | Label,
name?: string
): Graph<T>;

children(parentName: string): string | undefined;
hasNode(name: string): boolean;
neighbors(name: string): Array<Node<T>> | undefined;
node(id: string | Label): Node<T>;
nodeCount(): number;
nodes(): string[];
parent(childName: string): string | undefined;
predecessors(name: string): Array<Node<T>> | undefined;
removeNode(name: string): Graph<T>;
filterNodes(callback: (nodeId: string) => boolean): Graph<T>;
setDefaultNodeLabel(
callback: string | ((nodeId: string) => string | Label)
): Graph<T>;
setNode(name: string, label: string | Label): Graph<T>;
setParent(childName: string, parentName: string): void;
sinks(): Array<Node<T>>;
sources(): Array<Node<T>>;
successors(name: string): Array<Node<T>> | undefined;
}

namespace json {
function read(graph: any): Graph;
function write(graph: Graph): any;
}

namespace alg {
function components(graph: Graph): string[][];
function dijkstra(
graph: Graph,
source: string,
weightFn?: WeightFn,
edgeFn?: EdgeFn
): any;
function dijkstraAll(
graph: Graph,
weightFn?: WeightFn,
edgeFn?: EdgeFn
): any;
function findCycles(graph: Graph): string[][];
function floydWarchall(
graph: Graph,
weightFn?: WeightFn,
edgeFn?: EdgeFn
): any;
function isAcyclic(graph: Graph): boolean;
function postorder(graph: Graph, nodeNames: string | string[]): string[];
function preorder(graph: Graph, nodeNames: string | string[]): string[];
function prim<T>(graph: Graph<T>, weightFn?: WeightFn): Graph<T>;
function tarjam(graph: Graph): string[][];
function topsort(graph: Graph): string[];
}
}

export interface Label {
[key: string]: any;
}
export type WeightFn = (edge: Edge) => number;
export type EdgeFn = (outNodeName: string) => GraphEdge[];

export interface GraphLabel {
width?: number | undefined;
height?: number | undefined;
compound?: boolean | undefined;
rankdir?: string | undefined;
align?: string | undefined;
nodesep?: number | undefined;
edgesep?: number | undefined;
ranksep?: number | undefined;
marginx?: number | undefined;
marginy?: number | undefined;
acyclicer?: string | undefined;
ranker?: string | undefined;
}

export interface NodeConfig {
width?: number | undefined;
height?: number | undefined;
}

export interface EdgeConfig {
minlen?: number | undefined;
weight?: number | undefined;
width?: number | undefined;
height?: number | undefined;
lablepos?: "l" | "c" | "r" | undefined;
labeloffest?: number | undefined;
}

interface Constraint {
high: string;
low: string;
}

interface LayoutOptions {
constraints?: Constraint[];
}

export function layout(
graph: graphlib.Graph,
// layout?: GraphLabel & NodeConfig & EdgeConfig
options: LayoutOptions
): void;

export interface Edge {
v: string;
w: string;
name?: string | undefined;
}

export interface GraphEdge {
points: Array<{ x: number; y: number }>;
[key: string]: any;
}

export type Node<T = {}> = T & {
x: number;
y: number;
width: number;
height: number;
class?: string | undefined;
label?: string | undefined;
padding?: number | undefined;
paddingX?: number | undefined;
paddingY?: number | undefined;
rx?: number | undefined;
ry?: number | undefined;
shape?: string | undefined;
};
}
9 changes: 5 additions & 4 deletions lib/layout.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,18 @@ var Graph = require("./graphlib").Graph;

module.exports = layout;

function layout(g, opts) {
function layout(g, inputOpts) {
var opts = inputOpts || {};
var time = opts && opts.debugTiming ? util.time : util.notime;
time("layout", function() {
var layoutGraph =
time(" buildLayoutGraph", function() { return buildLayoutGraph(g); });
time(" runLayout", function() { runLayout(layoutGraph, time); });
time(" runLayout", function() { runLayout(layoutGraph, time, opts); });
time(" updateInputGraph", function() { updateInputGraph(g, layoutGraph); });
});
}

function runLayout(g, time) {
function runLayout(g, time, opts) {
time(" makeSpaceForEdgeLabels", function() { makeSpaceForEdgeLabels(g); });
time(" removeSelfEdges", function() { removeSelfEdges(g); });
time(" acyclic", function() { acyclic.run(g); });
Expand All @@ -42,7 +43,7 @@ function runLayout(g, time) {
time(" normalize.run", function() { normalize.run(g); });
time(" parentDummyChains", function() { parentDummyChains(g); });
time(" addBorderSegments", function() { addBorderSegments(g); });
time(" order", function() { order(g); });
time(" order", function() { order(g, opts); });
time(" insertSelfEdges", function() { insertSelfEdges(g); });
time(" adjustCoordinateSystem", function() { coordinateSystem.adjust(g); });
time(" position", function() { position(g); });
Expand Down
14 changes: 11 additions & 3 deletions lib/order/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ module.exports = order;
* 1. Graph nodes will have an "order" attribute based on the results of the
* algorithm.
*/
function order(g) {
function order(g, opts) {
var maxRank = util.maxRank(g),
downLayerGraphs = buildLayerGraphs(g, _.range(1, maxRank + 1), "inEdges"),
upLayerGraphs = buildLayerGraphs(g, _.range(maxRank - 1, -1, -1), "outEdges");
Expand All @@ -37,15 +37,18 @@ function order(g) {
var bestCC = Number.POSITIVE_INFINITY,
best;

var constraints = opts.constraints || [];
for (var i = 0, lastBest = 0; lastBest < 4; ++i, ++lastBest) {
sweepLayerGraphs(i % 2 ? downLayerGraphs : upLayerGraphs, i % 4 >= 2);
sweepLayerGraphs(i % 2 ? downLayerGraphs : upLayerGraphs, i % 4 >= 2, constraints);

layering = util.buildLayerMatrix(g);
var cc = crossCount(g, layering);
if (cc < bestCC) {
lastBest = 0;
best = _.cloneDeep(layering);
bestCC = cc;
} else if (cc === bestCC) {
best = _.cloneDeep(layering);
}
}

Expand All @@ -58,8 +61,13 @@ function buildLayerGraphs(g, ranks, relationship) {
});
}

function sweepLayerGraphs(layerGraphs, biasRight) {
function sweepLayerGraphs(layerGraphs, biasRight, constraints) {
var cg = new Graph();

_.forEach(constraints, function(constraint) {
cg.setEdge(constraint.high, constaint.low);
});

_.forEach(layerGraphs, function(lg) {
var root = lg.graph().root;
var sorted = sortSubgraph(lg, root, cg, biasRight);
Expand Down
6 changes: 3 additions & 3 deletions test/order/order-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ describe("order", function() {
g.setPath(["a", "b", "c"]);
g.setEdge("b", "d");
g.setPath(["a", "e", "f"]);
order(g);
order(g, {});
var layering = util.buildLayerMatrix(g);
expect(crossCount(g, layering)).to.equal(0);
});
Expand All @@ -30,7 +30,7 @@ describe("order", function() {
_.forEach(["a", "d"], function(v) { g.setNode(v, { rank: 1 }); });
_.forEach(["b", "f", "e"], function(v) { g.setNode(v, { rank: 2 }); });
_.forEach(["c", "g"], function(v) { g.setNode(v, { rank: 3 }); });
order(g);
order(g, {});
var layering = util.buildLayerMatrix(g);
expect(crossCount(g, layering)).to.equal(0);
});
Expand All @@ -40,7 +40,7 @@ describe("order", function() {
_.forEach(["b", "e", "g"], function(v) { g.setNode(v, { rank: 2 }); });
_.forEach(["c", "f", "h"], function(v) { g.setNode(v, { rank: 3 }); });
g.setNode("d", { rank: 4 });
order(g);
order(g, {});
var layering = util.buildLayerMatrix(g);
expect(crossCount(g, layering)).to.be.lte(1);
});
Expand Down

0 comments on commit f98fecb

Please sign in to comment.