diff --git a/packages/sparql-metamap/index.html b/packages/sparql-metamap/index.html
index b15af14..177e2df 100644
--- a/packages/sparql-metamap/index.html
+++ b/packages/sparql-metamap/index.html
@@ -17,6 +17,7 @@
+
diff --git a/packages/sparql-metamap/src/sparql-metamap.ts b/packages/sparql-metamap/src/sparql-metamap.ts
index 4e41822..ff18407 100644
--- a/packages/sparql-metamap/src/sparql-metamap.ts
+++ b/packages/sparql-metamap/src/sparql-metamap.ts
@@ -20,7 +20,7 @@ PREFIX void:
PREFIX void-ext:
SELECT DISTINCT ?subjectClass ?prop ?objectClass ?objectDatatype ?triples
?objectClassTopParent ?objectClassTopParentLabel ?subjectClassTopParent ?subjectClassTopParentLabel
-?subjectClassLabel ?objectClassLabel ?subjectClassComment ?objectClassComment
+?subjectClassLabel ?objectClassLabel ?subjectClassComment ?objectClassComment ?propLabel ?propComment
WHERE {
{
SELECT * WHERE {
@@ -43,6 +43,8 @@ WHERE {
?pp void:property ?prop ;
void:triples ?triples .
+ OPTIONAL {?prop rdfs:label ?propLabel }
+ OPTIONAL {?prop rdfs:comment ?propComment }
OPTIONAL {
{
?pp void:classPartition [ void:class ?objectClass ] .
@@ -76,6 +78,7 @@ type Cluster = {
x?: number;
y?: number;
color?: string;
+ count: number;
positions: {x: number; y: number}[];
};
@@ -118,6 +121,7 @@ export class SparqlMetamap extends HTMLElement {
predicatesCount: {[key: string]: number} = {};
hidePredicates: Set = new Set();
+ hideClusters: Set = new Set();
clusters: {[key: string]: Cluster} = {};
@@ -165,6 +169,12 @@ export class SparqlMetamap extends HTMLElement {
height: 100%;
border: 1px solid lightgray;
}
+ #sparql-metamap hr {
+ width: 90%;
+ border: none;
+ height: 1px;
+ background: lightgrey;
+ }
.clusterLabel {
// position: absolute;
// transform: translate(-50%, -50%);
@@ -194,26 +204,35 @@ export class SparqlMetamap extends HTMLElement {
+
`;
this.appendChild(style);
this.appendChild(container);
// Add sidebar filtering buttons
- const showAllButton = this.querySelector("#metamap-show-all") as HTMLButtonElement;
- showAllButton.addEventListener("click", () => {
+ const showAllPredsButton = this.querySelector("#metamap-show-preds") as HTMLButtonElement;
+ showAllPredsButton.addEventListener("click", () => {
const checkboxes = this.querySelectorAll("#metamap-predicates-list input[type='checkbox']");
checkboxes.forEach(checkbox => {
(checkbox as HTMLInputElement).checked = true;
@@ -221,8 +240,8 @@ export class SparqlMetamap extends HTMLElement {
this.hidePredicates.clear();
this.renderer?.refresh({skipIndexation: true});
});
- const hideAllButton = this.querySelector("#metamap-hide-all") as HTMLButtonElement;
- hideAllButton.addEventListener("click", () => {
+ const hideAllPredsButton = this.querySelector("#metamap-hide-preds") as HTMLButtonElement;
+ hideAllPredsButton.addEventListener("click", () => {
const checkboxes = this.querySelectorAll("#metamap-predicates-list input[type='checkbox']");
checkboxes.forEach(checkbox => {
(checkbox as HTMLInputElement).checked = false;
@@ -238,6 +257,26 @@ export class SparqlMetamap extends HTMLElement {
await this.initGraph();
});
+ // Filtering buttons for clusters
+ const showAllClustersButton = this.querySelector("#metamap-show-clusters") as HTMLButtonElement;
+ showAllClustersButton.addEventListener("click", () => {
+ const checkboxes = this.querySelectorAll("#metamap-clusters-list input[type='checkbox']");
+ checkboxes.forEach(checkbox => {
+ (checkbox as HTMLInputElement).checked = true;
+ });
+ this.hideClusters.clear();
+ this.renderer?.refresh({skipIndexation: true});
+ });
+ const hideAllClustersButton = this.querySelector("#metamap-hide-clusters") as HTMLButtonElement;
+ hideAllClustersButton.addEventListener("click", () => {
+ const checkboxes = this.querySelectorAll("#metamap-clusters-list input[type='checkbox']");
+ checkboxes.forEach(checkbox => {
+ (checkbox as HTMLInputElement).checked = false;
+ });
+ this.hideClusters = new Set(Object.keys(this.clusters));
+ this.renderer?.refresh({skipIndexation: true});
+ });
+
// const palette = iwanthue(Object.keys(countryClusters).length, { seed: "eurSISCountryClusters" });
this.graph = new Graph({multi: true});
@@ -279,13 +318,16 @@ export class SparqlMetamap extends HTMLElement {
if (!this.graph.hasNode(subjUri)) {
this.graph.addNode(subjUri, {
label: subjCurie,
+ curie: subjCurie,
size: count,
cluster: subjCluster,
endpoint: endpoint,
datatypes: [],
});
- if (row.subjectClassLabel)
+ if (row.subjectClassLabel) {
this.graph.updateNodeAttribute(subjUri, "displayLabel", () => row.subjectClassLabel.value);
+ this.graph.updateNodeAttribute(subjUri, "label", () => row.subjectClassLabel.value);
+ }
if (row.subjectClassComment)
this.graph.updateNodeAttribute(subjUri, "comment", () => row.subjectClassComment.value);
}
@@ -322,32 +364,55 @@ export class SparqlMetamap extends HTMLElement {
const objCurie = this.getCurie(objUri);
this.graph.addNode(objUri, {
label: objCurie,
+ curie: objCurie,
size: count,
cluster: objCluster,
endpoint: endpoint,
datatypes: [],
});
- if (row.objectClassLabel)
+ if (row.objectClassLabel) {
this.graph.updateNodeAttribute(objUri, "displayLabel", () => row.objectClassLabel.value);
+ this.graph.updateNodeAttribute(objUri, "label", () => row.objectClassLabel.value);
+ }
if (row.objectClassComment)
this.graph.updateNodeAttribute(objUri, "comment", () => row.objectClassComment.value);
}
// Add edge
const predCurie = this.getCurie(row.prop.value);
- if (!this.predicatesCount[predCurie]) this.predicatesCount[predCurie] = 1;
- else this.predicatesCount[predCurie] += 1;
- this.graph.addEdge(subjUri, objUri, {
- label: predCurie,
- size: 2,
- // size: count,
- type: "arrow",
- });
+ let edgeExists = false;
+ for (const edge of this.graph.edges(subjUri, objUri)) {
+ if (this.graph.getEdgeAttributes(edge).curie === predCurie) {
+ edgeExists = true;
+ break;
+ }
+ }
+ if (!edgeExists) {
+ // Add the edge only if it doesn't already exist
+ const edgeAttrs: any = {
+ label: predCurie,
+ curie: predCurie,
+ size: 2,
+ // size: count,
+ type: "arrow",
+ };
+ if (row.propLabel) {
+ edgeAttrs.curie = edgeAttrs.label;
+ edgeAttrs.label = row.propLabel.value;
+ edgeAttrs.displayLabel = row.propLabel.value;
+ }
+ if (row.propComment) edgeAttrs["comment"] = row.propComment.value;
+ if (!this.predicatesCount[predCurie]) this.predicatesCount[predCurie] = 1;
+ else this.predicatesCount[predCurie] += 1;
+ this.graph.addEdge(subjUri, objUri, edgeAttrs);
+ }
}
}
}
+ // Create clusters
this.graph.forEachNode((_node, atts) => {
- if (!this.clusters[atts.cluster]) this.clusters[atts.cluster] = {label: atts.cluster, positions: []};
+ if (!this.clusters[atts.cluster]) this.clusters[atts.cluster] = {label: atts.cluster, positions: [], count: 1};
+ else this.clusters[atts.cluster].count += 1;
});
// create and assign one color by cluster
const palette = iwanthue(Object.keys(this.clusters).length, {seed: "topClassesClusters"});
@@ -405,12 +470,12 @@ export class SparqlMetamap extends HTMLElement {
if (typeof parallelMinIndex === "number") {
this.graph.mergeEdgeAttributes(edge, {
type: parallelIndex ? "curved" : "straight",
- curvature: getCurvature(parallelIndex, parallelMaxIndex),
+ curvature: getEdgeCurvature(parallelIndex, parallelMaxIndex),
});
} else if (typeof parallelIndex === "number") {
this.graph.mergeEdgeAttributes(edge, {
type: "curved",
- curvature: getCurvature(parallelIndex, parallelMaxIndex),
+ curvature: getEdgeCurvature(parallelIndex, parallelMaxIndex),
});
} else {
this.graph.setEdgeAttribute(edge, "type", "straight");
@@ -442,6 +507,7 @@ export class SparqlMetamap extends HTMLElement {
// barnesHutOptimize: true,
// gravity: 0.1,
linLogMode: true,
+ // slowDown: 4,
// adjustSizes: true,
// strongGravityMode: true,
},
@@ -488,6 +554,10 @@ export class SparqlMetamap extends HTMLElement {
res.label = "";
res.color = "#f6f6f6";
}
+ // Filter clusters
+ if (this.hideClusters.size > 0 && this.hideClusters.has(data.cluster)) {
+ res.hidden = true;
+ }
// If a node is selected, it is highlighted
if (this.selectedNode === node) {
res.highlighted = true;
@@ -530,13 +600,14 @@ export class SparqlMetamap extends HTMLElement {
) {
res.hidden = true;
}
- if (this.hidePredicates.size > 0 && this.hidePredicates.has(data.label)) {
+ if (this.hidePredicates.size > 0 && this.hidePredicates.has(data.curie)) {
res.hidden = true;
}
return res;
});
this.renderPredicateList();
+ this.renderClusterList();
// Feed the datalist autocomplete values:
const searchSuggestions = this.querySelector("#suggestions") as HTMLDataListElement;
@@ -546,41 +617,40 @@ export class SparqlMetamap extends HTMLElement {
.map(node => ``)
.join("\n");
- // Create the clustersLabel layer
- const clustersLayer = document.createElement("div");
- clustersLayer.id = "clustersLayer";
- clustersLayer.style.width = "100%";
- clustersLayer.style.height = "100%";
- clustersLayer.style.position = "absolute";
- let clusterLabelsDoms = "";
- for (const c in this.clusters) {
- // for each cluster create a div label
- const cluster = this.clusters[c];
- // adapt the position to viewport coordinates
- const viewportPos = this.renderer.graphToViewport(cluster as Coordinates);
- clusterLabelsDoms += `${cluster.label}
`;
- }
- clustersLayer.innerHTML = clusterLabelsDoms;
- // Insert the layer underneath the hovers layer
- container.insertBefore(clustersLayer, container.querySelector(".sigma-hovers"));
-
- // Clusters labels position needs to be updated on each render
- this.renderer.on("afterRender", () => {
- for (const c in this.clusters) {
- const cluster = this.clusters[c];
- const clusterLabel = document.getElementById(cluster.label);
- if (clusterLabel && this.renderer) {
- // update position from the viewport
- const viewportPos = this.renderer.graphToViewport(cluster as Coordinates);
- clusterLabel.style.top = `${viewportPos.y}px`;
- clusterLabel.style.left = `${viewportPos.x}px`;
- }
- }
- });
+ // // Create the clustersLabel layer
+ // const clustersLayer = document.createElement("div");
+ // clustersLayer.id = "clustersLayer";
+ // clustersLayer.style.width = "100%";
+ // clustersLayer.style.height = "100%";
+ // clustersLayer.style.position = "absolute";
+ // let clusterLabelsDoms = "";
+ // for (const c in this.clusters) {
+ // // for each cluster create a div label
+ // const cluster = this.clusters[c];
+ // // adapt the position to viewport coordinates
+ // const viewportPos = this.renderer.graphToViewport(cluster as Coordinates);
+ // clusterLabelsDoms += `${cluster.label}
`;
+ // }
+ // clustersLayer.innerHTML = clusterLabelsDoms;
+ // // Insert the layer underneath the hovers layer
+ // container.insertBefore(clustersLayer, container.querySelector(".sigma-hovers"));
+ // // Clusters labels position needs to be updated on each render
+ // this.renderer.on("afterRender", () => {
+ // for (const c in this.clusters) {
+ // const cluster = this.clusters[c];
+ // const clusterLabel = document.getElementById(cluster.label);
+ // if (clusterLabel && this.renderer) {
+ // // update position from the viewport
+ // const viewportPos = this.renderer.graphToViewport(cluster as Coordinates);
+ // clusterLabel.style.top = `${viewportPos.y}px`;
+ // clusterLabel.style.left = `${viewportPos.x}px`;
+ // }
+ // }
+ // });
setTimeout(() => {
layout.kill();
- }, 1500);
+ }, 3000);
// console.log(this.graph.getNodeAttributes("http://purl.uniprot.org/core/Protein"));
}
@@ -607,7 +677,8 @@ export class SparqlMetamap extends HTMLElement {
nodeInfoDiv.innerHTML = "";
if (this.selectedNode) {
const nodeAttrs = this.graph.getNodeAttributes(this.selectedNode);
- nodeInfoDiv.innerHTML = ``;
+ nodeInfoDiv.innerHTML = `
`;
+ nodeInfoDiv.innerHTML += ``;
if (nodeAttrs.displayLabel) nodeInfoDiv.innerHTML += `${nodeAttrs.displayLabel}
`;
if (nodeAttrs.comment) nodeInfoDiv.innerHTML += `${nodeAttrs.comment}
`;
if (nodeAttrs.cluster)
@@ -717,7 +788,6 @@ export class SparqlMetamap extends HTMLElement {
const sidebar = this.querySelector("#metamap-predicates-list") as HTMLElement;
sidebar.innerHTML = "";
const sortedPredicates = Object.entries(this.predicatesCount).sort((a, b) => b[1] - a[1]);
-
for (const [predicateLabel, predicateCount] of sortedPredicates) {
const checkbox = document.createElement("input");
checkbox.type = "checkbox";
@@ -728,11 +798,9 @@ export class SparqlMetamap extends HTMLElement {
const label = document.createElement("label");
label.htmlFor = predicateLabel;
label.textContent = `${predicateLabel} (${predicateCount.toLocaleString()})`;
-
const container = document.createElement("div");
container.appendChild(checkbox);
container.appendChild(label);
-
sidebar.appendChild(container);
}
}
@@ -742,11 +810,39 @@ export class SparqlMetamap extends HTMLElement {
else this.hidePredicates.delete(predicateLabel);
this.renderer?.refresh({skipIndexation: true});
}
+
+ renderClusterList() {
+ const sidebar = this.querySelector("#metamap-clusters-list") as HTMLElement;
+ sidebar.innerHTML = "";
+ const sortedClusters = Object.entries(this.clusters).sort((a, b) => b[1].count - a[1].count);
+ for (const [clusterLabel, clusterAttrs] of sortedClusters) {
+ const checkbox = document.createElement("input");
+ checkbox.type = "checkbox";
+ checkbox.id = clusterLabel;
+ checkbox.checked = true;
+ checkbox.onchange = () => this.toggleCluster(clusterLabel, checkbox.checked);
+
+ const label = document.createElement("label");
+ if (clusterAttrs.color) label.style.color = clusterAttrs.color;
+ label.htmlFor = clusterLabel;
+ label.textContent = `${clusterLabel} (${clusterAttrs.count})`;
+ const container = document.createElement("div");
+ container.appendChild(checkbox);
+ container.appendChild(label);
+ sidebar.appendChild(container);
+ }
+ }
+
+ toggleCluster(predicateLabel: string, checked: boolean) {
+ if (!checked) this.hideClusters.add(predicateLabel);
+ else this.hideClusters.delete(predicateLabel);
+ this.renderer?.refresh({skipIndexation: true});
+ }
}
-function getCurvature(index: number, maxIndex: number): number {
+function getEdgeCurvature(index: number, maxIndex: number): number {
if (maxIndex <= 0) throw new Error("Invalid maxIndex");
- if (index < 0) return -getCurvature(-index, maxIndex);
+ if (index < 0) return -getEdgeCurvature(-index, maxIndex);
const amplitude = 3.5;
const maxCurvature = amplitude * (1 - Math.exp(-maxIndex / amplitude)) * DEFAULT_EDGE_CURVATURE;
return (maxCurvature * index) / maxIndex;