Skip to content

Commit 6499549

Browse files
committed
fixing behaviour of Sunburst with subject list
1 parent ea2810a commit 6499549

4 files changed

Lines changed: 175 additions & 48 deletions

File tree

src/components/Ontologies/OntologySunburst.vue

Lines changed: 83 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ if (typeof Sunburst === "function") {
1818
1919
export default {
2020
name: "OntologySunburst",
21+
props: {
22+
itemClicked: { default: null, type: Object },
23+
},
24+
emits: ["subjectNode"],
2125
setup() {
2226
const theme = useTheme();
2327
return { theme };
@@ -142,6 +146,8 @@ export default {
142146
filename: "SRAO-Sunburst",
143147
},
144148
},
149+
subjectsArrList: [],
150+
nodeClicked: "",
145151
};
146152
},
147153
computed: {
@@ -163,29 +169,90 @@ export default {
163169
};
164170
return options;
165171
},
166-
...mapState("ontologyBrowser", ["sunburstData", "loadingData", "tree"]),
172+
...mapState("ontologyBrowser", [
173+
"sunburstData",
174+
"loadingData",
175+
"tree",
176+
"flattenedTree",
177+
]),
178+
},
179+
mounted() {
180+
this.flattenedOriginalTree(this.tree);
167181
},
168182
methods: {
183+
/**
184+
* Recursively flattens a hierarchical tree structure into a single array of nodes.
185+
* Each node is added to the `subjectsArrList` array. If a node has children,
186+
* the method is called recursively on the children.
187+
*
188+
* @param {Array<Object>} tree - The hierarchical tree structure to be flattened. Each node may have a `children` property containing an array of child nodes.
189+
* @return {void} This method does not return a value. It modifies the `subjectsArrList` array in place.
190+
*/
191+
flattenedOriginalTree(tree) {
192+
tree.forEach((item) => {
193+
this.subjectsArrList.push(item);
194+
if (item["children"] && item["children"].length) {
195+
this.flattenedOriginalTree(item["children"]);
196+
}
197+
});
198+
},
199+
200+
/**
201+
* Handles click events on a node and performs routing or emits a selected subject node to the parent component.
202+
*
203+
* @param {Object} node - The node object that was clicked. This object contains properties such as `descendants_count`,`name`, `identifier`, and `ancestors` which are used in processing the click event.
204+
* @return {void} This method does not return any value. It performs routing or emits an event based on the clicked node.
205+
*/
169206
processClickEvent(node) {
170-
if (node.descendants_count === 0) {
171-
let currentTerm = this.$route.query.term
172-
? decodeURIComponent(this.$route.query.term)
173-
: null;
174-
if (!currentTerm || currentTerm !== node.name) {
175-
this.$router.push({
176-
path: this.$route.path,
177-
query: { term: encodeURIComponent(node.name) },
178-
});
207+
if (node.name !== "Subject") {
208+
if (node.descendants_count === 0) {
209+
let currentTerm = this.$route.query.term
210+
? decodeURIComponent(this.$route.query.term)
211+
: null;
212+
if (!currentTerm || currentTerm !== node.name) {
213+
this.$router.push({
214+
path: this.$route.path,
215+
query: { term: encodeURIComponent(node.name) },
216+
});
217+
}
179218
}
180-
} else {
181-
if (node.name !== "Subject") {
182-
let ancestors = this.getAncestors()(node.identifier);
183-
if (node["innerArcLength"] === 0)
184-
ancestors = ancestors.concat(node.id);
185-
this.openTerms(ancestors);
219+
220+
//If the node is clicked for the first time emit node, if the same node is clicked second time while it is active this means that node was already open.
221+
//Hence it's ancestor should be passed.
222+
if (node["ancestors"] && !node["ancestors"].length) {
223+
this.$emit("subjectNode", []);
224+
} else {
225+
if (this.nodeClicked !== node["identifier"]) {
226+
this.nodeClicked = node["identifier"];
227+
} else {
228+
let nodeAncestor = node["ancestors"].pop();
229+
this.nodeClicked = nodeAncestor;
230+
}
231+
232+
//Filter the selected subject from the arrayList and emit to the parent component
233+
let selectedSubject = this.subjectsArrList.filter(
234+
(n) => n.identifier === this.nodeClicked,
235+
);
236+
237+
//Adding key-value pair
238+
if (
239+
selectedSubject[0]["children"] &&
240+
selectedSubject[0]["children"].length
241+
) {
242+
selectedSubject[0]["hasChildren"] = true;
243+
selectedSubject[0]["isSunburst"] = true;
244+
}
245+
246+
this.$emit("subjectNode", selectedSubject);
186247
}
187248
}
188249
},
250+
251+
/**
252+
* Get ToolTip
253+
* @param point
254+
* @return {boolean|string}
255+
*/
189256
getTooltip(point) {
190257
return point.name === "Subjects"
191258
? false

src/components/Ontologies/TermDetails.vue

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<v-chip
66
:class="`text-${color} border-${color} border border-solid border-opacity-100 mb-3 cursor-pointer bg-white`"
77
variant="elevated"
8-
@click="$router.push({ path: '/browse/subject' })"
8+
@click="backToGraph()"
99
>
1010
<v-icon :class="`text-${color}`"> fas fa-arrow-left</v-icon>
1111
<span :class="`text-${color} ml-3`"> GO BACK TO GRAPH </span>
@@ -157,6 +157,7 @@ export default {
157157
components: { Icon, StatusPills },
158158
mixins: [stringUtils],
159159
props: { selectedOntology: { required: true, type: String } },
160+
emits: ["clearSelection"],
160161
data() {
161162
return {
162163
headers: [
@@ -215,7 +216,16 @@ export default {
215216
"getCurrentPage",
216217
"getAncestors",
217218
]),
218-
...mapActions("ontologyBrowser", ["fetchNewPage", "changePerPage"]),
219+
...mapActions("ontologyBrowser", [
220+
"fetchNewPage",
221+
"changePerPage",
222+
"openTerms",
223+
]),
224+
backToGraph() {
225+
this.$emit("clearSelection", true);
226+
this.openTerms([]);
227+
this.$router.push({ path: "/browse/subject" });
228+
},
219229
},
220230
};
221231
</script>

src/store/ontologyBrowser.js

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,7 @@ export const actions = {
6262
if (!term || state.activeTerms.includes(term.identifier)) {
6363
commit("resetActiveTerms");
6464
commit("resetSelectedTerm");
65-
}
66-
else {
65+
} else {
6766
await dispatch("fetchRecords", term.name);
6867
commit("setActiveTerms", [term.identifier]);
6968
commit("setSelectedTerm", term);
@@ -153,21 +152,21 @@ export const mutations = {
153152
export const getters = {
154153
getAncestors:
155154
(state) =>
156-
(value, field = "id") => {
157-
let ancestors = [];
158-
state.flattenedTree
159-
.filter((obj) => obj["name"] === value["name"])
160-
.forEach((node) => {
161-
node.ancestors.forEach((ancestorID) => {
162-
state.flattenedTree
163-
.filter((obj) => obj.identifier === ancestorID)
164-
.forEach((ancestor) => {
165-
ancestors.push(ancestor[field]);
166-
});
167-
});
155+
(value, field = "id") => {
156+
let ancestors = [];
157+
state.flattenedTree
158+
.filter((obj) => obj["name"] === value["name"])
159+
.forEach((node) => {
160+
node.ancestors.forEach((ancestorID) => {
161+
state.flattenedTree
162+
.filter((obj) => obj.identifier === ancestorID)
163+
.forEach((ancestor) => {
164+
ancestors.push(ancestor[field]);
165+
});
168166
});
169-
return [...new Set(ancestors)];
170-
},
167+
});
168+
return [...new Set(ancestors)];
169+
},
171170
getCurrentPage: (state) => {
172171
return state.pagination.page;
173172
},

src/views/Browsers/OntologyBrowser.vue

Lines changed: 65 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -100,10 +100,11 @@
100100
<TermDetails
101101
v-if="records && selectedTerm"
102102
:selected-ontology="selectedOntology"
103+
@clear-selection="noSelection"
103104
/>
104105
<v-card v-else class="pa-0" flat>
105106
<v-card-text class="pa-0">
106-
<OntologySunburst />
107+
<OntologySunburst @subject-node="subjectNode" />
107108
</v-card-text>
108109
</v-card>
109110
</div>
@@ -153,6 +154,7 @@ export default {
153154
"pagination",
154155
"selectedTerm",
155156
]),
157+
156158
selectedOntology() {
157159
return this.$route.params.id;
158160
},
@@ -270,8 +272,7 @@ export default {
270272
});
271273
272274
this.openTerms(Array.from(parentsToOpen));
273-
}
274-
else {
275+
} else {
275276
await this.activateTerms();
276277
}
277278
},
@@ -284,7 +285,6 @@ export default {
284285
this.openTerms([]);
285286
return;
286287
}
287-
288288
// CASE 2: Active Search
289289
const targetId = newTerm.identifier || newTerm;
290290
const strTargetId = String(targetId);
@@ -299,7 +299,14 @@ export default {
299299
path.forEach((id) => {
300300
// Add ID to open set ONLY if it is NOT the target itself
301301
// (We want the parents open, but the target closed)
302-
if (String(id) !== strTargetId) {
302+
if (
303+
String(id) !== strTargetId &&
304+
!Object.keys(newTerm).includes("isSunburst")
305+
) {
306+
allIdsToOpen.add(String(id));
307+
}
308+
//Target is open because the selection is from Sunburst
309+
else if (newTerm["isSunburst"]) {
303310
allIdsToOpen.add(String(id));
304311
}
305312
});
@@ -320,8 +327,7 @@ export default {
320327
if (index !== -1) {
321328
if (this.$refs.virtualScroll.scrollToIndex) {
322329
this.$refs.virtualScroll.scrollToIndex(index);
323-
}
324-
else {
330+
} else {
325331
// Fallback: 50px is the estimated item height
326332
this.$refs.virtualScroll.$el.scrollTop = index * 50;
327333
}
@@ -344,11 +350,15 @@ export default {
344350
"openTerms",
345351
"leavePage",
346352
]),
353+
347354
/**
348-
* Give the result of the term selected
349-
* @param term
355+
* Searches for a term and updates the route based on its presence in the active terms list.
356+
*
357+
* @param {Object} term - The term object to be searched.
358+
* @param {string} term.identifier - The unique identifier of the term.
359+
* @param {string} term.name - The name of the term to encode and include in the query parameters if not already active.
360+
* @return {void} This method does not return a value.
350361
*/
351-
352362
searchTerm(term) {
353363
this.resetPagination();
354364
if (this.activeTerms.includes(term.identifier))
@@ -386,6 +396,15 @@ export default {
386396
return results;
387397
},
388398
399+
/**
400+
* Toggles the open or closed state of a node in the hierarchy.
401+
* Updates the list of currently opened nodes based on the given item's identifier.
402+
*
403+
* @param {Object} item - The node object to be toggled.
404+
* @param {boolean} item.hasChildren - Indicates if the node has child items.
405+
* @param {number|string} item.identifier - The unique identifier of the node.
406+
* @return {void} This method does not return a value.
407+
*/
389408
toggleNode(item) {
390409
if (!item.hasChildren) return;
391410
@@ -398,16 +417,22 @@ export default {
398417
if (isOpen) {
399418
// Close: Remove strict match
400419
newOpened = newOpened.filter((id) => String(id) !== strId);
401-
}
402-
else {
420+
} else {
403421
// Open: Add original ID
404422
newOpened.push(item.identifier);
405423
}
406-
424+
newOpened = newOpened.map((item) =>
425+
typeof item === "string" ? Number(item) : item,
426+
);
407427
this.openTerms(newOpened);
408428
},
409429
410-
// Prune logic that supports multiple matches
430+
/**
431+
* Prune logic that supports multiple matches
432+
* @param nodes
433+
* @param targetId
434+
* @return {*[]}
435+
*/
411436
pruneTreeWithChildren(nodes, targetId) {
412437
const filtered = [];
413438
const strTarget = String(targetId);
@@ -444,12 +469,35 @@ export default {
444469
445470
/**
446471
* SAFE CHECK: Checks if a node is open regardless of ID type (String/Number)
472+
* @param identifier
473+
* @return {*|boolean}
447474
*/
448475
isOpen(identifier) {
449476
if (!this.openedTerms) return false;
450477
// Convert everything to String for comparison
451478
return this.openedTerms.map(String).includes(String(identifier));
452479
},
480+
481+
/**
482+
* Updates the search property with the first element of the provided value array
483+
* and calls the toggleNode method with the same element.
484+
* @param {Array} value - An array where the first element is used to update the search property and passed to the toggleNode method.
485+
* @return {void}
486+
*/
487+
subjectNode(value) {
488+
if (value && value.length) {
489+
this.search = value[0];
490+
this.toggleNode(value[0]);
491+
} else {
492+
this.search = null;
493+
}
494+
},
495+
496+
noSelection(value) {
497+
if (value) {
498+
this.search = null;
499+
}
500+
},
453501
},
454502
};
455503
</script>
@@ -458,6 +506,7 @@ export default {
458506
.subject_color--border {
459507
border: 1px solid #e67e22 !important;
460508
}
509+
461510
.domain_color--border {
462511
border: 1px solid #712727 !important;
463512
}
@@ -493,9 +542,11 @@ export default {
493542
.col {
494543
flex-basis: initial !important;
495544
}
545+
496546
.cursor-pointer {
497547
cursor: pointer !important;
498548
}
549+
499550
.hover-bg:hover {
500551
background-color: rgba(0, 0, 0, 0.04);
501552
}

0 commit comments

Comments
 (0)