forked from security_vision/semantic_graph
Relation tooltip, filter, page title
This commit is contained in:
parent
a43c320af6
commit
1650efae49
3 changed files with 185 additions and 56 deletions
|
@ -22,11 +22,13 @@
|
||||||
--selected-color: var(--color1);
|
--selected-color: var(--color1);
|
||||||
--link-color: rgba(255,255,255,0.5);
|
--link-color: rgba(255,255,255,0.5);
|
||||||
--link-hover-color: var(--hover-color);
|
--link-hover-color: var(--hover-color);
|
||||||
--link-hover-related-color: var(--hover-related-color);
|
--link-hover-related-color: var(--hover-related-color);
|
||||||
--link-focus-color: var(--hover-color);
|
--link-focus-color: var(--hover-color);
|
||||||
|
|
||||||
--body-back: #96a7b7; /*#9cb3c9; /*#8195a7; /*#9cb3c9; #b9cada*/
|
--body-back: #96a7b7; /*#9cb3c9; /*#8195a7; /*#9cb3c9; #b9cada*/
|
||||||
--title-color: #1c1c1c;
|
--title-color: #1c1c1c;
|
||||||
|
|
||||||
|
--zoom: 1; /* to be overriden by js */
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
|
@ -63,33 +65,33 @@ svg .links line, svg .links path {
|
||||||
/* stroke: #f3722c; */
|
/* stroke: #f3722c; */
|
||||||
/* stroke: #9df32c; */
|
/* stroke: #9df32c; */
|
||||||
stroke: var(--link-color);
|
stroke: var(--link-color);
|
||||||
stroke-width: 6;
|
stroke-width: calc(10px / var(--zoom));
|
||||||
fill: none;
|
fill: none;
|
||||||
transition: stroke-width 1s;
|
/* transition: stroke-width 1s; */
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
svg .links line.hover, svg .links path.hover {
|
svg .links line.hover, svg .links path.hover {
|
||||||
stroke: var(--link-hover-color);
|
stroke: var(--link-hover-color) !important;
|
||||||
/* stroke-width: 12; */
|
/* stroke-width: 12; */
|
||||||
marker-end: url(#arrowHeadSelected);
|
marker-end: url(#arrowHeadSelected) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
svg .links .linkedHover path{
|
svg .links .linkedHover path{
|
||||||
stroke: var(--link-hover-related-color);
|
stroke: var(--link-hover-related-color);
|
||||||
stroke-width: 12;
|
stroke-width: calc(12px / var(--zoom));
|
||||||
marker-end: url(#arrowHeadSelectedRelated);
|
marker-end: url(#arrowHeadSelectedRelated);
|
||||||
}
|
}
|
||||||
|
|
||||||
svg .links .linkedSelected path{
|
svg .links .linkedSelected path{
|
||||||
stroke: var(--link-focus-color);
|
stroke: var(--link-hover-related-color);
|
||||||
stroke-width: 12;
|
stroke-width: calc(12px / var(--zoom));
|
||||||
marker-end: url(#arrowHeadSelected);
|
marker-end: url(#arrowHeadSelectedRelated);
|
||||||
}
|
}
|
||||||
|
|
||||||
svg.zoomed .links line, svg.zoomed .links path {
|
/* svg.zoomed .links line, svg.zoomed .links path {
|
||||||
stroke-width: 2;
|
stroke-width: 2;
|
||||||
}
|
} */
|
||||||
|
|
||||||
/* svg.zoomed .links line, svg.zoomed .links path.hover {
|
/* svg.zoomed .links line, svg.zoomed .links path.hover {
|
||||||
stroke-width: 4;
|
stroke-width: 4;
|
||||||
|
@ -154,16 +156,18 @@ svg #header text#subtitle {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.node text.nodeTitle {
|
.node text.nodeTitle {
|
||||||
text-anchor: start;
|
text-anchor: start;
|
||||||
dominant-baseline: hanging;
|
dominant-baseline: hanging;
|
||||||
|
font-size: calc(20pt / var(--zoom)) !important;
|
||||||
/*achieves a 'text-anchor: top'*/
|
/*achieves a 'text-anchor: top'*/
|
||||||
/* font-size: 16pt; */
|
/* font-size: 16pt; */
|
||||||
/*Set this in JS*/
|
/*Set this in JS*/
|
||||||
transition: font-size .4s, opacity 1s;
|
transition: opacity 1s;
|
||||||
fill: #5d5d5f; /*also when hovering node*/
|
fill: #5d5d5f; /*also when hovering node*/
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
pointer-events: none;
|
/* pointer-events: none; */
|
||||||
/*prevent mouse glitches*/
|
/*prevent mouse glitches*/
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -171,6 +175,7 @@ svg #header text#subtitle {
|
||||||
.node text.nodeTitle.overlapping {
|
.node text.nodeTitle.overlapping {
|
||||||
/* used to be shown on hover, but disabled now that we have a tooltip */
|
/* used to be shown on hover, but disabled now that we have a tooltip */
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
svg.zoomed .node text.nodeTitle {
|
svg.zoomed .node text.nodeTitle {
|
||||||
|
@ -190,10 +195,10 @@ svg.zoomed.zoomed2 .node text.nodeTitle {
|
||||||
.node.linkHover circle, .node.linkHover path, .node.linkedHover path, label:hover .node path {
|
.node.linkHover circle, .node.linkHover path, .node.linkedHover path, label:hover .node path {
|
||||||
fill: var(--hover-related-color) !important;
|
fill: var(--hover-related-color) !important;
|
||||||
stroke: var(--hover-related-color);
|
stroke: var(--hover-related-color);
|
||||||
stroke-width: 5px;
|
stroke-width: 2px;
|
||||||
}
|
}
|
||||||
.node.linkedSelected path {
|
.selectedNode .node:not(.linkedSelected) path {
|
||||||
fill: var(--hover-related-color) !important;
|
fill: lightgray !important;
|
||||||
/* same as linkHover/linkedHover but without border */
|
/* same as linkHover/linkedHover but without border */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -204,7 +209,7 @@ svg.zoomed.zoomed2 .node text.nodeTitle {
|
||||||
.node:hover circle, .node:hover path, .node.selected path {
|
.node:hover circle, .node:hover path, .node.selected path {
|
||||||
fill: var(--hover-color) !important;
|
fill: var(--hover-color) !important;
|
||||||
stroke: var(--hover-color);
|
stroke: var(--hover-color);
|
||||||
stroke-width: 5px;
|
stroke-width: 2px;
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
.node:hover text {
|
.node:hover text {
|
||||||
|
@ -212,7 +217,11 @@ svg.zoomed.zoomed2 .node text.nodeTitle {
|
||||||
fill: var(--hover-color);
|
fill: var(--hover-color);
|
||||||
} */
|
} */
|
||||||
|
|
||||||
.node.selected circle, .node.selected path {
|
|
||||||
|
/* .selectedNode .node circle, .selectedNode .node path {
|
||||||
|
fill: lightgray !important;
|
||||||
|
} */
|
||||||
|
.selectedNode .node.selected circle, .selectedNode .node.selected path {
|
||||||
fill: var(--selected-color) !important;
|
fill: var(--selected-color) !important;
|
||||||
stroke: var(--selected-color);
|
stroke: var(--selected-color);
|
||||||
stroke-width: 5px;
|
stroke-width: 5px;
|
||||||
|
@ -245,7 +254,7 @@ svg.zoomed.zoomed2 .node text.nodeTitle {
|
||||||
|
|
||||||
.node.Institution circle, .node.Institution path {
|
.node.Institution circle, .node.Institution path {
|
||||||
/* fill: lightcoral; */
|
/* fill: lightcoral; */
|
||||||
fill: red;
|
fill: #11F999;
|
||||||
}
|
}
|
||||||
|
|
||||||
.node.Dataset circle, .node.Dataset path {
|
.node.Dataset circle, .node.Dataset path {
|
||||||
|
@ -258,37 +267,37 @@ svg.zoomed.zoomed2 .node text.nodeTitle {
|
||||||
}
|
}
|
||||||
|
|
||||||
.node.Institution.Institution-company path{
|
.node.Institution.Institution-company path{
|
||||||
fill: blue;
|
fill: #45F68A;
|
||||||
}
|
}
|
||||||
.node.Institution.Institution-government path{
|
.node.Institution.Institution-government path{
|
||||||
fill: orange;
|
fill: #60F37B;
|
||||||
}
|
}
|
||||||
.node.Institution.Institution-local-government path{
|
.node.Institution.Institution-local-government path{
|
||||||
fill: purple;
|
fill: #75F06D;
|
||||||
}
|
}
|
||||||
.node.Institution.Institution-law-enforcement path{
|
.node.Institution.Institution-law-enforcement path{
|
||||||
fill: seagreen;
|
fill: #87EC60;
|
||||||
}
|
}
|
||||||
.node.Institution.Institution-ngo path{
|
.node.Institution.Institution-ngo path{
|
||||||
fill: yellow;
|
fill: #97E853;
|
||||||
}
|
}
|
||||||
.node.Institution.Institution-university path{
|
.node.Institution.Institution-university path{
|
||||||
fill: pink;
|
fill: #A6E447;
|
||||||
}
|
}
|
||||||
.node.Institution.Institution-research path{
|
.node.Institution.Institution-research path{
|
||||||
fill: fuchsia;
|
fill: #B4E03C;
|
||||||
}
|
}
|
||||||
.node.Institution.Institution-project path{
|
.node.Institution.Institution-project path{
|
||||||
fill: rgb(0, 255, 255);
|
fill: #C1DB31;
|
||||||
}
|
}
|
||||||
.node.Institution.Institution-watchdog path{
|
.node.Institution.Institution-watchdog path{
|
||||||
fill: rgb(145, 255, 0);
|
fill: #CED628;
|
||||||
}
|
}
|
||||||
.node.Institution.Institution-expert-group path{
|
.node.Institution.Institution-expert-group path{
|
||||||
fill: rgb(149, 87, 161);
|
fill: #DAD121;
|
||||||
}
|
}
|
||||||
.node.Institution.Institution-foundation path{
|
.node.Institution.Institution-foundation path{
|
||||||
fill: brown;
|
fill: #E5CB1E;
|
||||||
}
|
}
|
||||||
.node.Institution.Institution-international-organization path{
|
.node.Institution.Institution-international-organization path{
|
||||||
fill: gray;
|
fill: gray;
|
||||||
|
@ -348,6 +357,13 @@ svg.zoomed.zoomed2 .node text.nodeTitle {
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
box-shadow: 2px 2px 5px rgba(0, 0, 0, .5);
|
box-shadow: 2px 2px 5px rgba(0, 0, 0, .5);
|
||||||
}
|
}
|
||||||
|
#tooltip.link{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#tooltip .entity{
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
#tooltip:not(.visible){
|
#tooltip:not(.visible){
|
||||||
position:absolute;
|
position:absolute;
|
||||||
|
|
157
www/graph.js
157
www/graph.js
|
@ -22,6 +22,13 @@ const CONFIG = {
|
||||||
"label": "Institution",
|
"label": "Institution",
|
||||||
"type": "categories",
|
"type": "categories",
|
||||||
},
|
},
|
||||||
|
// TODO: nested filters
|
||||||
|
// TODO:restructure, allow for groups of types:
|
||||||
|
// [12:08, 07-05-2021] Francesco Ragazzi: Government / Regional / local can be the same
|
||||||
|
// [12:08, 07-05-2021] Francesco Ragazzi: Also IO
|
||||||
|
// [12:08, 07-05-2021] Francesco Ragazzi: NGO, Foundation can be grouped
|
||||||
|
// [12:08, 07-05-2021] Francesco Ragazzi: University, Research, Expert group can be grouped
|
||||||
|
// [12:08, 07-05-2021] Francesco Ragazzi: Watchdog and can also be grouped with government
|
||||||
"Law Enforcement": {
|
"Law Enforcement": {
|
||||||
// "label": "Institution",
|
// "label": "Institution",
|
||||||
"type": "institution_types",
|
"type": "institution_types",
|
||||||
|
@ -95,8 +102,53 @@ const CONFIG = {
|
||||||
"Software Deployed",
|
"Software Deployed",
|
||||||
"Software Developer",
|
"Software Developer",
|
||||||
"Dataset Developer",
|
"Dataset Developer",
|
||||||
|
"Related Institutions",
|
||||||
|
"Involved Entities",
|
||||||
],
|
],
|
||||||
|
|
||||||
|
"link_labels": {
|
||||||
|
"Clients": {
|
||||||
|
"label": "is client of",
|
||||||
|
"swap": true,
|
||||||
|
},
|
||||||
|
"Managed by": {
|
||||||
|
"label": "manages",
|
||||||
|
"swap": true,
|
||||||
|
},
|
||||||
|
"Used by": {
|
||||||
|
"label": "uses",
|
||||||
|
"swap": true,
|
||||||
|
},
|
||||||
|
"Funded by": {
|
||||||
|
"label": "is funded by",
|
||||||
|
"swap": false,
|
||||||
|
},
|
||||||
|
"Provided by": {
|
||||||
|
"label": "provides",
|
||||||
|
"swap": true,
|
||||||
|
},
|
||||||
|
"Software Deployed": {
|
||||||
|
"label": "is used in",
|
||||||
|
"swap": true,
|
||||||
|
},
|
||||||
|
"Software Developer": {
|
||||||
|
"label": "is developed by",
|
||||||
|
"swap": false,
|
||||||
|
},
|
||||||
|
"Dataset Developer": {
|
||||||
|
"label": "develops dataset for",
|
||||||
|
"swap": true,
|
||||||
|
},
|
||||||
|
"Related Institutions": {
|
||||||
|
"label": "is related to",
|
||||||
|
"swap": true,
|
||||||
|
},
|
||||||
|
"Involved Entities": {
|
||||||
|
"label": "is involved in",
|
||||||
|
"swap": true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
"geo_properties": [
|
"geo_properties": [
|
||||||
"Geolocation",
|
"Geolocation",
|
||||||
"City Coordinates",
|
"City Coordinates",
|
||||||
|
@ -113,7 +165,7 @@ const CONFIG = {
|
||||||
"Country": ["Country"],
|
"Country": ["Country"],
|
||||||
"City": ["City"],
|
"City": ["City"],
|
||||||
// ["Deployment type"], // TODO: select this
|
// ["Deployment type"], // TODO: select this
|
||||||
"Institution" : ["Institution Type"], // TODO: select this (local gov, etc.)
|
"Institution": ["Institution Type"], // TODO: select this (local gov, etc.)
|
||||||
"Dataset": ["Datasets used"],
|
"Dataset": ["Datasets used"],
|
||||||
"Company": ["Managed by", "Provided by", "Developped by (institutions)"],
|
"Company": ["Managed by", "Provided by", "Developped by (institutions)"],
|
||||||
"Tech": ["Technologies Used", "Software Deployed"],
|
"Tech": ["Technologies Used", "Software Deployed"],
|
||||||
|
@ -130,6 +182,17 @@ const CONFIG = {
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
function getLinkLabelConfig(linkName){
|
||||||
|
if(CONFIG.link_labels.hasOwnProperty(linkName)){
|
||||||
|
return CONFIG.link_labels[linkName];
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
'label': linkName,
|
||||||
|
'swap': false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// let width = window.innerWidth;
|
// let width = window.innerWidth;
|
||||||
// let height = window.innerHeight;
|
// let height = window.innerHeight;
|
||||||
|
|
||||||
|
@ -264,7 +327,7 @@ function getTitle(obj) {
|
||||||
function getCategories(obj) {
|
function getCategories(obj) {
|
||||||
// console.log(obj);
|
// console.log(obj);
|
||||||
let cats = obj.printouts['Category'].map(n => n.fulltext.split(':')[1]);
|
let cats = obj.printouts['Category'].map(n => n.fulltext.split(':')[1]);
|
||||||
if(obj.printouts.hasOwnProperty("Institution Type") && obj.printouts['Institution Type'].length) {
|
if (obj.printouts.hasOwnProperty("Institution Type") && obj.printouts['Institution Type'].length) {
|
||||||
obj.printouts['Institution Type'].forEach(type => {
|
obj.printouts['Institution Type'].forEach(type => {
|
||||||
cats.push(getInstitutionClass(type.fulltext));
|
cats.push(getInstitutionClass(type.fulltext));
|
||||||
});
|
});
|
||||||
|
@ -272,7 +335,7 @@ function getCategories(obj) {
|
||||||
return cats;
|
return cats;
|
||||||
}
|
}
|
||||||
function getInstitutionClass(name) {
|
function getInstitutionClass(name) {
|
||||||
return "Institution-"+ slugify(name);
|
return "Institution-" + slugify(name);
|
||||||
}
|
}
|
||||||
function getClasses(obj) {
|
function getClasses(obj) {
|
||||||
const classes = getCategories(obj);
|
const classes = getCategories(obj);
|
||||||
|
@ -339,7 +402,7 @@ class NodeMap {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
this.svg = this.root.append('svg')
|
this.svg = this.root.append('svg')
|
||||||
this.svg.append('defs').html(`<marker markerHeight="4" markerWidth="4" refY="0" refX="6" viewBox="0 -3 8 6" preserveAspectRatio="none" orient="auto" id="arrowHead" ><path d="M0,-3L8,0L0,3"></path></marker><marker markerHeight="4" markerWidth="4" refY="0" refX="6" viewBox="0 -3 8 6" preserveAspectRatio="none" orient="auto" id="arrowHeadSelected"><path d="M0,-3L8,0L0,3"></path></marker><marker markerHeight="4" markerWidth="4" refY="0" refX="6" viewBox="0 -3 8 6" preserveAspectRatio="none" orient="auto" id="arrowHeadSelectedRelated"><path d="M0,-3L8,0L0,3"></path></marker>
|
this.svg.append('defs').html(`<marker markerHeight="3" markerWidth="3" refY="0" refX="6" viewBox="0 -3 8 6" preserveAspectRatio="none" orient="auto" id="arrowHead" ><path d="M0,-3L8,0L0,3"></path></marker><marker markerHeight="3" markerWidth="3" refY="0" refX="6" viewBox="0 -3 8 6" preserveAspectRatio="none" orient="auto" id="arrowHeadSelected"><path d="M0,-3L8,0L0,3"></path></marker><marker markerHeight="3" markerWidth="3" refY="0" refX="6" viewBox="0 -3 8 6" preserveAspectRatio="none" orient="auto" id="arrowHeadSelectedRelated"><path d="M0,-3L8,0L0,3"></path></marker>
|
||||||
<!--Sketching:-->
|
<!--Sketching:-->
|
||||||
<defs>
|
<defs>
|
||||||
<filter id="tint">
|
<filter id="tint">
|
||||||
|
@ -358,7 +421,7 @@ class NodeMap {
|
||||||
</filter>
|
</filter>
|
||||||
</defs>`);
|
</defs>`);
|
||||||
|
|
||||||
this.svg.on('click', (e) => { console.log(e); this.deselectNode()})
|
this.svg.on('click', (e) => { console.log(e); this.deselectNode() })
|
||||||
|
|
||||||
// const noise = 0.001;
|
// const noise = 0.001;
|
||||||
// this.svg.append('defs').append('filter').attr('id', 'splotch').html( `${
|
// this.svg.append('defs').append('filter').attr('id', 'splotch').html( `${
|
||||||
|
@ -421,7 +484,7 @@ class NodeMap {
|
||||||
return "country";
|
return "country";
|
||||||
})
|
})
|
||||||
.attr("d", this.proj)
|
.attr("d", this.proj)
|
||||||
// .attr("filter", 'url(#splotch)')
|
// .attr("filter", 'url(#splotch)')
|
||||||
// .attr("fill", );
|
// .attr("fill", );
|
||||||
|
|
||||||
this.g_borders
|
this.g_borders
|
||||||
|
@ -443,6 +506,7 @@ class NodeMap {
|
||||||
if (zoomTimeout) {
|
if (zoomTimeout) {
|
||||||
clearTimeout(zoomTimeout)
|
clearTimeout(zoomTimeout)
|
||||||
}
|
}
|
||||||
|
document.querySelector(':root').style.setProperty('--zoom', evt.transform.k);
|
||||||
zoomTimeout = setTimeout(() => {
|
zoomTimeout = setTimeout(() => {
|
||||||
this.g_nodes.attr('style', `font-size:${22000 / this.height / evt.transform.k}pt`)
|
this.g_nodes.attr('style', `font-size:${22000 / this.height / evt.transform.k}pt`)
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
@ -517,7 +581,6 @@ class NodeMap {
|
||||||
|
|
||||||
this.graph.nodes.forEach((d) => {
|
this.graph.nodes.forEach((d) => {
|
||||||
for (const prop of CONFIG.geo_properties) {
|
for (const prop of CONFIG.geo_properties) {
|
||||||
|
|
||||||
// console.log(this,d.printouts, prop)
|
// console.log(this,d.printouts, prop)
|
||||||
if (d.printouts[prop].length) {
|
if (d.printouts[prop].length) {
|
||||||
// console.log("fix node", d);
|
// console.log("fix node", d);
|
||||||
|
@ -667,8 +730,15 @@ class NodeMap {
|
||||||
}
|
}
|
||||||
|
|
||||||
showTooltip(el, node, links) {
|
showTooltip(el, node, links) {
|
||||||
// TODO: make links optional (otherwise collect links here)
|
if(el.tagName != 'path'){
|
||||||
|
let parentEl = el.parentNode;
|
||||||
|
if(parentEl.tagName != 'g'){
|
||||||
|
parentEl = parentEl.parentNode;
|
||||||
|
}
|
||||||
|
el = parentEl.querySelector('path');
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: make links optional (otherwise collect links here)
|
||||||
this.tooltipEl.innerHTML = `
|
this.tooltipEl.innerHTML = `
|
||||||
<span class='category'>${getCategories(node)[0]}</span>
|
<span class='category'>${getCategories(node)[0]}</span>
|
||||||
<h3>${node.fulltext}</h3>
|
<h3>${node.fulltext}</h3>
|
||||||
|
@ -685,11 +755,46 @@ class NodeMap {
|
||||||
this.tooltipEl.style.left = (rect.left + rect.width / 2 - rectTT.width / 2) + 'px';
|
this.tooltipEl.style.left = (rect.left + rect.width / 2 - rectTT.width / 2) + 'px';
|
||||||
// console.log(el, node, rect.top);
|
// console.log(el, node, rect.top);
|
||||||
|
|
||||||
this.tooltipEl.classList.add('visible');
|
this.tooltipEl.classList.add('visible', 'node');
|
||||||
|
}
|
||||||
|
|
||||||
|
showRelationTooltip(link, evt) {
|
||||||
|
const {label, swap} = getLinkLabelConfig(link.name);
|
||||||
|
if(swap){
|
||||||
|
this.tooltipEl.innerHTML = `
|
||||||
|
<span class='relation'>
|
||||||
|
<span class='entity'>${link.target.fulltext}</span>
|
||||||
|
${label}
|
||||||
|
<span class='entity'>${link.source.fulltext}</span>
|
||||||
|
</span>
|
||||||
|
`;
|
||||||
|
} else {
|
||||||
|
this.tooltipEl.innerHTML = `
|
||||||
|
<span class='relation'>
|
||||||
|
<span class='entity'>${link.source.fulltext}</span>
|
||||||
|
${label}
|
||||||
|
<span class='entity'>${link.target.fulltext}</span>
|
||||||
|
</span>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rectTT = this.tooltipEl.getBoundingClientRect();
|
||||||
|
|
||||||
|
this.trackerEv = (evt) => {
|
||||||
|
this.tooltipEl.style.top = (evt.clientY - rectTT.height - 10) + 'px';
|
||||||
|
this.tooltipEl.style.left = (evt.clientX - rectTT.width / 2) + 'px';
|
||||||
|
};
|
||||||
|
window.addEventListener('mousemove', this.trackerEv);
|
||||||
|
|
||||||
|
this.tooltipEl.classList.add('visible', 'link');
|
||||||
}
|
}
|
||||||
|
|
||||||
hideTooltip() {
|
hideTooltip() {
|
||||||
this.tooltipEl.classList.remove('visible');
|
this.tooltipEl.classList.remove('visible', 'node', 'link');
|
||||||
|
if(this.trackerEv){
|
||||||
|
window.removeEventListener('mousemove', this.trackerEv);
|
||||||
|
this.trackerEv = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
selectNode(node) {
|
selectNode(node) {
|
||||||
|
@ -714,7 +819,7 @@ class NodeMap {
|
||||||
connectedNodes.forEach(n => document.getElementById(n.id).classList.add('linkedSelected'));
|
connectedNodes.forEach(n => document.getElementById(n.id).classList.add('linkedSelected'));
|
||||||
links.forEach(l => document.getElementById(getLinkId(l)).classList.add('linkedSelected'));
|
links.forEach(l => document.getElementById(getLinkId(l)).classList.add('linkedSelected'));
|
||||||
|
|
||||||
|
this.container.classed('selectedNode', true);
|
||||||
// TODO: show details;
|
// TODO: show details;
|
||||||
|
|
||||||
// alert('not yet implemented');
|
// alert('not yet implemented');
|
||||||
|
@ -730,6 +835,7 @@ class NodeMap {
|
||||||
while (els.length) {
|
while (els.length) {
|
||||||
els[0].classList.remove('linkedSelected');
|
els[0].classList.remove('linkedSelected');
|
||||||
}
|
}
|
||||||
|
this.container.classed('selectedNode', false);
|
||||||
}
|
}
|
||||||
|
|
||||||
update() {
|
update() {
|
||||||
|
@ -779,7 +885,10 @@ class NodeMap {
|
||||||
.attr('d', (n) => {
|
.attr('d', (n) => {
|
||||||
return getSymbolForNode(n)(n);
|
return getSymbolForNode(n)(n);
|
||||||
})
|
})
|
||||||
var nodeTitle = group.append('text').attr("class", "nodeTitle").attr("y", "3").attr('x', 5);
|
var nodeTitle = group.append('text').attr("class", "nodeTitle").attr("y", "4").attr('x', 5);
|
||||||
|
// nodeTitle.on('mouseover', (evt, n) =>{
|
||||||
|
// console.log(evt,n)
|
||||||
|
// });
|
||||||
nodeTitle
|
nodeTitle
|
||||||
.each(function (node, i, nodes) {
|
.each(function (node, i, nodes) {
|
||||||
var textLength = void 0;
|
var textLength = void 0;
|
||||||
|
@ -790,7 +899,7 @@ class NodeMap {
|
||||||
titleTexts = splitText(titleText);
|
titleTexts = splitText(titleText);
|
||||||
}
|
}
|
||||||
if (titleTexts !== false) {
|
if (titleTexts !== false) {
|
||||||
const tspan1 = self.append("tspan").text(titleTexts[0]).attr("y", "3").attr("x", "5");
|
const tspan1 = self.append("tspan").text(titleTexts[0]).attr("y", "4").attr("x", "5");
|
||||||
const tspan = self.append("tspan").text(titleTexts[1]).attr("dy", "1em").attr("x", "5");
|
const tspan = self.append("tspan").text(titleTexts[1]).attr("dy", "1em").attr("x", "5");
|
||||||
// const textLength1 = tspan.node().getComputedTextLength();
|
// const textLength1 = tspan.node().getComputedTextLength();
|
||||||
// const textLength2 = tspan.node().getComputedTextLength();
|
// const textLength2 = tspan.node().getComputedTextLength();
|
||||||
|
@ -873,8 +982,8 @@ class NodeMap {
|
||||||
group.append("path")
|
group.append("path")
|
||||||
.attr("marker-end", "url(#arrowHead)")
|
.attr("marker-end", "url(#arrowHead)")
|
||||||
.attr('id', (d, i) => 'linkpath_' + i)
|
.attr('id', (d, i) => 'linkpath_' + i)
|
||||||
.on("mouseover", function (ev, link) {
|
.on("mouseover", (ev, link) => {
|
||||||
d3.select(this).classed('hover', true);
|
d3.select(ev.target).classed('hover', true);
|
||||||
const nodes = document.getElementsByClassName('node');
|
const nodes = document.getElementsByClassName('node');
|
||||||
for (let n of nodes) {
|
for (let n of nodes) {
|
||||||
const d = d3.select(n).datum();
|
const d = d3.select(n).datum();
|
||||||
|
@ -882,9 +991,13 @@ class NodeMap {
|
||||||
n.classList.add('linkHover');
|
n.classList.add('linkHover');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.showRelationTooltip(link, ev);
|
||||||
|
|
||||||
// console.log(l);
|
// console.log(l);
|
||||||
}).on("mouseout", function (ev, link) {
|
}).on("mouseout", (ev, link) => {
|
||||||
d3.select(this).classed('hover', false);
|
this.hideTooltip();
|
||||||
|
d3.select(ev.target).classed('hover', false);
|
||||||
const nodes = document.getElementsByClassName('linkHover');
|
const nodes = document.getElementsByClassName('linkHover');
|
||||||
while (nodes.length) {
|
while (nodes.length) {
|
||||||
nodes[0].classList.remove('linkHover');
|
nodes[0].classList.remove('linkHover');
|
||||||
|
@ -1581,9 +1694,9 @@ class Store {
|
||||||
}
|
}
|
||||||
|
|
||||||
isFiltered(node) {
|
isFiltered(node) {
|
||||||
if(this.filters.categories.includes(node.printouts['Category'][0].fulltext.split(':')[1]))
|
if (this.filters.categories.includes(node.printouts['Category'][0].fulltext.split(':')[1]))
|
||||||
return true;
|
return true;
|
||||||
if(node.printouts['Institution Type'].length && this.filters.institution_types.includes(node.printouts['Institution Type'][0].fulltext))
|
if (node.printouts['Institution Type'].length && this.filters.institution_types.includes(node.printouts['Institution Type'][0].fulltext))
|
||||||
return true;
|
return true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -1634,12 +1747,12 @@ class Store {
|
||||||
Object.keys(CONFIG.filters).forEach(f => {
|
Object.keys(CONFIG.filters).forEach(f => {
|
||||||
const settings = CONFIG.filters[f];
|
const settings = CONFIG.filters[f];
|
||||||
|
|
||||||
if(settings.type == 'institution_types')
|
if (settings.type == 'institution_types')
|
||||||
// TODO; For now, skip
|
// TODO; For now, skip
|
||||||
return;
|
return;
|
||||||
|
|
||||||
let categories = [f];
|
let categories = [f];
|
||||||
if( settings.type == 'institution_types')
|
if (settings.type == 'institution_types')
|
||||||
categories = ['Institution', getInstitutionClass(f)];
|
categories = ['Institution', getInstitutionClass(f)];
|
||||||
|
|
||||||
let labelEl = document.createElement('label')
|
let labelEl = document.createElement('label')
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<link rel="stylesheet" href="graph.css">
|
<link rel="stylesheet" href="graph.css">
|
||||||
|
<title>Remote Biometric Identification | A survey of the European Union</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
@ -18,7 +18,7 @@
|
||||||
|
|
||||||
|
|
||||||
<aside id="filters">
|
<aside id="filters">
|
||||||
<h3>Legend</h3>
|
<h3>Filter</h3>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
</header>
|
</header>
|
||||||
|
|
Loading…
Reference in a new issue