Skip to content

Commit 8c78e11

Browse files
committed
feat: ✨ metakg overview
1 parent bcc869b commit 8c78e11

File tree

5 files changed

+157
-18
lines changed

5 files changed

+157
-18
lines changed

web-app/src/assets/app.css

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -511,10 +511,10 @@ a.middle-indicator-text {
511511
.clearButtonSmall {
512512
background-color: rgba(255, 255, 255, 0.1);
513513
padding: 3px 5px;
514-
border: solid rgb(250, 109, 0) 2px;
514+
border: solid rgb(158, 0, 250) 2px;
515515
border-radius: 10px;
516516
text-decoration: none;
517-
color: rgb(250, 109, 0);
517+
color: rgb(187, 0, 250);
518518
font-size: 0.9em;
519519
font-weight: light;
520520
font-variant: small-caps;

web-app/src/components/EntityPill.vue

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,17 @@
66
<div></div>
77
<div class="yellow">
88
<ul>
9-
<li v-for="item in subjects" :key="item">{{ item }}</li>
9+
<li v-for="item in subjects_viewed" :key="item">{{ item }}</li>
10+
<li
11+
v-if="!viewingAll"
12+
@click="
13+
limit = subjects.length;
14+
viewingAll = true;
15+
"
16+
class="blue-text pointer"
17+
>
18+
<b>See All ({{ subjects.length }})</b>
19+
</li>
1020
</ul>
1121
</div>
1222
</div>
@@ -17,13 +27,28 @@ export default {
1727
name: 'EntityPill',
1828
data: function () {
1929
return {
20-
badgeID: Math.floor(Math.random() * 90000) + 10000
30+
badgeID: Math.floor(Math.random() * 90000) + 10000,
31+
limit: 7
2132
};
2233
},
2334
props: ['object', 'subjects'],
2435
computed: {
2536
color: function () {
2637
return this.$store.getters.getEntityColor(this.object);
38+
},
39+
subjects_viewed: function () {
40+
if (this.subjects.length < this.limit) {
41+
return this.subjects;
42+
} else {
43+
return this.subjects.slice(0, this.limit);
44+
}
45+
},
46+
viewingAll: function () {
47+
if (this.subjects.length <= this.limit) {
48+
return true;
49+
} else {
50+
return false;
51+
}
2752
}
2853
}
2954
};

web-app/src/components/RegistryItem.vue

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,20 @@
4747
</span>
4848
</template>
4949
</router-link>
50+
<router-link
51+
target="_blank"
52+
data-tippy-content="Explore This API's Knowledge Graph"
53+
:to="{
54+
path: '/portal/translator/metakg',
55+
query: {
56+
q: 'api.smartapi.id:' + api._id
57+
}
58+
}"
59+
class="versionBadge purple pointer"
60+
>
61+
<img class="scale-in-center" src="@/assets/img/metakg-01.png" width="10" /> MetaKG
62+
<i class="fa fa-external-link" aria-hidden="true"></i>
63+
</router-link>
5064
</div>
5165
<template v-if="api.info.description && api.info.description.length > 500">
5266
<CollapsibleText :text="api.info.description"></CollapsibleText>

web-app/src/components/RegistryMetaKG.vue

Lines changed: 58 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<template>
22
<div class="p-1">
3-
<p class="orange-text m-0" style="padding-bottom: 10px">
4-
View biomedical entity knowledge available
3+
<p class="purple-text m-0" style="padding-bottom: 10px">
4+
<img class="scale-in-center" src="@/assets/img/metakg-01.png" width="20" /> View MetaKG
55
<button @click="open = !open" type="button" class="clearButtonSmall">
66
{{ open ? 'CLOSE' : 'OPEN' }}
77
</button>
@@ -10,7 +10,17 @@
1010
<h3 class="p-2 grey-text" v-if="loading">Loading...</h3>
1111
<template v-if="!loading && !noHits">
1212
<div class="col s12 m8">
13-
<h5 style="font-weight: lighter">Entity Relationship Overview</h5>
13+
<h5 style="font-weight: lighter">
14+
MetaKG Entity Overview
15+
<small class="right"
16+
>Edges ({{ numberWithCommas(total) }}) | Objects ({{ objects.length }}) | Subjects ({{
17+
subjects.length
18+
}})</small
19+
>
20+
</h5>
21+
<p v-if="total && total > size" class="center yellow lighten-4 orange-text rounded">
22+
This is just a subset of the available MetaKG
23+
</p>
1424
<div v-if="graphData" style="max-height: 500px; overflow-y: scroll">
1525
<div class="d-flex flex-wrap align-items-start">
1626
<template v-for="(subjects, object) in graphData" :key="object">
@@ -19,16 +29,19 @@
1929
</div>
2030
</div>
2131
</div>
22-
<div class="col s12 m4 grey darken-3">
32+
<div class="col s12 m4 grey darken-4">
2333
<div class="d-flex justify-content-center">
2434
<img class="scale-in-center" src="@/assets/img/metakg-01.png" width="80" />
2535
<h5 class="white-text center" style="font-weight: lighter">MetaKG Explorer</h5>
2636
</div>
2737
<template v-if="networkData">
2838
<SimpleNetwork :nodes="networkData.nodes" :edges="networkData.edges"></SimpleNetwork>
2939
</template>
40+
<p v-if="total && total > size" class="center yellow lighten-2 black-text rounded">
41+
This is just a subset of the available MetaKG
42+
</p>
3043
<p class="center">
31-
<span class="white-text caps"> Explore {{ api.info.title }}'s MetaKG </span>
44+
<span class="white-text caps"> Explore the full {{ api.info.title }}'s MetaKG </span>
3245
</p>
3346
<div class="d-flex justify-content-center align-items-center p-1">
3447
<router-link
@@ -37,12 +50,11 @@
3750
:to="{
3851
path: '/portal/translator/metakg',
3952
query: {
40-
'api.x-translator.component': api?.info?.['x-translator']?.component,
41-
'api.name': api.info.title
53+
q: 'api.smartapi.id:' + api._id
4254
}
4355
}"
44-
>Try It Now</router-link
45-
>
56+
>Try It Now <i class="fa fa-external-link" aria-hidden="true"></i
57+
></router-link>
4658
</div>
4759
</div>
4860
</template>
@@ -77,7 +89,11 @@ export default {
7789
loading: true,
7890
graphData: null,
7991
networkData: null,
80-
noHits: false
92+
noHits: false,
93+
total: 0,
94+
size: 400,
95+
objects: [],
96+
subjects: []
8197
};
8298
},
8399
watch: {
@@ -90,6 +106,9 @@ export default {
90106
}
91107
},
92108
methods: {
109+
numberWithCommas(x) {
110+
return x.toString().replace(/\B(?<!\.\d*)(?=(\d{3})+(?!\d))/g, ',');
111+
},
93112
getNetworkData(hits) {
94113
let self = this;
95114
let nodes = new Set();
@@ -126,6 +145,7 @@ export default {
126145
data: {
127146
weight: nodeWeight[node] + 100,
128147
id: node,
148+
name: node[0],
129149
color: self.$store.getters.getEntityColor(node)
130150
}
131151
};
@@ -135,20 +155,32 @@ export default {
135155
edges: edges
136156
};
137157
},
158+
getFacetData(facets) {
159+
if (facets?.['object.raw']?.terms) {
160+
this.objects = facets?.['object.raw']?.terms.map((v) => v.term);
161+
}
162+
if (facets?.['subject.raw']?.terms) {
163+
this.subjects = facets?.['subject.raw']?.terms.map((v) => v.term);
164+
}
165+
},
138166
sendRequest() {
139167
let self = this;
140168
let base = process.env.NODE_ENV == 'development' ? 'https://dev.smart-api.info' : '';
141169
axios
142170
.get(
143171
base +
144-
'/api/metakg?size=20&q=(api.name:"' +
172+
'/api/metakg?q=(api.name:"' +
145173
self.api.info.title +
146-
'")&size=300&fields=object,subject'
174+
'")&size=' +
175+
self.size +
176+
'&fields=object,subject&facet_size=300&aggs=object.raw,subject.raw'
147177
)
148178
.then((res) => {
149179
let data = {};
150180
if (res.data?.hits && res.data?.hits?.length) {
181+
self.total = res.data?.total;
151182
self.getNetworkData(res.data.hits);
183+
self.getFacetData(res.data?.facets);
152184
res.data.hits.forEach((item) => {
153185
if (!(item.subject in data)) {
154186
data[item.subject] = [item.object];
@@ -158,6 +190,20 @@ export default {
158190
}
159191
}
160192
});
193+
let sortable = [];
194+
for (var key in data) {
195+
sortable.push([key, data[key]]);
196+
}
197+
sortable.sort(function (a, b) {
198+
return a[1].length - b[1].length;
199+
});
200+
sortable.reverse();
201+
let objSorted = {};
202+
sortable.forEach(function (item) {
203+
objSorted[item[0]] = item[1].sort();
204+
});
205+
data = objSorted;
206+
161207
self.graphData = data;
162208
self.loading = false;
163209
self.noHits = false;

web-app/src/components/SimpleNetwork.vue

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
<script>
66
import cytoscape from 'cytoscape';
7+
import tippy from 'tippy.js';
78
89
export default {
910
name: 'SimpleNetwork',
@@ -21,6 +22,7 @@ export default {
2122
},
2223
methods: {
2324
draw() {
25+
let self = this;
2426
this.cy = cytoscape({
2527
container: document.getElementById('sn' + this.badgeID),
2628
elements: [...this.edges, ...this.nodes],
@@ -29,6 +31,12 @@ export default {
2931
{
3032
selector: 'node',
3133
style: {
34+
content: 'data(name)',
35+
'text-wrap': 'wrap',
36+
'text-valign': 'center',
37+
'text-halign': 'center',
38+
'font-size': '2em',
39+
color: 'white',
3240
'background-color': 'data(color)',
3341
'z-index': 1000,
3442
width: 'data(weight)',
@@ -47,6 +55,54 @@ export default {
4755
}
4856
]
4957
});
58+
59+
function readableName(text) {
60+
const result = text.replace(/([A-Z])/g, '\n $1');
61+
return result.charAt(0).toUpperCase() + result.slice(1);
62+
}
63+
64+
function makePopper(ele) {
65+
let ref = ele.popperRef();
66+
ele.tippy = tippy(document.createElement('div'), {
67+
getReferenceClientRect: ref.getBoundingClientRect,
68+
placement: 'top',
69+
trigger: 'mouseenter', // mandatory
70+
arrow: true,
71+
interactive: true,
72+
allowHTML: true,
73+
theme: 'light',
74+
appendTo: document.body, // or append dummyDomEle to document.body
75+
onShow: function (instance) {
76+
instance.setContent(
77+
'<div class="white-text" style="padding:3px 5px;background:' +
78+
ele.data('color') +
79+
'">' +
80+
readableName(ele.id()) +
81+
'</div>'
82+
);
83+
},
84+
onUntrigger: function (instance) {
85+
instance.show();
86+
}
87+
});
88+
}
89+
90+
this.cy.ready(function () {
91+
self.cy.elements().forEach(function (ele) {
92+
if (ele.isNode()) {
93+
makePopper(ele);
94+
}
95+
});
96+
});
97+
98+
this.cy.userZoomingEnabled(false);
99+
100+
this.cy.elements().unbind('mouseover');
101+
this.cy.elements().bind('mouseover', (event) => event.target?.tippy?.show());
102+
103+
this.cy.elements().unbind('mouseout');
104+
this.cy.elements().bind('mouseout', (event) => event.target?.tippy?.hide());
105+
50106
this.cy
51107
.layout({
52108
name: 'concentric',
@@ -55,8 +111,6 @@ export default {
55111
minNodeSpacing: 100
56112
})
57113
.run();
58-
59-
this.cy.userZoomingEnabled(false);
60114
}
61115
},
62116
mounted: function () {

0 commit comments

Comments
 (0)