forked from security_vision/semantic_graph
Title placement
This commit is contained in:
parent
9a79cea9d7
commit
2dda26ff77
3 changed files with 268 additions and 90 deletions
123
www/graph.css
123
www/graph.css
|
@ -1,3 +1,9 @@
|
|||
@font-face {
|
||||
font-family: 'Lexend Mega Regular';
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
src: local('Lexend Mega Regular'), url('LexendMega-Regular.woff') format('woff');
|
||||
}
|
||||
|
||||
:root {
|
||||
--color1: #f94144;
|
||||
|
@ -10,7 +16,6 @@
|
|||
--color8: #4d908e;
|
||||
--color9: #577590;
|
||||
--color10: #277da1;
|
||||
|
||||
--hover-color: var(--color1);
|
||||
/* --hover-color: var(darkblue); */
|
||||
--selected-color: var(--color1);
|
||||
|
@ -51,11 +56,52 @@ svg .links line.hover, svg .links path.hover{
|
|||
svg.zoomed .links line, svg.zoomed .links path {
|
||||
stroke-width: 2;
|
||||
}
|
||||
|
||||
svg.zoomed .links line, svg.zoomed .links path.hover {
|
||||
stroke-width: 2;
|
||||
stroke-width: 4;
|
||||
}
|
||||
|
||||
svg .title {
|
||||
font-size: 200;
|
||||
}
|
||||
|
||||
svg .subtitle {
|
||||
font-size: 100;
|
||||
}
|
||||
|
||||
svg #countries .country{
|
||||
fill:rgba(200,200,200,0.7);
|
||||
}
|
||||
svg #countries .country.eu_country{
|
||||
fill:black;
|
||||
}
|
||||
|
||||
svg #header #titlePath, svg #header #subtitlePath {
|
||||
stroke: none;
|
||||
fill: none;
|
||||
}
|
||||
|
||||
svg #header text {
|
||||
font-size: 180px;
|
||||
font-family: "Lexend Mega Regular";
|
||||
/*Comfortaa*/
|
||||
opacity: .8;
|
||||
fill: black;
|
||||
text-shadow: rgba(0, 0, 0, .5) 5px 5px 10px;
|
||||
/* text-transform: uppercase; */
|
||||
}
|
||||
|
||||
svg #header text:nth-of-type(2) {
|
||||
dominant-baseline: hanging;
|
||||
transform: translate(10px, 25px);
|
||||
}
|
||||
|
||||
svg #header text#subtitle {
|
||||
font-size: 70px;
|
||||
fill: var(--color9);
|
||||
}
|
||||
|
||||
.links text {
|
||||
display: none;
|
||||
font-size: 5pt;
|
||||
|
@ -64,27 +110,29 @@ svg.zoomed .links line, svg.zoomed .links path.hover{
|
|||
}
|
||||
|
||||
.node {
|
||||
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.node text.nodeTitle {
|
||||
text-anchor: start;
|
||||
dominant-baseline: hanging; /*achieves a 'text-anchor: top'*/
|
||||
dominant-baseline: hanging;
|
||||
/*achieves a 'text-anchor: top'*/
|
||||
font-size: 16pt;
|
||||
transition: font-size .4s, opacity 1s;
|
||||
fill: white;
|
||||
opacity: 1;
|
||||
pointer-events: none; /*prevent mouse glitches*/
|
||||
pointer-events: none;
|
||||
/*prevent mouse glitches*/
|
||||
}
|
||||
|
||||
.node:not(:hover):not(.linkHover) text.nodeTitle.overlapping {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
|
||||
svg.zoomed .node text.nodeTitle {
|
||||
font-size: 6pt;
|
||||
}
|
||||
|
||||
svg.zoomed.zoomed2 .node text.nodeTitle {
|
||||
font-size: 3pt;
|
||||
}
|
||||
|
@ -94,10 +142,12 @@ svg.zoomed.zoomed2 .node text.nodeTitle{
|
|||
}
|
||||
|
||||
/* Whenever a connected link is hovered */
|
||||
|
||||
.node.linkHover circle {
|
||||
stroke: var(--hover-color);
|
||||
stroke-width: 5px;
|
||||
}
|
||||
|
||||
.node.linkHover text.nodeTitle.overlapping {
|
||||
transition: opacity 0s;
|
||||
}
|
||||
|
@ -106,10 +156,12 @@ svg.zoomed.zoomed2 .node text.nodeTitle{
|
|||
stroke: var(--hover-color);
|
||||
stroke-width: 5px;
|
||||
}
|
||||
|
||||
.node:hover text {
|
||||
transition: none;
|
||||
fill: var(--hover-color);
|
||||
}
|
||||
|
||||
.node.selected circle {
|
||||
stroke: var(--selected-color);
|
||||
stroke-width: 5px;
|
||||
|
@ -118,30 +170,34 @@ svg.zoomed.zoomed2 .node text.nodeTitle{
|
|||
.node.City circle {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.node.City {
|
||||
fill: white;
|
||||
stroke: black;
|
||||
stroke-width: .5px;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.node.Person circle {
|
||||
fill: lightgreen
|
||||
}
|
||||
|
||||
.node.Technology circle {
|
||||
fill: lightcoral;
|
||||
}
|
||||
|
||||
.node.Deployment circle {
|
||||
fill: lightblue;
|
||||
}
|
||||
|
||||
.node.Institution circle {
|
||||
fill: lightgoldenrodyellow
|
||||
}
|
||||
|
||||
.node.Dataset circle {
|
||||
fill: plum
|
||||
}
|
||||
|
||||
|
||||
|
||||
.labels .label text {
|
||||
fill: yellow;
|
||||
opacity: 1 !important;
|
||||
|
@ -163,7 +219,6 @@ svg.zoomed.zoomed2 .node text.nodeTitle{
|
|||
fill: var(--color7)
|
||||
} */
|
||||
|
||||
|
||||
#nodeInfo {
|
||||
position: fixed;
|
||||
display: block;
|
||||
|
@ -202,18 +257,64 @@ svg.zoomed.zoomed2 .node text.nodeTitle{
|
|||
a, a:link {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
#filters,#menu{
|
||||
header {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
background: white;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
/* color:var(--color9); */
|
||||
margin: 0;
|
||||
text-transform: uppercase;
|
||||
display: none;
|
||||
}
|
||||
|
||||
p.subtitle {
|
||||
margin: 0;
|
||||
color: var(--color10);
|
||||
/* text-transform: uppercase;; */
|
||||
display: none;
|
||||
}
|
||||
|
||||
#filters h3{
|
||||
text-align: center;;
|
||||
}
|
||||
#filters label {
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
#filters span:hover {
|
||||
color: var(--hover-color);
|
||||
}
|
||||
|
||||
#filters input {
|
||||
/* display: none; */
|
||||
}
|
||||
|
||||
#filters input+span {
|
||||
display: inline-block;
|
||||
padding-left: 10px;
|
||||
|
||||
/* background: var(--color9); */
|
||||
text-decoration:line-through;
|
||||
}
|
||||
|
||||
#filters input:checked+span {
|
||||
/* color: var(--selected-color); */
|
||||
text-decoration: none;
|
||||
/* box-shadow: inset 0 0 5px black; */
|
||||
}
|
||||
|
||||
#map .borders {
|
||||
stroke-width: 6px;
|
||||
stroke: white;
|
||||
|
|
84
www/graph.js
84
www/graph.js
|
@ -1,5 +1,7 @@
|
|||
|
||||
const CONFIG = {
|
||||
'title': "Biometric Mass Surveillance",
|
||||
'subtitle': "Connections in the European Union & beyond",
|
||||
// 'nodeSize': 8,
|
||||
'nodeRadius': 5,
|
||||
'nodeRepositionPadding': 3,
|
||||
|
@ -56,6 +58,29 @@ const CONFIG = {
|
|||
// let height = window.innerHeight;
|
||||
|
||||
|
||||
// Slugify a string, by https://lucidar.me/en/web-dev/how-to-slugify-a-string-in-javascript/
|
||||
function slugify(str) {
|
||||
str = str.replace(/^\s+|\s+$/g, '');
|
||||
|
||||
// Make the string lowercase
|
||||
str = str.toLowerCase();
|
||||
|
||||
// Remove accents, swap ñ for n, etc
|
||||
var from = "ÁÄÂÀÃÅČÇĆĎÉĚËÈÊẼĔȆÍÌÎÏŇÑÓÖÒÔÕØŘŔŠŤÚŮÜÙÛÝŸŽáäâàãåčçćďéěëèêẽĕȇíìîïňñóöòôõøðřŕšťúůüùûýÿžþÞĐđßÆa·/_,:;";
|
||||
var to = "AAAAAACCCDEEEEEEEEIIIINNOOOOOORRSTUUUUUYYZaaaaaacccdeeeeeeeeiiiinnooooooorrstuuuuuyyzbBDdBAa------";
|
||||
for (var i = 0, l = from.length; i < l; i++) {
|
||||
str = str.replace(new RegExp(from.charAt(i), 'g'), to.charAt(i));
|
||||
}
|
||||
|
||||
// Remove invalid chars
|
||||
str = str.replace(/[^a-z0-9 -]/g, '')
|
||||
// Collapse whitespace and replace by -
|
||||
.replace(/\s+/g, '-')
|
||||
// Collapse dashes
|
||||
.replace(/-+/g, '-');
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
@ -202,6 +227,7 @@ class NodeMap {
|
|||
render() {
|
||||
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" fill="#f3722c"><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" fill="white"></path></marker>');
|
||||
|
||||
this.resize();
|
||||
|
||||
this.projection = d3.geoHill()
|
||||
|
@ -236,14 +262,14 @@ class NodeMap {
|
|||
.data(this.countries)
|
||||
.enter()
|
||||
.append("path")
|
||||
.attr("class", "countries")
|
||||
.attr("d", this.proj)
|
||||
.attr("fill", (n) => {
|
||||
.attr("class", (n) => {
|
||||
if (CONFIG.countries.indexOf(n.properties.name) !== -1) {
|
||||
return '';
|
||||
return 'country eu_country';
|
||||
}
|
||||
return "rgba(200,200,200,.7)";
|
||||
});
|
||||
return "country";
|
||||
})
|
||||
.attr("d", this.proj)
|
||||
// .attr("fill", );
|
||||
|
||||
this.g_borders
|
||||
.append("path")
|
||||
|
@ -270,6 +296,49 @@ class NodeMap {
|
|||
.call(zoom)
|
||||
.call(zoom.transform, d3.zoomIdentity.scale(.5, .5));
|
||||
|
||||
this.title = this.container.append('g').attr('id', 'header');
|
||||
|
||||
const titleFeature = {
|
||||
"type": "LineString",
|
||||
"coordinates": []
|
||||
};
|
||||
const subtitleFeature = {
|
||||
"type": "LineString",
|
||||
"coordinates": []
|
||||
};
|
||||
for (let index = 26; index < 70; index++) {
|
||||
// projection apparently tries to find the shortest path between two points
|
||||
// which is NOT following a lat/lon line on the globe
|
||||
titleFeature.coordinates.push([index, 52]);
|
||||
subtitleFeature.coordinates.push([index, 49]);
|
||||
}
|
||||
this.title.append("path")
|
||||
.attr("id", "titlePath")
|
||||
.attr("d", this.proj(titleFeature))
|
||||
;
|
||||
this.title.append("path")
|
||||
.attr("id", "subtitlePath")
|
||||
.attr("d", this.proj(subtitleFeature))
|
||||
;
|
||||
this.title.append("text")
|
||||
.html('<textPath xlink:href="#titlePath">Biometric</textPath>')
|
||||
this.title.append("text")
|
||||
.html('<textPath xlink:href="#titlePath">Mass Surveillance</textPath>')
|
||||
this.title.append("text")
|
||||
.attr("id", "subtitle")
|
||||
.html('<textPath xlink:href="#subtitlePath">' + CONFIG.subtitle + '</textPath>')
|
||||
|
||||
// this.title.append('text')
|
||||
// .attr('class', 'title')
|
||||
// .attr('x', 1000)
|
||||
// .attr('y', 1000)
|
||||
// .text(CONFIG.title);
|
||||
// this.title.append('text')
|
||||
// .attr('class', 'subtitle')
|
||||
// .attr('x', 1000)
|
||||
// .attr('y', 1200)
|
||||
// .text(CONFIG.subtitle);
|
||||
|
||||
this.link = this.container.append("g")
|
||||
.attr('class', 'links')
|
||||
.selectAll(".link");
|
||||
|
@ -516,7 +585,8 @@ class NodeMap {
|
|||
.data(this.graph.links)
|
||||
.join(
|
||||
enter => {
|
||||
let group = enter.append("g").attr("class", "link");
|
||||
let group = enter.append("g")
|
||||
.attr("class", (l) => "link " + slugify(l.name));
|
||||
group.append("path")
|
||||
.attr("marker-end", "url(#arrowHead)")
|
||||
.attr('id', (d, i) => 'linkid_' + i)
|
||||
|
|
|
@ -9,11 +9,18 @@
|
|||
<body>
|
||||
|
||||
<div id='map'></div>
|
||||
<div id='alluvial'></div>
|
||||
<!-- <div id='alluvial'></div> -->
|
||||
|
||||
<div id="filters">
|
||||
<header>
|
||||
<h1>Biometric Mass Surveillance</h1>
|
||||
<p class='subtitle'>Connections in the European Union & beyond</p>
|
||||
|
||||
</div>
|
||||
|
||||
<aside id="filters">
|
||||
<h3>Legend</h3>
|
||||
</aside>
|
||||
|
||||
</header>
|
||||
|
||||
<script src="https://d3js.org/d3.v6.js"></script>
|
||||
|
||||
|
|
Loading…
Reference in a new issue