@hpcc-js/comms
Version:
hpcc-js - Communications
814 lines (728 loc) • 28.1 kB
text/typescript
// Ported from: https://github.com/hpcc-systems/HPCC-Platform/blob/f0ed9dbeca49c39fb55aa28fec295c89407ac663/esp/src/src/ESPGraph.ts
export function safeAssign(obj: { [id: string]: any }, key: string, value: string) {
if (key === "__proto__" || key === "constructor" || key === "prototype") return;
obj[key] = value;
}
function xmlEncode(str: string): string {
str = "" + str;
return str.replace(/&/g, "&")
.replace(/"/g, """)
.replace(/'/g, "'")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/\n/g, " ")
.replace(/\r/g, " ")
;
}
function espTime2Seconds(duration?: string): number {
if (!duration) {
return 0;
} else if (!isNaN(+duration)) {
return parseFloat(duration);
}
const re = /(?:(?:(\d+).days.)?(?:(\d+)h)?(?:(\d+)m)?(?:(\d+\.\d+|\d+)s))|(?:(\d+\.\d+|\d+)ms|(\d+\.\d+|\d+)us|(\d+\.\d+|\d+)ns)/;
const match = re.exec(duration);
if (!match) return 0;
const days = +match[1] || 0;
const hours = +match[2] || 0;
const mins = +match[3] || 0;
const secs = +match[4] || 0;
const ms = +match[5] || 0;
const us = +match[6] || 0;
const ns = +match[7] || 0;
return (days * 24 * 60 * 60) + (hours * 60 * 60) + (mins * 60) + secs + ms / 1000 + us / 1000000 + ns / 1000000000;
}
function unitTest(size: string, unit: string) {
const nsIndex = size.indexOf(unit);
if (nsIndex !== -1) {
return parseFloat(size.substring(0, nsIndex));
}
return -1;
}
function espSize2Bytes(size: string): number {
if (!size) {
return 0;
} else if (!isNaN(+size)) {
return parseFloat(size);
}
let retVal = unitTest(size, "Kb");
if (retVal >= 0) {
return retVal * 1024;
}
retVal = unitTest(size, "Mb");
if (retVal >= 0) {
return retVal * Math.pow(1024, 2);
}
retVal = unitTest(size, "Gb");
if (retVal >= 0) {
return retVal * Math.pow(1024, 3);
}
retVal = unitTest(size, "Tb");
if (retVal >= 0) {
return retVal * Math.pow(1024, 4);
}
retVal = unitTest(size, "Pb");
if (retVal >= 0) {
return retVal * Math.pow(1024, 5);
}
retVal = unitTest(size, "Eb");
if (retVal >= 0) {
return retVal * Math.pow(1024, 6);
}
retVal = unitTest(size, "Zb");
if (retVal >= 0) {
return retVal * Math.pow(1024, 7);
}
retVal = unitTest(size, "b");
if (retVal >= 0) {
return retVal;
}
return 0;
}
function espSkew2Number(skew: string): number {
if (!skew) {
return 0;
}
return parseFloat(skew);
}
enum GRAPH_TYPE {
UNKNOWN = 0,
GRAPH = 1,
SUBGRAPH = 2,
VERTEX = 3,
EDGE = 4,
LAST = 5
}
enum GRAPH_TYPE_STRING {
UNKNOWN = "Unknown",
GRAPH = "Graph",
SUBGRAPH = "Cluster",
VERTEX = "Vertex",
EDGE = "Edge",
LAST = "Last"
}
class LocalisedXGMMLWriter {
graph: QueryGraph;
m_xgmml: string;
m_visibleSubgraphs: { [id: string]: Subgraph };
m_visibleVertices: { [id: string]: Vertex };
m_semiVisibleVertices: { [id: string]: Vertex };
m_visibleEdges: { [id: string]: Edge };
noSpills: boolean;
constructor(graph: QueryGraph) {
this.graph = graph;
this.m_xgmml = "";
this.m_visibleSubgraphs = {};
this.m_visibleVertices = {};
this.m_semiVisibleVertices = {};
this.m_visibleEdges = {};
}
calcVisibility(items: GraphItem[], localisationDepth: number, localisationDistance: number, noSpills: boolean): void {
this.noSpills = noSpills;
items.forEach((item) => {
if (this.graph.isVertex(item)) {
this.calcInVertexVisibility(item, localisationDistance);
this.calcOutVertexVisibility(item, localisationDistance);
} else if (this.graph.isEdge(item)) {
this.calcInVertexVisibility(item.getSource(), localisationDistance - 1);
this.calcOutVertexVisibility(item.getTarget(), localisationDistance - 1);
} else if (this.graph.isSubgraph(item)) {
this.m_visibleSubgraphs[item.__hpcc_id] = item;
this.calcSubgraphVisibility(item, localisationDepth - 1);
}
});
this.calcVisibility2();
}
calcInVertexVisibility(vertex: Vertex, localisationDistance: number) {
if (this.noSpills && vertex.isSpill()) {
localisationDistance++;
}
this.m_visibleVertices[vertex.__hpcc_id] = vertex;
if (localisationDistance > 0) {
vertex.getInEdges().forEach(edge => {
this.calcInVertexVisibility(edge.getSource(), localisationDistance - 1);
});
}
}
calcOutVertexVisibility(vertex: Vertex, localisationDistance: number): void {
if (this.noSpills && vertex.isSpill()) {
localisationDistance++;
}
this.m_visibleVertices[vertex.__hpcc_id] = vertex;
if (localisationDistance > 0) {
vertex.getOutEdges().forEach(edge => {
this.calcOutVertexVisibility(edge.getTarget(), localisationDistance - 1);
});
}
}
calcSubgraphVisibility(subgraph: Subgraph, localisationDepth: number): void {
if (localisationDepth < 0) {
return;
}
if (localisationDepth > 0) {
subgraph.__hpcc_subgraphs.forEach((subgraph, idx) => {
this.calcSubgraphVisibility(subgraph, localisationDepth - 1);
});
}
subgraph.__hpcc_subgraphs.forEach((subgraph, idx) => {
this.m_visibleSubgraphs[subgraph.__hpcc_id] = subgraph;
});
subgraph.__hpcc_vertices.forEach((vertex, idx) => {
this.m_visibleVertices[vertex.__hpcc_id] = vertex;
});
// Calculate edges that pass through the subgraph ---
const dedupEdges = {};
this.graph.edges.forEach((edge: Edge, idx: any) => {
if (edge.getSource().__hpcc_parent !== edge.getTarget().__hpcc_parent && subgraph === this.getCommonAncestor(edge)) {
// Only include one unique edge between subgraphs ---
if (!dedupEdges[edge.getSource().__hpcc_parent.__hpcc_id + "::" + edge.getTarget().__hpcc_parent.__hpcc_id]) {
dedupEdges[edge.getSource().__hpcc_parent.__hpcc_id + "::" + edge.getTarget().__hpcc_parent.__hpcc_id] = true;
this.m_visibleEdges[edge.__hpcc_id] = edge;
}
}
});
}
buildVertexString(vertex: Vertex, isPoint: boolean): string {
let attrStr = "";
let propsStr = "";
const props = vertex.getProperties();
for (const key in props) {
if (isPoint && key.indexOf("_kind") >= 0) {
propsStr += "<att name=\"_kind\" value=\"point\"/>";
} else if (key === "id" || key === "label") {
attrStr += " " + key + "=\"" + xmlEncode(props[key]) + "\"";
} else {
propsStr += "<att name=\"" + key + "\" value=\"" + xmlEncode(props[key]) + "\"/>";
}
}
return "<node" + attrStr + ">" + propsStr + "</node>";
}
buildEdgeString(edge: Edge): string {
let attrStr: string = "";
let propsStr: string = "";
const props = edge.getProperties();
for (const key in props) {
if (key.toLowerCase() === "id" ||
key.toLowerCase() === "label" ||
key.toLowerCase() === "source" ||
key.toLowerCase() === "target") {
attrStr += " " + key + "=\"" + xmlEncode(props[key]) + "\"";
} else {
propsStr += "<att name=\"" + key + "\" value=\"" + xmlEncode(props[key]) + "\"/>";
}
}
return "<edge" + attrStr + ">" + propsStr + "</edge>";
}
getAncestors(v: Vertex, ancestors: Subgraph[]): void {
let parent = v.__hpcc_parent;
while (parent) {
ancestors.push(parent);
parent = parent.__hpcc_parent;
}
}
getCommonAncestorV(v1: Vertex, v2: Vertex): Subgraph | null {
const v1_ancestors = [];
const v2_ancestors = [];
this.getAncestors(v1, v1_ancestors);
this.getAncestors(v2, v2_ancestors);
let finger1 = v1_ancestors.length - 1;
let finger2 = v2_ancestors.length - 1;
let retVal = null;
while (finger1 >= 0 && finger2 >= 0 && v1_ancestors[finger1] === v2_ancestors[finger2]) {
retVal = v1_ancestors[finger1];
--finger1;
--finger2;
}
return retVal;
}
getCommonAncestor(e: Edge): Subgraph | null {
return this.getCommonAncestorV(e.getSource(), e.getTarget());
}
calcAncestorVisibility(vertex: Vertex): void {
const ancestors = [];
this.getAncestors(vertex, ancestors);
ancestors.forEach((item, idx) => {
this.m_visibleSubgraphs[item.__hpcc_id] = item;
});
}
calcVisibility2(): void {
for (const key in this.m_visibleVertices) {
const vertex = this.m_visibleVertices[key];
vertex.getInEdges().forEach((edge: Edge, idx: any) => {
this.m_visibleEdges[edge.__hpcc_id] = edge;
});
vertex.getOutEdges().forEach((edge: Edge, idx: any) => {
this.m_visibleEdges[edge.__hpcc_id] = edge;
});
this.calcAncestorVisibility(vertex);
}
this.calcSemiVisibleVertices();
}
addSemiVisibleEdge(edge: Edge): void {
if (edge && !this.m_visibleEdges[edge.__hpcc_id]) {
this.m_visibleEdges[edge.__hpcc_id] = edge;
}
}
addSemiVisibleVertex(vertex: Vertex): void {
if (!this.m_visibleVertices[vertex.__hpcc_id]) {
this.m_semiVisibleVertices[vertex.__hpcc_id] = vertex;
this.calcAncestorVisibility(vertex);
}
}
calcSemiVisibleVertices(): void {
for (const key in this.m_visibleEdges) {
const edge = this.m_visibleEdges[key];
let source = edge.getSource();
this.addSemiVisibleVertex(source);
while (this.noSpills && source.isSpill()) {
const inEdges = source.getInEdges();
if (inEdges.length) {
this.addSemiVisibleEdge(inEdges[0]);
source = inEdges[0].getSource();
this.addSemiVisibleVertex(source);
} else {
break;
}
}
let target = edge.getTarget();
this.addSemiVisibleVertex(target);
while (this.noSpills && target.isSpill()) {
const outEdges = target.getOutEdges();
if (outEdges.length) {
this.addSemiVisibleEdge(outEdges[0]);
target = outEdges[0].getTarget();
this.addSemiVisibleVertex(target);
} else {
break;
}
}
}
}
writeXgmml(): void {
this.subgraphVisited(this.graph.subgraphs[0], true);
this.graph.edges.forEach((edge: any, idx: any) => {
this.edgeVisited(edge);
});
}
subgraphVisited(subgraph: Subgraph, root: boolean = false): boolean {
if (this.m_visibleSubgraphs[subgraph.__hpcc_id]) {
let propsStr = "";
this.m_xgmml += root ? "" : "<node id=\"" + subgraph.__hpcc_id + "\"><att><graph>";
const xgmmlLen = this.m_xgmml.length;
subgraph.walkSubgraphs(this);
subgraph.walkVertices(this);
if (xgmmlLen === this.m_xgmml.length) {
// Add at least one child otherwise subgraphs will render as a vertex ---
const vertex = subgraph.__hpcc_vertices[0];
if (vertex) {
this.m_xgmml += this.buildVertexString(vertex, true);
}
}
const props = subgraph.getProperties();
for (const key in props) {
propsStr += "<att name=\"" + key + "\" value=\"" + xmlEncode(props[key]) + "\"/>";
}
this.m_xgmml += root ? "" : "</graph></att>" + propsStr + "</node>";
}
return false;
}
vertexVisited(vertex: Vertex) {
if (this.m_visibleVertices[vertex.__hpcc_id]) {
this.m_xgmml += this.buildVertexString(vertex, false);
} else if (this.m_semiVisibleVertices[vertex.__hpcc_id]) {
this.m_xgmml += this.buildVertexString(vertex, true);
}
}
edgeVisited(edge: Edge) {
if (this.m_visibleEdges[edge.__hpcc_id]) {
this.m_xgmml += this.buildEdgeString(edge);
}
}
}
abstract class GraphItem {
abstract _globalType: "Graph" | "Cluster" | "Vertex" | "Edge";
__hpcc_graph: QueryGraph;
__hpcc_parent: Subgraph;
__widget: any;
__hpcc_id: string;
_globalID: string;
constructor(graph: QueryGraph, id: string) {
this.__hpcc_graph = graph;
this.__hpcc_id = id;
this._globalID = id;
}
getProperties() {
const retVal: { [id: string]: any } = {};
for (const key in this) {
if (key.indexOf("__") !== 0 && this.hasOwnProperty(key)) {
retVal[key] = this[key];
}
}
return retVal;
}
}
class Subgraph extends GraphItem {
_globalType: "Graph" | "Cluster" | "Vertex" | "Edge";
__hpcc_subgraphs: any[];
__hpcc_vertices: any[];
__hpcc_edges: any[];
id: string;
constructor(graph: QueryGraph, id: string) {
super(graph, id);
this._globalType = id === "0" ? "Graph" : "Cluster";
this.__hpcc_subgraphs = [];
this.__hpcc_vertices = [];
this.__hpcc_edges = [];
this.id = id;
}
addSubgraph(subgraph) {
subgraph.__hpcc_parent = this;
if (!this.__hpcc_subgraphs.some(subgraph2 => subgraph === subgraph2)) {
this.__hpcc_subgraphs.push(subgraph);
}
}
addVertex(vertex) {
vertex.__hpcc_parent = this;
if (!this.__hpcc_vertices.some(vertex2 => vertex === vertex2)) {
this.__hpcc_vertices.push(vertex);
}
}
removeVertex(vertex: any) {
this.__hpcc_vertices = this.__hpcc_vertices.filter(vertex2 => vertex !== vertex2);
}
addEdge(edge) {
edge.__hpcc_parent = this;
if (!this.__hpcc_edges.some(edge2 => edge === edge2)) {
this.__hpcc_edges.push(edge);
}
}
removeEdge(edge: any) {
this.__hpcc_edges = this.__hpcc_edges.filter(edge2 => edge !== edge2);
}
remove() {
this.__hpcc_subgraphs.forEach(subgraph => subgraph.__hpcc_parent = this.__hpcc_parent);
this.__hpcc_vertices.forEach(vertex => vertex.__hpcc_parent = this.__hpcc_parent);
this.__hpcc_edges.forEach(edge => edge.__hpcc_parent = this.__hpcc_parent);
delete this.__hpcc_parent;
this.__hpcc_graph.removeItem(this);
}
walkSubgraphs(visitor: { subgraphVisited: (arg0: Subgraph) => boolean; }) {
this.__hpcc_subgraphs.forEach((subgraph, idx) => {
if (visitor.subgraphVisited(subgraph)) {
subgraph.walkSubgraphs(visitor);
}
});
}
walkVertices(visitor: { vertexVisited: (arg0: Vertex) => void; }) {
this.__hpcc_vertices.forEach((vertex, idx) => {
visitor.vertexVisited(vertex);
});
}
}
class Vertex extends GraphItem {
_globalType: "Graph" | "Cluster" | "Vertex" | "Edge" = "Vertex";
_isSpill: boolean;
constructor(graph: QueryGraph, id: string) {
super(graph, id);
}
isSpill() {
return this._isSpill;
}
remove() {
const inVertices = this.getInVertices();
if (inVertices.length <= 1) {
console.warn(this.__hpcc_id + ": remove only supports single or zero inputs activities...");
}
this.getInEdges().forEach((edge: Edge) => {
edge.remove();
});
this.getOutEdges().forEach((edge: Edge) => {
edge.setSource(inVertices[0]);
});
this.__hpcc_parent?.removeVertex(this);
this.__hpcc_graph.removeItem(this);
}
getInVertices(): Vertex[] {
return this.getInEdges().map((edge) => {
return edge.getSource();
});
}
getInEdges(): Edge[] {
return this.__hpcc_graph.edges.filter((edge) => {
return edge.getTarget() === this;
});
}
getOutVertices(): Vertex[] {
return this.getOutEdges().map((edge) => {
return edge.getTarget();
});
}
getOutEdges(): Edge[] {
return this.__hpcc_graph.edges.filter((edge) => {
return edge.getSource() === this;
});
}
}
class Edge extends GraphItem {
_globalType: "Graph" | "Cluster" | "Vertex" | "Edge" = "Edge";
_sourceActivity: any;
source: any;
_targetActivity: any;
target: any;
constructor(graph: QueryGraph, id: string) {
super(graph, id);
this._globalType = "Edge";
}
remove() {
this.__hpcc_graph.subgraphs.forEach((subgraph) => {
subgraph.removeEdge(this);
});
this.__hpcc_graph.removeItem(this);
}
getSource(): Vertex {
return this.__hpcc_graph.idx[this._sourceActivity || this.source] as Vertex;
}
setSource(source: Vertex) {
if (this._sourceActivity) {
this._sourceActivity = source.__hpcc_id;
} else if (this.source) {
this.source = source.__hpcc_id;
}
if (this.__widget) {
this.__widget.setSource(this.getSource().__widget);
}
}
getTarget(): Vertex {
return this.__hpcc_graph.idx[this._targetActivity || this.target] as Vertex;
}
}
export class QueryGraph {
idx: { [id: string]: Subgraph | Vertex | Edge } = {};
subgraphs: Subgraph[] = [];
vertices: Vertex[] = [];
edges: Edge[] = [];
xgmml: string = "";
constructor() {
this.clear();
}
clear() {
this.xgmml = "";
this.idx = {};
this.subgraphs = [];
this.vertices = [];
this.edges = [];
}
load(xgmml: string) {
this.clear();
this.merge(xgmml);
}
merge(xgmml: string) {
this.xgmml = xgmml;
const parser = new DOMParser();
const dom = parser.parseFromString(xgmml, "text/xml");
this.walkDocument(dom.documentElement, "0");
}
isSubgraph(item: GraphItem): item is Subgraph {
return item instanceof Subgraph;
}
isVertex(item: GraphItem): item is Vertex {
return item instanceof Vertex;
}
isEdge(item: GraphItem): item is Edge {
return item instanceof Edge;
}
getGlobalType(item: QueryGraph | Subgraph | Vertex | Edge): GRAPH_TYPE {
if (item instanceof Vertex) {
return GRAPH_TYPE.VERTEX;
} else if (item instanceof Edge) {
return GRAPH_TYPE.EDGE;
} else if (item instanceof Subgraph) {
return GRAPH_TYPE.SUBGRAPH;
} else if (item instanceof QueryGraph) {
return GRAPH_TYPE.GRAPH;
}
return GRAPH_TYPE.UNKNOWN;
}
getGlobalTypeString(item: QueryGraph | Subgraph | Vertex | Edge): GRAPH_TYPE_STRING {
if (item instanceof Vertex) {
return GRAPH_TYPE_STRING.VERTEX;
} else if (item instanceof Edge) {
return GRAPH_TYPE_STRING.EDGE;
} else if (item instanceof Subgraph) {
return GRAPH_TYPE_STRING.SUBGRAPH;
} else if (item instanceof QueryGraph) {
return GRAPH_TYPE_STRING.GRAPH;
}
return GRAPH_TYPE_STRING.UNKNOWN;
}
getItem(docNode: HTMLElement, id: string): Subgraph | Vertex | Edge {
if (!this.idx[id]) {
switch (docNode.tagName) {
case "graph":
const subgraph = new Subgraph(this, id);
this.subgraphs.push(subgraph);
this.idx[id] = subgraph;
break;
case "node":
const vertex = new Vertex(this, id);
this.vertices.push(vertex);
this.idx[id] = vertex;
break;
case "edge":
const edge = new Edge(this, id);
this.edges.push(edge);
this.idx[id] = edge;
break;
default:
console.warn("Graph.getItem - Unknown Node Type!");
break;
}
}
const retVal = this.idx[id];
Array.from(docNode.attributes).forEach(attr => {
safeAssign(retVal, attr.name, attr.value);
});
return retVal;
}
removeItem(item: Subgraph | Vertex | Edge) {
delete this.idx[item.__hpcc_id];
if (item instanceof Subgraph) {
this.subgraphs = this.subgraphs.filter((subgraph: Subgraph) => {
return item !== subgraph;
});
} else if (item instanceof Vertex) {
this.vertices = this.vertices.filter(vertex => {
return item !== vertex;
});
} else if (item instanceof Edge) {
this.edges = this.edges.filter((edge: Edge) => {
return item !== edge;
});
}
}
getChildByTagName(docNode: HTMLElement, tagName: string): HTMLElement | null {
let retVal: HTMLElement | null = null;
Array.from(docNode.childNodes as NodeListOf<HTMLElement>).some((childNode) => {
if (childNode.tagName === tagName) {
retVal = childNode;
return true;
}
});
return retVal;
}
walkDocument(docNode: HTMLElement, id: string): Subgraph | Vertex | Edge {
const retVal: any = this.getItem(docNode, id);
(docNode.childNodes as NodeListOf<HTMLElement>).forEach((childNode) => {
switch (childNode.nodeType) {
case 1: // ELEMENT_NODE
switch (childNode.tagName) {
case "graph":
break;
case "node":
let isSubgraph = false;
const attNode = this.getChildByTagName(childNode, "att");
if (attNode) {
const graphNode = this.getChildByTagName(attNode, "graph");
if (graphNode) {
isSubgraph = true;
const subgraph = this.walkDocument(graphNode, childNode.getAttribute("id"));
retVal.addSubgraph(subgraph);
}
}
if (!isSubgraph) {
const vertex = this.walkDocument(childNode, childNode.getAttribute("id"));
retVal.addVertex(vertex);
}
break;
case "att":
const name = childNode.getAttribute("name");
const uname = "_" + name;
const value = childNode.getAttribute("value");
if (name.indexOf("Time") === 0) {
safeAssign(retVal, uname, value);
safeAssign(retVal, name, "" + espTime2Seconds(value));
} else if (name.indexOf("Size") === 0) {
safeAssign(retVal, uname, value);
safeAssign(retVal, name, "" + espSize2Bytes(value));
} else if (name.indexOf("Skew") === 0) {
safeAssign(retVal, uname, value);
safeAssign(retVal, name, "" + espSkew2Number(value));
} else {
safeAssign(retVal, name, value);
}
break;
case "edge":
const edge: any = this.walkDocument(childNode, childNode.getAttribute("id"));
if (edge.NumRowsProcessed !== undefined) {
edge._eclwatchCount = edge.NumRowsProcessed.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
} else if (edge.Count !== undefined) {
edge._eclwatchCount = edge.Count.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
} else if (edge.count !== undefined) {
edge._eclwatchCount = edge.count.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
if (edge.inputProgress) {
edge._eclwatchInputProgress = "[" + edge.inputProgress.replace(/\B(?=(\d{3})+(?!\d))/g, ",") + "]";
}
if (edge.SkewMaxRowsProcessed && edge.SkewMinRowsProcessed) {
edge._eclwatchSkew = "+" + edge.SkewMaxRowsProcessed + ", " + edge.SkewMinRowsProcessed;
}
if (edge._dependsOn) {
} else if (edge._childGraph) {
} else if (edge._sourceActivity || edge._targetActivity) {
edge._isSpill = true;
const source = edge.getSource();
if (source) {
source._isSpill = true;
}
const target = edge.getTarget();
if (target) {
target._isSpill = true;
}
}
retVal.addEdge(edge);
break;
default:
break;
}
break;
case 2: // ATTRIBUTE_NODE
case 3: // TEXT_NODE
case 4: // CDATA_SECTION_NODE
case 5: // ENTITY_REFERENCE_NODE
case 6: // ENTITY_NODE
case 7: // PROCESSING_INSTRUCTION_NODE
case 8: // COMMENT_NODE
case 9: // DOCUMENT_NODE
case 10: // DOCUMENT_TYPE_NODE
case 11: // DOCUMENT_FRAGMENT_NODE
case 12: // NOTATION_NODE
break;
default:
break;
}
});
return retVal;
}
removeSubgraphs(): void {
const subgraphs = [...this.subgraphs];
subgraphs.forEach((subgraph) => {
if (subgraph.__hpcc_parent instanceof Subgraph) {
subgraph.remove();
}
});
}
removeSpillVertices(): void {
const vertices = [...this.vertices];
vertices.forEach((vertex) => {
if (vertex.isSpill()) {
vertex.remove();
}
});
}
getLocalisedXGMML(items: GraphItem[], localisationDepth: number, localisationDistance: number, noSpills: boolean): string {
const xgmmlWriter = new LocalisedXGMMLWriter(this);
xgmmlWriter.calcVisibility(items, localisationDepth, localisationDistance, noSpills);
xgmmlWriter.writeXgmml();
return "<graph>" + xgmmlWriter.m_xgmml + "</graph>";
}
}