Skip to content

Commit bcc869b

Browse files
committed
feat: :spakles: metakg preview on registry items
1 parent 171907c commit bcc869b

File tree

7 files changed

+437
-4
lines changed

7 files changed

+437
-4
lines changed

web-app/src/assets/app.css

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,18 @@ code {
115115
0 0 0 6px rgba(34, 237, 176, 0.2);
116116
}
117117

118+
.metakg-cy {
119+
width: 300px;
120+
height: 300px;
121+
display: block;
122+
overflow: hidden;
123+
margin: auto;
124+
}
125+
126+
.metakg-card {
127+
border-top: rgb(255, 115, 0) 5px solid !important;
128+
}
129+
118130
.edge-table tr td {
119131
padding: 2px;
120132
}
@@ -182,6 +194,10 @@ code {
182194
align-items: center;
183195
}
184196

197+
.p-0 {
198+
padding: 0 !important;
199+
}
200+
185201
.p-1 {
186202
padding: 1em !important;
187203
}
@@ -492,6 +508,32 @@ a.middle-indicator-text {
492508
transition: 0.7s;
493509
}
494510

511+
.clearButtonSmall {
512+
background-color: rgba(255, 255, 255, 0.1);
513+
padding: 3px 5px;
514+
border: solid rgb(250, 109, 0) 2px;
515+
border-radius: 10px;
516+
text-decoration: none;
517+
color: rgb(250, 109, 0);
518+
font-size: 0.9em;
519+
font-weight: light;
520+
font-variant: small-caps;
521+
}
522+
523+
.clearButtonSmall:hover {
524+
background-color: rgba(33, 163, 255, 0.3);
525+
border: solid #7eccf0 2px;
526+
color: rgb(50, 50, 148);
527+
transition: 0.3s;
528+
}
529+
530+
.clearButtonSmall:focus {
531+
background-color: rgba(33, 163, 255, 0.3);
532+
border: solid #7eccf0 2px;
533+
color: rgb(39, 39, 39);
534+
transition: 0.3s;
535+
}
536+
495537
@media only screen and (max-width: 500px) {
496538
.hideOnSmall {
497539
display: none;
@@ -1072,6 +1114,64 @@ body {
10721114
opacity 300ms;
10731115
}
10741116

1117+
.align-items-start {
1118+
align-items: start;
1119+
}
1120+
1121+
.entityPill {
1122+
outline: none !important;
1123+
-moz-user-select: none;
1124+
-khtml-user-select: none;
1125+
-webkit-user-select: none;
1126+
-ms-user-select: none;
1127+
user-select: none;
1128+
display: flex;
1129+
align-items: stretch;
1130+
margin: 5px;
1131+
/* Permalink - use to edit and share this gradient: https://colorzilla.com/gradient-editor/#eeeeee+0,eeeeee+100;Grey+Flat */
1132+
background: linear-gradient(
1133+
to bottom,
1134+
#eeeeee 0%,
1135+
#eeeeee 100%
1136+
); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */
1137+
border-radius: 10px;
1138+
}
1139+
1140+
.entityPill div:first-child {
1141+
color: white;
1142+
text-shadow: 1px 1px 1px rgb(104, 104, 104);
1143+
font-weight: bold;
1144+
font-size: 13px;
1145+
display: flex;
1146+
align-items: center;
1147+
padding: 5px;
1148+
border-top-left-radius: 10px;
1149+
border-bottom-left-radius: 10px;
1150+
}
1151+
1152+
.entityPill div:nth-child(2) {
1153+
width: 20px;
1154+
background-color: rgb(208, 208, 208);
1155+
clip-path: polygon(0% 0%, 18% 0, 100% 50%, 17% 100%, 0% 100%);
1156+
}
1157+
1158+
.entityPill div:nth-child(3) {
1159+
font-weight: bold;
1160+
font-size: 10px;
1161+
/* Permalink - use to edit and share this gradient: https://colorzilla.com/gradient-editor/#eeeeee+0,eeeeee+100;Grey+Flat */
1162+
background: linear-gradient(
1163+
to bottom,
1164+
#eeeeee 0%,
1165+
#eeeeee 100%
1166+
); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */
1167+
1168+
display: flex;
1169+
align-items: center;
1170+
border-top-right-radius: 10px;
1171+
border-bottom-right-radius: 10px;
1172+
padding: 5px;
1173+
}
1174+
10751175
.apiStatus {
10761176
outline: none !important;
10771177
-moz-user-select: none;

web-app/src/components/EntityPill.vue

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<template>
2+
<div class="entityPill" :class="'ep' + badgeID">
3+
<div :style="{ 'background-color': color }">
4+
{{ object }}
5+
</div>
6+
<div></div>
7+
<div class="yellow">
8+
<ul>
9+
<li v-for="item in subjects" :key="item">{{ item }}</li>
10+
</ul>
11+
</div>
12+
</div>
13+
</template>
14+
15+
<script>
16+
export default {
17+
name: 'EntityPill',
18+
data: function () {
19+
return {
20+
badgeID: Math.floor(Math.random() * 90000) + 10000
21+
};
22+
},
23+
props: ['object', 'subjects'],
24+
computed: {
25+
color: function () {
26+
return this.$store.getters.getEntityColor(this.object);
27+
}
28+
}
29+
};
30+
</script>

web-app/src/components/RegistryItem.vue

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<template>
22
<div class="card">
3-
<div class="card-content">
3+
<div class="card-content" style="padding-bottom: 0px">
44
<span class="card-title">
55
<span class="blue-grey-text bold" v-text="api.info.title"></span>&nbsp;
66
<small class="grey-text lighter" v-text="api.info.version"> </small>
@@ -68,6 +68,8 @@
6868
</template>
6969
</div>
7070
</div>
71+
<!-- METAKG -->
72+
<RegistryMetaKG :api="api"></RegistryMetaKG>
7173
<!-- TOGGLE DETAILS -->
7274
<div class="card-action grey lighten-3" v-if="total > 1" :class="showDetails ? 'blue' : 'grey'">
7375
<button
@@ -277,6 +279,7 @@
277279
<script>
278280
import SourceStatus from '../components/SourceStatus.vue';
279281
import UptimeStatus from '../components/UptimeStatus.vue';
282+
import RegistryMetaKG from '../components/RegistryMetaKG.vue';
280283
import CollapsibleText from '../components/CollapsibleText.vue';
281284
import { truncate } from 'lodash';
282285
import tippy from 'tippy.js';
@@ -289,7 +292,8 @@ export default {
289292
components: {
290293
SourceStatus,
291294
UptimeStatus,
292-
CollapsibleText
295+
CollapsibleText,
296+
RegistryMetaKG
293297
},
294298
data: function () {
295299
return {
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
<template>
2+
<div class="p-1">
3+
<p class="orange-text m-0" style="padding-bottom: 10px">
4+
View biomedical entity knowledge available
5+
<button @click="open = !open" type="button" class="clearButtonSmall">
6+
{{ open ? 'CLOSE' : 'OPEN' }}
7+
</button>
8+
</p>
9+
<div v-if="open" class="grey lighten-2 metakg-card p-0 row d-flex align-items-stretch">
10+
<h3 class="p-2 grey-text" v-if="loading">Loading...</h3>
11+
<template v-if="!loading && !noHits">
12+
<div class="col s12 m8">
13+
<h5 style="font-weight: lighter">Entity Relationship Overview</h5>
14+
<div v-if="graphData" style="max-height: 500px; overflow-y: scroll">
15+
<div class="d-flex flex-wrap align-items-start">
16+
<template v-for="(subjects, object) in graphData" :key="object">
17+
<EntityPill :object="object" :subjects="subjects"></EntityPill>
18+
</template>
19+
</div>
20+
</div>
21+
</div>
22+
<div class="col s12 m4 grey darken-3">
23+
<div class="d-flex justify-content-center">
24+
<img class="scale-in-center" src="@/assets/img/metakg-01.png" width="80" />
25+
<h5 class="white-text center" style="font-weight: lighter">MetaKG Explorer</h5>
26+
</div>
27+
<template v-if="networkData">
28+
<SimpleNetwork :nodes="networkData.nodes" :edges="networkData.edges"></SimpleNetwork>
29+
</template>
30+
<p class="center">
31+
<span class="white-text caps"> Explore {{ api.info.title }}'s MetaKG </span>
32+
</p>
33+
<div class="d-flex justify-content-center align-items-center p-1">
34+
<router-link
35+
class="btn btn-large purple white-text"
36+
target="_blank"
37+
:to="{
38+
path: '/portal/translator/metakg',
39+
query: {
40+
'api.x-translator.component': api?.info?.['x-translator']?.component,
41+
'api.name': api.info.title
42+
}
43+
}"
44+
>Try It Now</router-link
45+
>
46+
</div>
47+
</div>
48+
</template>
49+
<div v-if="!loading && noHits">
50+
<p class="center grey-text p-2">Oops...Can't load this right now...</p>
51+
</div>
52+
</div>
53+
</div>
54+
</template>
55+
56+
<script>
57+
import axios from 'axios';
58+
59+
import EntityPill from './EntityPill.vue';
60+
import SimpleNetwork from './SimpleNetwork.vue';
61+
62+
export default {
63+
name: 'RegistryMetaKG',
64+
components: {
65+
EntityPill,
66+
SimpleNetwork
67+
},
68+
props: {
69+
api: {
70+
type: Object,
71+
default: {}
72+
}
73+
},
74+
data: function () {
75+
return {
76+
open: false,
77+
loading: true,
78+
graphData: null,
79+
networkData: null,
80+
noHits: false
81+
};
82+
},
83+
watch: {
84+
open: function (v) {
85+
if (v) {
86+
if (!this.graphData) {
87+
this.sendRequest();
88+
}
89+
}
90+
}
91+
},
92+
methods: {
93+
getNetworkData(hits) {
94+
let self = this;
95+
let nodes = new Set();
96+
let edges = [];
97+
let nodeData = [];
98+
let nodeWeight = {};
99+
100+
hits.forEach((hit) => {
101+
if (hit.object in nodeWeight) {
102+
nodeWeight[hit.object] += 1;
103+
} else {
104+
nodeWeight[hit.object] = 1;
105+
}
106+
if (hit.subject in nodeWeight) {
107+
nodeWeight[hit.subject] += 1;
108+
} else {
109+
nodeWeight[hit.subject] = 1;
110+
}
111+
nodes.add(hit.object);
112+
nodes.add(hit.subject);
113+
edges.push({
114+
group: 'edges',
115+
data: {
116+
id: Math.floor(100000 + Math.random() * 900000),
117+
source: hit.subject,
118+
target: hit.object
119+
}
120+
});
121+
});
122+
123+
nodeData = [...nodes].map((node) => {
124+
return {
125+
group: 'nodes',
126+
data: {
127+
weight: nodeWeight[node] + 100,
128+
id: node,
129+
color: self.$store.getters.getEntityColor(node)
130+
}
131+
};
132+
});
133+
this.networkData = {
134+
nodes: nodeData,
135+
edges: edges
136+
};
137+
},
138+
sendRequest() {
139+
let self = this;
140+
let base = process.env.NODE_ENV == 'development' ? 'https://dev.smart-api.info' : '';
141+
axios
142+
.get(
143+
base +
144+
'/api/metakg?size=20&q=(api.name:"' +
145+
self.api.info.title +
146+
'")&size=300&fields=object,subject'
147+
)
148+
.then((res) => {
149+
let data = {};
150+
if (res.data?.hits && res.data?.hits?.length) {
151+
self.getNetworkData(res.data.hits);
152+
res.data.hits.forEach((item) => {
153+
if (!(item.subject in data)) {
154+
data[item.subject] = [item.object];
155+
} else {
156+
if (item.subject in data && !data[item.subject].includes(item.object)) {
157+
data[item.subject].push(item.object);
158+
}
159+
}
160+
});
161+
self.graphData = data;
162+
self.loading = false;
163+
self.noHits = false;
164+
} else {
165+
self.noHits = true;
166+
self.loading = false;
167+
}
168+
})
169+
.catch((err) => {
170+
self.loading = false;
171+
self.noHits = true;
172+
throw err;
173+
});
174+
}
175+
}
176+
};
177+
</script>

0 commit comments

Comments
 (0)