-
Notifications
You must be signed in to change notification settings - Fork 3
/
classuse.ts
168 lines (147 loc) · 5.04 KB
/
classuse.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
/** Show the environment of a single node using a special layout.*/
import * as sparql from "../sparql";
import { NODE } from "../node";
import { Graph } from "./graph";
import { View } from "./view";
import { short } from "../rdf";
import log from "loglevel";
let count = 0;
/** Centers a class and shows directly and indirectly connected roles, functions and entity types in a concentric layout.
Hides all other nodes. Resetting the view unhides the other nodes but keeps the layout of those shown before.
Recalculate the layout to place those nodes in relation to the whole graph again.
@param clazz - The URI of the class.
@param subTop - The sub top letter of the class (R,F or E) */
export async function classUse(clazz: string, subTop: string): Promise<void> {
// preparation and check
const types = {
R: ["meta:Role", "meta:Function", "meta:EntityType"],
F: ["meta:Function", "meta:Role", "meta:EntityType"],
E: ["meta:EntityType", "meta:Function", "meta:Role"],
};
if (!(subTop in types)) {
log.error("Unknown subtop '" + subTop + "'. Cannot display class use.");
return;
}
const [innerType, middleType, outerType] = types[subTop];
const query =
`select distinct ?inner ?middle ?outer ?outerx
{
<${clazz}> (rdfs:subClassOf|skos:closeMatch|^skos:closeMatch)* ?inner.
?inner rdf:type ${innerType}.
OPTIONAL
{
?inner ?p ?middle.
?middle rdf:type ${middleType}.` +
// ?role ?p ?f.
// ?f rdf:type meta:Function.
// ?f (skos:closeMatch|^skos:closeMatch|^rdfs:subClassOf)* ?function.
`
OPTIONAL
{` +
// #?function ?q ?et
// #?et rdf:type meta:EntityType.
`
?middle ?q ?outer.
?outer rdf:type ${outerType}.
OPTIONAL {?outer (skos:closeMatch|^skos:closeMatch|^rdfs:subClassOf)+ ?outerx.}
}
}
}`;
interface ClassUseBinding {
inner: { value: string };
middle: { value: string };
outer: { value: string };
outerx: { value: string };
}
const bindings = (await sparql.select(query)) as Array<ClassUseBinding>;
const [inner, middle, outer, outerx] = [...new Array(4)].map(() => new Set());
for (let i = 0; i < bindings.length; i++) {
inner.add(bindings[i].inner.value);
if (bindings[i].middle) {
middle.add(bindings[i].middle.value);
}
if (bindings[i].outer) {
outer.add(bindings[i].outer.value);
}
if (bindings[i].outerx) {
outerx.add(bindings[i].outerx.value);
}
}
if (middle.size === 0) {
log.warn("Class " + clazz + " is not used.");
return;
}
// check passed ***************************************************************************************+
// Class Use does not work with Combine Matches enabled, disable it temporarily.
// Enable again after finishing Class Use.
// See https://github.com/IMISE/snik-cytoscape.js/issues/341
const box = document.getElementById("combineMatchModeBox") as HTMLInputElement;
const combineMatch = box.checked;
if (combineMatch) {
View.mainView.state.graph.combineMatch(false);
}
// Create new tab. See https://github.com/IMISE/snik-cytoscape.js/issues/341
const view = new View(true, "Class Use " + ++count + " " + short(clazz));
await view.initialized;
const graph = view.state.graph;
// show it ****************************************************************
graph.cy.startBatch();
graph.resetStyle();
Graph.setVisible(graph.cy.elements(), false);
graph.starMode = true;
const classes = new Set([...inner, ...middle, ...outer, ...outerx]);
const selectedNodes = graph.cy.collection(`node[id='${clazz}']`);
for (const c of classes) {
const cNodes = graph.cy.nodes(`node[id='${c}']`);
selectedNodes.merge(cNodes);
//selectedEdges = selectedEdges.union(cNodes.connectedEdges());
}
const selectedElements = selectedNodes.merge(selectedNodes.edgesWith(selectedNodes));
selectedNodes
.layout({
name: "concentric",
fit: true,
levelWidth: function () {
return 1;
},
minNodeSpacing: 20,
concentric: function (node: cytoscape.NodeSingular & { degree(): number }) {
const uri = node.data(NODE.ID);
if (uri === clazz) {
return 10;
}
if (inner.has(uri)) {
return 9;
}
if (middle.has(uri)) {
return 8;
}
if (outer.has(uri)) {
return 7;
}
if (outerx.has(uri)) {
return 6;
}
return 10; // temporary workaround for inner without subtop
/*
// faster but can't discern expanded entity types from directly connected ones
switch(node.data(NODE.SUBTOP))
{
case "EntityType": return 1;
case "Function": return 2;
case "Role": return 3;
default: return 3; // temporary workaround for inner without subtop
}
*/
},
})
.run();
Graph.setVisible(selectedElements, true);
if (combineMatch) {
[View.mainView, view].forEach((v) => v.state.graph.combineMatch(true));
} // enable combine match for the main view again and for the new view
const centerNode = graph.cy.nodes(`node[id='${clazz}']`);
graph.cy.center(centerNode);
graph.cy.fit(selectedNodes);
graph.cy.endBatch();
}