diff --git a/www/graph.css b/www/graph.css index c53e97c..c801646 100644 --- a/www/graph.css +++ b/www/graph.css @@ -391,6 +391,10 @@ svg.zoomed.zoomed2 .node text.nodeTitle { border-radius: 5px; box-shadow: 2px 2px 5px rgba(0, 0, 0, .5); } +#tooltip.deploymentTooltip{ + background-color: black; + color: white; +} #tooltip.link{ } @@ -415,6 +419,9 @@ svg.zoomed.zoomed2 .node text.nodeTitle { color: black; text-align: center;; } +#tooltip.deploymentTooltip .category{ + color: white; +} #tooltip .category::before{ content:'ยท ' } @@ -426,6 +433,12 @@ svg.zoomed.zoomed2 .node text.nodeTitle { color: gray; text-align: center;; } +#tooltip .node_sources::before{ + content: 'source '; +} +#tooltip .node_sources{ + text-align: center; +} #closeInfo { cursor: pointer; @@ -564,19 +577,55 @@ p.subtitle { #closeSelection{ pointer-events: none; - opacity: 0; - position: absolute; - top: 0; - right: 0; + position: fixed; + bottom: -100px; + left: 0; background-color: white; - font-size: 200%; - width: 35px; + font-size: 50px; + width: 80px; + height: 80px; text-align:center; z-index: 10; + line-height: 80px; + transition: bottom .2s; } .selectedNode #closeSelection{ - pointer-events: all;; - opacity: 1; + pointer-events: all; cursor: pointer; + bottom: 0; +} + +#sources{ + position: fixed; + bottom: 0; + left: 80px; + height: 47px; + padding: 20px 20px 13px 20px; + background: black; + color: white; + transition: bottom .2s; + line-height: 40px; +} + +#sources:not(.visible){ + bottom:-100px; +} + +#sources h3{ + margin: 5px 0; + text-align: left; + display: inline-block; + font-size: 100%; +} +#sources h3::after{ + content:':'; +} + +#sources .node_sources{ + display: inline; +} + +#sources a{ + color: lightblue; } \ No newline at end of file diff --git a/www/graph.js b/www/graph.js index cb94c0c..1f490e5 100644 --- a/www/graph.js +++ b/www/graph.js @@ -366,6 +366,7 @@ class NodeMap { this.root = d3.select(parent); this.resizeEvent = window.addEventListener('resize', this.resize.bind(this)); this.tooltipEl = document.getElementById('tooltip'); + this.sourcesEl = document.getElementById('sources'); this.selectedNode = null; document.getElementById('closeSelection').addEventListener('click', (ev) => this.deselectNode()); @@ -760,10 +761,17 @@ class NodeMap { } el = parentEl.querySelector('path'); } + + const categories = getCategories(node); + if (categories.includes('Deployments')){ + this.tooltipEl.classList.add('deploymentTooltip'); + } else { + this.tooltipEl.classList.remove('deploymentTooltip'); + } // TODO: make links optional (otherwise collect links here) this.tooltipEl.innerHTML = ` - ${getCategories(node)[0]} + ${categories[0]}

${node.fulltext}

`; if (links.length) { @@ -772,6 +780,7 @@ class NodeMap { Click to examine ${links.length} ${rels} `; } + const rect = el.getBoundingClientRect() const rectTT = this.tooltipEl.getBoundingClientRect(); this.tooltipEl.style.top = (rect.top - rectTT.height) + 'px'; @@ -783,6 +792,7 @@ class NodeMap { showRelationTooltip(link, evt) { const {label, swap} = getLinkLabelConfig(link.name); + this.tooltipEl.classList.remove('deploymentTooltip'); if(swap){ this.tooltipEl.innerHTML = ` @@ -845,12 +855,15 @@ class NodeMap { this.container.classed('selectedNode', true); document.body.classList.add('selectedNode'); + + this.showSources(node); // TODO: show details; // alert('not yet implemented'); } deselectNode() { + this.hideSources(); this.selectedNode = null; let nodeEls = document.getElementsByClassName('selected'); while (nodeEls.length) { @@ -864,6 +877,37 @@ class NodeMap { document.body.classList.remove('selectedNode'); } + showSources(node){ + + const categories = getCategories(node); + if (!categories.includes('Deployments')){ + return; + } + if(!node.printouts['Source'].length) { + return; + } + + + setTimeout(() => { // give potential visible sources time to hide + let sources = []; + for(let source of node.printouts['Source']){ + const url = document.createElement('a'); + url.href = source; + const hostname = url.hostname.startsWith('www.') ? url.hostname.substring(4) : url.hostname; + sources .push(`${hostname}`); + } + + + const title = node.printouts['Source'].length > 1 ? "Sources" : "Source" + this.sourcesEl.innerHTML = `

${title}

` + sources.join(', '); + this.sourcesEl.classList.add('visible'); + }, 500); + } + + hideSources(){ + this.sourcesEl.classList.remove('visible'); + } + update() { // console.log(this.graph) @@ -941,64 +985,6 @@ class NodeMap { - // const labelPadding = 1; - - // // // the component used to render each label - // var textLabel = fc.layoutTextLabel() - // .padding(labelPadding) - // //.value(function(d) { return map_data.properties.iso; }); - // //.value(function(d) { return d.properties.iso; }); - // .value( (d) => getTitle(d)); - - // // a strategy that combines simulated annealing with removal - // // of overlapping labels - // // */fc.layoutGreedy - // const strategy = fc.layoutRemoveOverlaps(fc.layoutGreedy()); - - // // create the layout that positions the labels - // this.layoutLabels = fc.layoutLabel(strategy) - // .size((d, i, g) => { - // // measure the label and add the required padding - // const textSize = g[i].getElementsByTagName('text')[0].getBBox(); - // console.log(textSize); - // // return [30, 20]; - // return [textSize.width + labelPadding * 2, textSize.height + labelPadding * 2]; - // }) - // .position(d => [d.x, d.y]) - // .component(textLabel); - - // // render! - // // this.node.datum(this.graph.nodes,).call(labels) - // this.labels = this.container.append('g').attr('class','labels'); - // this.labels.datum(this.graph.nodes) - // // // this.node - // .call(this.layoutLabels); - - - // // use simulate annealing to find minimum overlapping text label positions - // //https://github.com/d3fc/d3fc-label-layout/blob/master/README.md - // var strategy = fc.layoutGreedy(); - // //var strategy = fc.layoutAnnealing(); - - // // create the layout that positions the labels - // var labels = fc.layoutLabel(strategy) - // .size(function (_, i, g) { - // // measure the label and add the required padding - // var textSize = d3.select(g[i]) - // .select('text') - // .node() - // .getBBox(); - // return [textSize.width + labelPadding * 2, textSize.height + labelPadding * 2]; - // }) - // .position((d) => this.projection([d.lon, d.lat]); }) - // .component(textLabel); - - // // render! - // this.container.datum(countries) - // .call(labels); - - - this.link = this.link .data(this.graph.links) .join( @@ -1020,8 +1006,6 @@ class NodeMap { } this.showRelationTooltip(link, ev); - - // console.log(l); }).on("mouseout", (ev, link) => { this.hideTooltip(); d3.select(ev.target).classed('hover', false); @@ -1029,10 +1013,6 @@ class NodeMap { while (nodes.length) { nodes[0].classList.remove('linkHover'); } - // l.classed('hover',false); - // l.target.classed('hover',false); - // l.source.classed('hover',false); - // console.log(l,'l'); }).on("click", (ev, link) => { ev.stopPropagation(); this.selectNode(link.source); @@ -1105,28 +1085,25 @@ class NodeMap { var tgtSize = _mapGraph.getSizeForNode(l.target); // Compute the line endpoint such that the arrow - // is touching the edge of the node rectangle perfectly. - l.sourceX = sourceX + Math.sin(angle) * srcSize; - l.targetX = targetX - Math.sin(angle) * tgtSize; - l.sourceY = l.source.y + Math.cos(angle) * srcSize; - l.targetY = l.target.y - Math.cos(angle) * tgtSize; + // it not in the center, but rather slightly out of it + // use a small ofset for the angle to compensate roughly for the curve + l.sourceX = sourceX + Math.sin(angle+.5) * srcSize; + l.targetX = targetX - Math.sin(angle-.5) * tgtSize; + l.sourceY = l.source.y + Math.cos(angle+.5) * srcSize; + l.targetY = l.target.y - Math.cos(angle-.5) * tgtSize; // const coor_source = _mapGraph.projection.invert([l.source.x, l.source.y]); // const coor_target = _mapGraph.projection.invert([l.target.x, l.target.y]); // const middleCoor = [coor_source[0] * .5 + coor_target[0] * .5, coor_source[1] * .5 + coor_target[1] * .5]; // const middlePoint = _mapGraph.projection(middleCoor); + // find radius of arc based on distance between points const dr = Math.sqrt(dx * dx + dy * dy); // "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y let rel = d3.select(this); - rel.select("path") //${middlePoint[0]},${middlePoint[1]} - // .attr('d', `M ${l.sourceX},${l.sourceY} L ${l.targetX},${l.targetY}`) + rel.select("path") .attr('d', `M ${l.sourceX},${l.sourceY} A ${dr},${dr} 0 0,1 ${l.targetX},${l.targetY}`) - // .attr("x1", l.sourceX) - // .attr("y1", l.sourceY) - // .attr("x2", l.targetX) - // .attr("y2", l.targetY) rel.select('text') .attr("transform", function (d) { diff --git a/www/index.html b/www/index.html index f4cda28..ba5396d 100644 --- a/www/index.html +++ b/www/index.html @@ -9,6 +9,7 @@
+
×
@@ -16,7 +17,6 @@

Remote Biometric Identification

A survey of the European Union

-