Skip to content

Commit 5dcb5b8

Browse files
committed
Tidy graph collapsing logic
1 parent 1ee6339 commit 5dcb5b8

File tree

2 files changed

+129
-161
lines changed

2 files changed

+129
-161
lines changed

src/views/Graph.vue

Lines changed: 93 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ fragment FamilyProxyData on FamilyProxy {
214214
isQueued
215215
name
216216
ancestors {
217-
name
217+
name
218218
}
219219
childTasks {
220220
id
@@ -337,6 +337,19 @@ export const childArray = (nodeArray) => {
337337
})
338338
}
339339
340+
/**
341+
* Convert a mapping to a format that VTreeView can handle.
342+
* @param {Map<string, { children: Map, diabled: Boolean }>} map - The mapping to convert
343+
* @returns {Object[]} - The converted mapping
344+
*/
345+
export function convertTree (map) {
346+
return Array.from(map, ([name, { children, disabled }]) => ({
347+
name,
348+
children: convertTree(children),
349+
disabled,
350+
}))
351+
}
352+
340353
export default {
341354
name: 'Graph',
342355
@@ -484,7 +497,7 @@ export default {
484497
* @returns {Family[]} array containing nested structure of families
485498
*/
486499
treeDropDownFamily () {
487-
return this.familyArrayStore.length ? this.getTree() : [{ name: 'No families', disabled: true }]
500+
return this.allParentLookUp.size ? this.getTree() : [{ name: 'No families', disabled: true }]
488501
},
489502
/**
490503
* Gets the array of cycles for use in vuetify toolbar drop down
@@ -498,11 +511,10 @@ export default {
498511
*
499512
* example return mapping:
500513
* {
501-
* root: []
502-
* GREAT_GRANDPARENT_FAMILY : ['root'],
503-
* GRANDPARENT_FAMILY: [ 'GREAT_GRANDPARENT_FAMILY', 'root' ],
504-
* PARENT_FAMILY: [ 'GRANDPARENT_FAMILY', 'GREAT_GRANDPARENT_FAMILY', 'root' ],
505-
* FAMILY: ['PARENT_FAMILY', 'GRANDPARENT_FAMILY', 'GREAT_GRANDPARENT_FAMILY', 'root' ],
514+
* GREAT_GRANDPARENT_FAMILY: [ ],
515+
* GRANDPARENT_FAMILY: [ 'GREAT_GRANDPARENT_FAMILY' ],
516+
* PARENT_FAMILY: [ 'GREAT_GRANDPARENT_FAMILY', 'GRANDPARENT_FAMILY' ],
517+
* FAMILY: [ 'GREAT_GRANDPARENT_FAMILY', 'GRANDPARENT_FAMILY', 'PARENT_FAMILY' ],
506518
* }
507519
*
508520
* note: object value arrays contain family names as strings only - not nodes
@@ -512,11 +524,12 @@ export default {
512524
allParentLookUp () {
513525
const lookup = []
514526
for (const namespace of this.namespaces) {
527+
if (namespace.name === 'root') continue
515528
const ancestors = []
516529
let parent = namespace.node.firstParent
517-
while (parent) {
530+
while (parent.name !== 'root') {
518531
const parentNode = this.cylcTree.$index[parent.id]
519-
ancestors.push(parentNode.name)
532+
ancestors.unshift(parentNode.name)
520533
parent = parentNode.node.firstParent
521534
}
522535
lookup.push([namespace.name, ancestors])
@@ -583,7 +596,7 @@ export default {
583596
lookup[task.id] = childArray([task])
584597
}
585598
// for families
586-
for (const family of this.familyArrayStore) {
599+
for (const family of this.allParentLookUp.keys()) {
587600
const familyId = `${cycle.id}/${family}`
588601
if (this.cylcTree.$index[familyId]) {
589602
lookup[familyId] = childArray([this.cylcTree.$index[familyId]])
@@ -593,13 +606,6 @@ export default {
593606
}
594607
return lookup
595608
},
596-
/**
597-
* Get an array of family names
598-
* @returns {String[]} array of family names
599-
*/
600-
familyArrayStore () {
601-
return this.namespaces.flatMap((family) => family.name !== 'root' ? family.name : [])
602-
},
603609
controlGroups () {
604610
return [
605611
{
@@ -688,23 +694,20 @@ export default {
688694
* @returns {Family[]} array containing nested structure of families
689695
*/
690696
getTree () {
691-
const tree = []
697+
const tree = new Map()
692698
for (const [name, ancestors] of this.allParentLookUp) {
693-
if (name === 'root') continue
694699
let pointer = tree
695-
let ancestorName, disabled
696-
for (let i = ancestors.length - 2; i >= 0; i--) {
697-
ancestorName = ancestors[i]
698-
pointer = pointer.find((item) => item.name === ancestorName).children
699-
disabled ||= this.collapseFamily.includes(ancestorName)
700+
let disabled = false
701+
for (const ancestor of ancestors) {
702+
pointer = pointer.get(ancestor).children
703+
disabled ||= this.collapseFamily.includes(ancestor)
700704
}
701-
pointer.push({
702-
name,
703-
children: [],
705+
pointer.set(name, {
706+
children: new Map(),
704707
disabled,
705708
})
706709
}
707-
return tree
710+
return convertTree(tree)
708711
},
709712
710713
/**
@@ -772,34 +775,18 @@ export default {
772775
},
773776
774777
/**
775-
* Check if node is collapsed by family or ancestor
776-
* If not collapsed return null
777-
* If the node is collapsed by first parent return the parent name
778-
* If the node is collapsed by an ancestor further up the tree return the name of the ancestor
779-
* @property {String} nodeFirstParent - name of the first parent
780-
* @returns {String | null} name of the collapsed parent/ancestor or null
778+
* Check if node is collapsed by an ancestor family
779+
*
780+
* @param {string} id - ID of the task/family/namespace
781+
* @returns {string=} name of the collapsed ancestor if any
781782
*/
782-
isNodeCollapsedByFamily (nodeFirstParent) {
783-
// the nodes first parent is collapsed
784-
const firstParent = this.collapseFamily.includes(nodeFirstParent)
785-
// a family member up the tree is collapsed
786-
const ancestor = this.allParentLookUp.get(nodeFirstParent).some(element => {
787-
return this.collapseFamily.includes(element)
788-
})
789-
if (firstParent && !ancestor) {
790-
// the node is collapsed by its first parent
791-
return nodeFirstParent
792-
} else if (ancestor) {
793-
// the node is collapsed by an ancestor
794-
const lookupParents = this.allParentLookUp.get(nodeFirstParent)
795-
for (let i = lookupParents.length - 1; i >= 0; i--) {
796-
if (this.collapseFamily.includes(lookupParents[i])) {
797-
return lookupParents[i]
798-
}
799-
}
800-
} else {
801-
// the node is not collapsed
802-
return null
783+
getCollapsedAncestor (id) {
784+
const { name } = this.cylcTree.$index[id].node.firstParent
785+
if (name !== 'root') {
786+
// TODO: could use Set.intersection if browser support is good enough?
787+
return this.collapseFamily.find((family) => (
788+
name === family || this.allParentLookUp.get(name).includes(family)
789+
))
803790
}
804791
},
805792
@@ -985,19 +972,14 @@ export default {
985972
) {
986973
// Take our array of grandchildren and remove nodes that we dont want to include
987974
// nodeFormattedArray will be an array of string node ids to be included in the grouping
988-
const nodeFormattedArray = grandChildren.filter((grandChild) => {
989-
let isAncestor = true
990-
const nodeFirstParent = this.cylcTree.$index[grandChild.id].node.firstParent.name
991-
isAncestor = !this.isNodeCollapsedByFamily(nodeFirstParent)
992-
return (
993-
// if its not in the list of families (unless its been collapsed)
994-
(!this.familyArrayStore.includes(grandChild.name) || this.collapseFamily.includes(grandChild.name)) &&
995-
// the node has been removed/collapsed
996-
!removedNodes.has(grandChild.name) &&
997-
// the node doesnt have a collapsed ancestor
998-
isAncestor
999-
)
1000-
}).map(a => `"${a.id}"`)
975+
const nodeFormattedArray = grandChildren.filter((grandChild) => (
976+
// if its not in the list of families (unless its been collapsed)
977+
(!this.allParentLookUp.has(grandChild.name) || this.collapseFamily.includes(grandChild.name)) &&
978+
// the node has been removed/collapsed
979+
!removedNodes.has(grandChild.name) &&
980+
// the node doesnt have a collapsed ancestor
981+
!this.getCollapsedAncestor(grandChild.id)
982+
)).map(a => `"${a.id}"`)
1001983
// if there are any nodes left after the filtering step
1002984
// make a dotcode subgraph string
1003985
if (nodeFormattedArray.length) {
@@ -1090,26 +1072,18 @@ export default {
10901072
}
10911073
}
10921074
}
1093-
const nodeFormattedArray = indexSearch.filter((a) => {
1094-
const isRoot = a.name !== 'root'
1095-
let isAncestor = true
1096-
if (isRoot) {
1097-
const nodeFirstParent = this.cylcTree.$index[a.id].node.firstParent.name
1098-
isAncestor = !this.isNodeCollapsedByFamily(nodeFirstParent)
1099-
}
1100-
return (
1101-
// if its not in the list of families (unless its been collapsed)
1102-
(!this.familyArrayStore.includes(a.name) || this.collapseFamily.includes(a.name)) &&
1103-
// the node has been removed/collapsed
1104-
(!removedNodes.has(a.name) || this.collapseFamily.includes(a.name)) &&
1105-
// its not a node representing this cycle
1106-
a.name !== cycle &&
1107-
// its not a root node
1108-
isRoot &&
1109-
// the node doesnt have a collapsed ancestor
1110-
isAncestor
1111-
)
1112-
}).map(a => `"${a.id}"`)
1075+
const nodeFormattedArray = indexSearch.filter((a) => (
1076+
// if its not in the list of families (unless its been collapsed)
1077+
(!this.allParentLookUp.has(a.name) || this.collapseFamily.includes(a.name)) &&
1078+
// the node has been removed/collapsed
1079+
(!removedNodes.has(a.name) || this.collapseFamily.includes(a.name)) &&
1080+
// its not a node representing this cycle
1081+
a.name !== cycle &&
1082+
// its not the root node
1083+
a.name !== 'root' &&
1084+
// the node doesnt have a collapsed ancestor
1085+
!this.getCollapsedAncestor(a.id)
1086+
)).map(a => `"${a.id}"`)
11131087
ret.push(`
11141088
subgraph cluster_margin_family_${cycle}
11151089
{
@@ -1192,24 +1166,24 @@ export default {
11921166
this.panZoomWidget.zoom(relativeZoom * width / desiredWidth)
11931167
},
11941168
1195-
edgeHasCollapsedTargetFamilyOnly (targetFirstFamily, sourceFirstFamily) {
1196-
const target = this.isNodeCollapsedByFamily(targetFirstFamily)
1197-
if (target && !this.isNodeCollapsedByFamily(sourceFirstFamily)) {
1198-
return { target }
1169+
edgeHasCollapsedTargetAncestorOnly (edge) {
1170+
const target = this.getCollapsedAncestor(edge.target.id)
1171+
if (target && !this.getCollapsedAncestor(edge.source.id)) {
1172+
return target
11991173
}
12001174
},
12011175
1202-
edgeHasCollapsedSourceFamilyOnly (targetFirstFamily, sourceFirstFamily) {
1203-
const source = this.isNodeCollapsedByFamily(sourceFirstFamily)
1204-
if (source && !this.isNodeCollapsedByFamily(targetFirstFamily)) {
1205-
return { source }
1176+
edgeHasCollapsedSourceAncestorOnly (edge) {
1177+
const source = this.getCollapsedAncestor(edge.source.id)
1178+
if (source && !this.getCollapsedAncestor(edge.target.id)) {
1179+
return source
12061180
}
12071181
},
12081182
1209-
edgeHasCollapsedTargetandSourceFamily (targetFirstFamily, sourceFirstFamily) {
1210-
const target = this.isNodeCollapsedByFamily(targetFirstFamily)
1183+
edgeHasCollapsedTargetandSourceAncestor (edge) {
1184+
const target = this.getCollapsedAncestor(edge.target.id)
12111185
if (target) {
1212-
const source = this.isNodeCollapsedByFamily(sourceFirstFamily)
1186+
const source = this.getCollapsedAncestor(edge.source.id)
12131187
if (source) {
12141188
return { target, source }
12151189
}
@@ -1262,53 +1236,45 @@ export default {
12621236
}
12631237
// ...now we have removed any parts of child nodes that shouldnt be there we can add nodes and edges that should be...
12641238
// ---------------ADD NODES BASED ON FAMILY------------
1265-
if (!this.collapseCycle.includes(cycle)) { // cycle collapsing takes priority over family collapsing
1266-
if (!this.isNodeCollapsedByFamily(indexSearch.node.firstParent.name)) {
1267-
nodes.push(indexSearch)
1268-
}
1239+
if (!this.collapseCycle.includes(cycle) && !this.getCollapsedAncestor(indexSearch.id)) {
1240+
nodes.push(indexSearch)
12691241
}
12701242
12711243
// ...this node is collapsed so need to remove any of its children (nodes and edges) from the graph if it has any...
12721244
for (const { id: childID } of this.allChildrenLookUp[indexSearch.id]) {
12731245
for (const edge of removedEdges.filter(({ source }) => source.id === childID)) {
1274-
const sourceFamilyName = this.cylcTree.$index[edge.source.id].node.firstParent.name
1275-
const targetFamilyName = this.cylcTree.$index[edge.target.id].node.firstParent.name
1276-
12771246
if (!this.collapseCycle.includes(edge.source.cycle)) {
1278-
const familyData = this.edgeHasCollapsedSourceFamilyOnly(targetFamilyName, sourceFamilyName)
1279-
if (familyData) {
1247+
const collapsedSourceAncestor = this.edgeHasCollapsedSourceAncestorOnly(edge)
1248+
if (collapsedSourceAncestor) {
12801249
edges.set(
1281-
...this.createEdge('noCollapsed', familyData.source, edge.target.task, edge.source.cycle, edge.target.cycle)
1250+
...this.createEdge('noCollapsed', collapsedSourceAncestor, edge.target.task, edge.source.cycle, edge.target.cycle)
12821251
)
12831252
}
12841253
12851254
if (!this.collapseCycle.includes(edge.target.cycle)) {
1286-
const familyData = this.edgeHasCollapsedTargetandSourceFamily(targetFamilyName, sourceFamilyName)
1287-
if (familyData && (familyData.source !== familyData.target || edge.source.cycle !== edge.target.cycle)) {
1255+
const collapsedAncestors = this.edgeHasCollapsedTargetandSourceAncestor(edge)
1256+
if (collapsedAncestors && (collapsedAncestors.source !== collapsedAncestors.target || edge.source.cycle !== edge.target.cycle)) {
12881257
edges.set(
1289-
...this.createEdge('noCollapsed', familyData.source, familyData.target, edge.source.cycle, edge.target.cycle)
1258+
...this.createEdge('noCollapsed', collapsedAncestors.source, collapsedAncestors.target, edge.source.cycle, edge.target.cycle)
12901259
)
12911260
}
12921261
}
12931262
}
12941263
}
12951264
for (const edge of removedEdges.filter(({ target }) => target.id === childID)) {
1296-
const sourceFamilyName = this.cylcTree.$index[edge.source.id].node.firstParent.name
1297-
const targetFamilyName = this.cylcTree.$index[edge.target.id].node.firstParent.name
1298-
12991265
if (!this.collapseCycle.includes(edge.target.cycle)) {
1300-
const familyData = this.edgeHasCollapsedTargetFamilyOnly(targetFamilyName, sourceFamilyName)
1301-
if (familyData) {
1266+
const collapsedTargetAncestor = this.edgeHasCollapsedTargetAncestorOnly(edge)
1267+
if (collapsedTargetAncestor) {
13021268
edges.set(
1303-
...this.createEdge('noCollapsed', edge.source.task, familyData.target, edge.source.cycle, edge.target.cycle)
1269+
...this.createEdge('noCollapsed', edge.source.task, collapsedTargetAncestor, edge.source.cycle, edge.target.cycle)
13041270
)
13051271
}
13061272
13071273
if (!this.collapseCycle.includes(edge.source.cycle)) {
1308-
const familyData = this.edgeHasCollapsedTargetandSourceFamily(targetFamilyName, sourceFamilyName)
1309-
if (familyData && (familyData.source !== familyData.target || edge.source.cycle !== edge.target.cycle)) {
1274+
const collapsedAncestors = this.edgeHasCollapsedTargetandSourceAncestor(edge)
1275+
if (collapsedAncestors && (collapsedAncestors.source !== collapsedAncestors.target || edge.source.cycle !== edge.target.cycle)) {
13101276
edges.set(
1311-
...this.createEdge('noCollapsed', familyData.source, familyData.target, edge.source.cycle, edge.target.cycle)
1277+
...this.createEdge('noCollapsed', collapsedAncestors.source, collapsedAncestors.target, edge.source.cycle, edge.target.cycle)
13121278
)
13131279
}
13141280
}
@@ -1340,13 +1306,13 @@ export default {
13401306
// ---------------ADD EDGES BASED ON CYCLE POINT------------
13411307
for (const { id: childID } of this.allChildrenLookUp[indexSearch.id]) {
13421308
for (const edge of removedEdges.filter(({ source }) => source.id === childID)) {
1343-
const targetFamilyName = this.cylcTree.$index[edge.target.id].node.firstParent.name
13441309
if (edge.source.cycle === edge.target.cycle) continue
13451310
// edge has collapsed source cycle only
13461311
if (!this.collapseCycle.includes(edge.target.cycle) && this.collapseCycle.includes(edge.source.cycle)) {
1347-
if (this.isNodeCollapsedByFamily(targetFamilyName)) {
1312+
const collapsedTargetAncestor = this.getCollapsedAncestor(edge.target.id)
1313+
if (collapsedTargetAncestor) {
13481314
edges.set(
1349-
...this.createEdge('collapsedSource', edge.source.cycle, this.isNodeCollapsedByFamily(targetFamilyName), edge.source.cycle, edge.target.cycle)
1315+
...this.createEdge('collapsedSource', edge.source.cycle, collapsedTargetAncestor, edge.source.cycle, edge.target.cycle)
13501316
)
13511317
} else {
13521318
edges.set(
@@ -1363,14 +1329,13 @@ export default {
13631329
}
13641330
}
13651331
for (const edge of removedEdges.filter(({ target }) => target.id === childID)) {
1366-
const sourceFamilyName = this.cylcTree.$index[edge.source.id].node.firstParent.name
1367-
13681332
if (edge.source.cycle === edge.target.cycle) continue
13691333
// edge has collapsed target cycle only
13701334
if (this.collapseCycle.includes(edge.target.cycle) && !this.collapseCycle.includes(edge.source.cycle)) {
1371-
if (this.isNodeCollapsedByFamily(sourceFamilyName)) {
1335+
const collapsedSourceAncestor = this.getCollapsedAncestor(edge.source.id)
1336+
if (collapsedSourceAncestor) {
13721337
edges.set(
1373-
...this.createEdge('collapsedTarget', this.isNodeCollapsedByFamily(sourceFamilyName), edge.target.cycle, edge.source.cycle, edge.target.cycle)
1338+
...this.createEdge('collapsedTarget', collapsedSourceAncestor, edge.target.cycle, edge.source.cycle, edge.target.cycle)
13741339
)
13751340
} else {
13761341
edges.set(

0 commit comments

Comments
 (0)