-
Notifications
You must be signed in to change notification settings - Fork 3
/
filter.ts
151 lines (139 loc) · 5.96 KB
/
filter.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
/**
Filters let the user toggle groups of graph elements, for example all nodes from the meta subontology.
Filters use the Cytoscape.js "display" attribute, while star operations (see graph.js) and reset style use the visibility attribute.
This ensures that filters and star operations interact properly, for example that resetting the style does not show filtered nodes.
See http://js.cytoscape.org/#style/visibility.
*/
import { NODE } from "../node";
import { checkboxKeydownListener, checkboxClickableDiv } from "./util";
import { View } from "./view";
import log from "loglevel";
const filterData = [
[`node[${NODE.SOURCE}='meta']`, `meta`, "meta"],
[`node[${NODE.SOURCE}='bb']`, `BB`, "bb"],
[`node[${NODE.SOURCE}='ob']`, `OB`, "ob"],
[`node[${NODE.SOURCE}='ciox']`, `CioX`, "ciox"],
[`node[${NODE.SOURCE}='he']`, `HE`, "he"],
[`node[${NODE.SOURCE}='it']`, `IT`, "it"],
[`node[${NODE.SOURCE}='it4it']`, `IT4IT`, "it4it"],
[`node[${NODE.SUBTOP}='${NODE.SUBTOP_ROLE}']`, `Role`, "role"],
[`node[${NODE.SUBTOP}='${NODE.SUBTOP_FUNCTION}']`, `Function`, "function"],
[`node[${NODE.SUBTOP}='${NODE.SUBTOP_ENTITY_TYPE}']`, `EntityType`, "entitytype"],
[`node[?${NODE.INSTANCE}]`, `Show Instances`, "show-instances"],
[`node[!${NODE.INSTANCE}]`, `Show Non-Instances`],
[`edge[p='http://www.w3.org/2000/01/rdf-schema#subClassOf']`, `subClassOf`, "subclassof"],
[`edge[p!='http://www.w3.org/2000/01/rdf-schema#subClassOf']`, `non-subClassOf`, "non-subclassof"],
[`edge[p^='http://www.w3.org/2004/02/skos/core#']`, `inter-ontology-relations`, "inter-ontology-relations"],
[`edge[p!^='http://www.w3.org/2004/02/skos/core#']`, `non-inter-ontology-relations`, "non-inter-ontology-relations"],
];
const filters: Array<Filter> = [];
const GRAPH_GETS_ADDITIONS = true;
// apply a function to all cytoscape cores in all tabs
const multicy = (f: (cy: cytoscape.Core) => any) =>
View.views()
.map((v: View) => v.state.cy)
.forEach((cy) => f(cy));
/**
Toggles the visibility of a set of nodes defined by a selector.
*/
export class Filter {
readonly selector: string;
readonly label: string;
readonly checkbox: HTMLInputElement;
readonly a: HTMLAnchorElement;
readonly cssClass: string;
visible: boolean;
/**
Creates filter with HTML elements, filter functionality and listeners.
@param selector - a Cytoscape.js selector, see {@link http://js.cytoscape.org/#selectors}
@param label - the menu entry label
@param i18n - internationalization key
*/
constructor(selector: string, label: string, i18n: string) {
this.selector = selector;
//let input = document.createRange().createContextualFragment('<input type="checkbox" class="filterbox" autocomplete="off" checked="true">'); // can't attach events to fragments
const input = document.createElement("input");
input.type = "checkbox";
this.checkbox = input;
input.classList.add("filterbox");
input.autocomplete = "off";
input.checked = true;
this.label = label;
this.a = document.createElement("a");
this.a.classList.add("dropdown-entry");
this.a.appendChild(input);
this.a.setAttribute("tabindex", "-1");
this.a.addEventListener("keydown", checkboxKeydownListener(input));
this.a.appendChild(checkboxClickableDiv(input, label, i18n));
// each filter has its own associated CSS class, such as "filter-BB"
this.cssClass = `filter-${label}`;
this.visible = true;
// Does not apply to elements that get added later, so only use if you don't add elements to the graph. Alternative if you want to use this update this after adding something.
// Assigns the CSS class of the filter to the nodes that match the filter selector.
multicy((cy) => cy.elements(this.selector).addClass(this.cssClass));
input.addEventListener("input", () => this.setVisible(input.checked));
filters.push(this);
}
/** label
* @returns the label*/
toString(): string {
return this.label;
}
/**
Set the visibility of the nodes selected by the filter.
visible whether the nodes should be visible */
setVisible(visible: boolean): void {
if (this.visible === visible) {
return;
}
this.visible = visible;
const hiddenSelectors = filters.filter((f) => !f.visible).map((f) => (GRAPH_GETS_ADDITIONS ? f.selector : "." + f.cssClass)); // class selector may be faster
if (hiddenSelectors.length === 0) {
multicy((cy) => cy.elements().removeClass("filtered"));
// cytoscape.js does not have a class negation selector so we need to add a negation class ourselves
// see https://stackoverflow.com/questions/54108410/how-to-negate-class-selector-in-cytoscape-js
multicy((cy) => cy.elements().addClass("unfiltered"));
log.debug("All filters checked");
} else {
// "or" all selectors together to obtain a combined one
const hiddenSelector = hiddenSelectors.reduce((a, b) => a + "," + b);
multicy((cy) => {
const filtered = cy.elements(hiddenSelector);
filtered.addClass("filtered");
filtered.removeClass("unfiltered");
const unfiltered = cy.elements().not(filtered);
unfiltered.removeClass("filtered");
unfiltered.addClass("unfiltered");
});
log.debug("filter " + hiddenSelector + " triggered");
}
}
/**
Add filter entries to the filter menu.
@param parent - the parent element to attach the entries to
@param as - an empty array of HTML anchors to be filled */
static addFilterEntries(parent: HTMLElement, as: Array<HTMLAnchorElement>): void {
for (const datum of filterData) {
const filter = new Filter(datum[0], datum[1], datum[2]);
parent.appendChild(filter.a);
as.push(filter.a);
}
}
/** Saves the visibility values of all filters.
@returns JSON representation of all filters */
static toJSON(): object {
const json = {};
for (const filter of filters) {
json[filter.label] = filter.visible;
}
return json;
}
/** Loads the visibility values and applies it to all filters.
@param json - JSON representation of all filters */
static fromJSON(json: object): void {
for (const filter of filters) {
filter.checkbox.checked = json[filter.label];
filter.visible = json[filter.label];
}
}
}