diff --git a/www/graph.css b/www/graph.css
index 2b06c50..44b1849 100644
--- a/www/graph.css
+++ b/www/graph.css
@@ -12,6 +12,7 @@
--color10: #277da1;
--hover-color: var(--color1);
+ /* --hover-color: var(darkblue); */
--selected-color: var(--color1);
--selected-color: var(--color1);
}
@@ -36,8 +37,18 @@ svg.dragging {
svg .links line,svg .links path{
stroke: #f3722c;
- stroke-width: 3;
+ stroke-width: 6;
fill:none;
+ transition: stroke-width 1s;
+ cursor: pointer;
+}
+
+svg .links line.hover, svg .links path.hover{
+ stroke:red;
+}
+
+svg.zoomed .links line, svg.zoomed .links path{
+ stroke-width: 2;
}
.links text{
@@ -47,8 +58,28 @@ svg .links line,svg .links path{
fill: whitesmoke;
}
-.node text{
- text-anchor: middle;
+.node{
+
+ cursor: pointer;
+}
+
+.node text.nodeTitle{
+ text-anchor: start;
+ dominant-baseline: hanging; /*achieves a 'text-anchor: top'*/
+ font-size:16pt;
+ transition: font-size .4s, opacity 1s;
+ fill: white;
+ opacity: 1;
+}
+.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;
}
@@ -56,14 +87,23 @@ svg .links line,svg .links path{
fill: white;
}
-.node:hover{
- cursor: pointer;
+/* Whenever a connected link is hovered */
+.node.linkHover circle{
+ stroke: var(--hover-color);
+ stroke-width: 5px;
+}
+.node.linkHover text.nodeTitle.overlapping{
+ transition: opacity 0s;
}
.node:hover circle{
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;
@@ -94,6 +134,13 @@ svg .links line,svg .links path{
fill: plum
}
+
+
+.labels .label text{
+ fill:yellow;
+ opacity: 1 !important;
+}
+
/* .node.Person circle {
fill: var(--color2)
}
@@ -160,3 +207,9 @@ a:hover{
background: white;
padding: 10px;
}
+
+#map .borders{
+ stroke-width: 6px;
+ stroke: white;
+ fill:none;
+}
\ No newline at end of file
diff --git a/www/graph.js b/www/graph.js
index 709e31b..c56ab1c 100644
--- a/www/graph.js
+++ b/www/graph.js
@@ -13,7 +13,7 @@ const CONFIG = {
'lonMax': 35,
'center': [11, 47],
},
- "filters": ["Institution", "Deployments", "Technology", "Dataset"],
+ "filters": ["Institution", "Deployments",/* "Technology", "Dataset"*/],
"link_properties": [
"Clients",
@@ -159,6 +159,32 @@ class NodeMap {
this.render();
}
+ calculateLabels() {
+ const els = document.querySelectorAll('.node text')
+ for (let i = 0; i < els.length; i++) {
+ const el = els[i];
+ let overlapping = false;
+ for (let index = 0; index < i; index++) {
+ const el2 = els[index];
+ const box1 = el.getBoundingClientRect()
+ const box2 = el2.getBoundingClientRect()
+ const overlap = !(box1.right < box2.left ||
+ box1.left > box2.right ||
+ box1.bottom < box2.top ||
+ box1.top > box2.bottom)
+ if (overlap) {
+ // TODO: try to flip labels horizontally to see if that helps
+ el.classList.add('overlapping');
+ overlapping = true;
+ break;
+ }
+ }
+ if(!overlapping)
+ el.classList.remove('overlapping');
+ }
+
+ }
+
render() {
this.svg = this.root.append('svg')
this.svg.append('defs').html('');
@@ -166,8 +192,8 @@ class NodeMap {
this.projection = d3.geoHill()
.rotate([-12, 0, 0])
- .translate([this.vbWidth / 2, this.vbHeight * 1.5])
- .scale(this.vbHeight * 1.5);
+ .translate([this.vbWidth, this.vbHeight * 3])
+ .scale(this.vbHeight * 3);
this.nodeSize = this.vbHeight / 200;
@@ -175,12 +201,12 @@ class NodeMap {
const graticule = d3.geoGraticule10();
const euCenter = this.projection(CONFIG.eu.center);
- const container = this.svg.append("g").attr("id", "container");
+ this.container = this.svg.append("g").attr("id", "container");
// container.append("circle").attr("cx", euCenter[0]).attr("cy", euCenter[1]).attr("r", 500).attr("fill","red")
- this.g_countries = container.append("g").attr("id", "countries");
- this.g_borders = container.append("g").attr("id", "borders");
- this.g_graticule = container.append("g").attr('id', 'graticule')
+ this.g_countries = this.container.append("g").attr("id", "countries");
+ this.g_borders = this.container.append("g").attr("id", "borders");
+ this.g_graticule = this.container.append("g").attr('id', 'graticule')
.append('path')
.attr("class", "graticule")
.attr("fill", "none")
@@ -209,26 +235,33 @@ class NodeMap {
.append("path")
.attr("class", "borders")
.attr("d", this.proj(this.borders))
- .attr("fill", "none")
- .attr("stroke-width", "2px")
- .attr("stroke", (n) => {
- return "white";
- });
- this.svg.call(d3.zoom().scaleExtent([0.3, 8]).on("start", () => {
+ const zoom = d3.zoom().scaleExtent([0.2, 10]).on("start", () => {
this.svg.node().classList.add("dragging");
}).on("end", () => {
this.svg.node().classList.remove("dragging");
}).on("zoom", ({ transform }) => {
- container.attr("transform", transform);
- }));
+ this.container.attr("transform", transform);
+ const oldZoom = this.svg.classed('zoomed');
+ const newZoom = transform.k > 2.0;
+ if (oldZoom != newZoom) {
+ this.svg.classed('zoomed', newZoom);
- this.node = container.append("g")
- .attr('class', 'nodes')
- .selectAll(".node");
- this.link = container.append("g")
+ setTimeout(() => {
+ this.calculateLabels();
+ }, 500);
+ }
+ });
+ this.svg
+ .call(zoom)
+ .call(zoom.transform, d3.zoomIdentity.scale(.5, .5));
+
+ this.link = this.container.append("g")
.attr('class', 'links')
.selectAll(".link");
+ this.node = this.container.append("g")
+ .attr('class', 'nodes')
+ .selectAll(".node");
this.graph.nodes.forEach((d) => {
@@ -282,6 +315,8 @@ class NodeMap {
;
this.update();
+
+ setTimeout(() => this.calculateLabels(), 1000);
}
getSizeForNode(node) {
@@ -296,10 +331,10 @@ class NodeMap {
let group = enter.append("g").attr("class", getClasses);
// group.call(drag(simulation));
group.on("click", (evt, n) => selectNode(evt, n, node));
- group.append('circle').attr("r", this.nodeSize);
- var nodeTitle = group.append('text').attr("class", "nodeTitle").attr("y", "5");
+ group.append('circle').attr("r", 5 /*this.nodeSize*/);
+ var nodeTitle = group.append('text').attr("class", "nodeTitle").attr("y", "3").attr('x', 5);
nodeTitle
- .each(function (node, nodes) {
+ .each(function (node, i, nodes) {
var textLength = void 0;
const self = d3.select(this);
const titleText = getTitle(node);
@@ -308,30 +343,108 @@ class NodeMap {
titleTexts = splitText(titleText);
}
if (titleTexts !== false) {
- const tspan1 = self.append("tspan").text(titleTexts[0]).attr("y", "-10").attr("x", "0");
- const tspan = self.append("tspan").text(titleTexts[1]).attr("y", "10").attr("x", "0");
- const textLength1 = tspan.node().getComputedTextLength();
- const textLength2 = tspan.node().getComputedTextLength();
- textLength = Math.max(textLength1, textLength2);
+ const tspan1 = self.append("tspan").text(titleTexts[0]).attr("y", "3").attr("x", "5");
+ const tspan = self.append("tspan").text(titleTexts[1]).attr("dy", "1em").attr("x", "5");
+ // const textLength1 = tspan.node().getComputedTextLength();
+ // const textLength2 = tspan.node().getComputedTextLength();
+ // textLength = Math.max(textLength1, textLength2);
} else {
self.text(titleText);
- textLength = self.node().getComputedTextLength();
- }
- // scale according to text length:
- if (textLength > this.nodeSize * 2) {
- self.attr('transform', 'scale(' + this.nodeSize * 2 / textLength / 1.05 + ')');
+ // textLength = self.node().getComputedTextLength();
}
});
return group;
});
+
+
+ // 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(
enter => {
- console.log(enter);
let group = enter.append("g").attr("class", "link");
- group.append("path").attr("marker-end", "url(#arrowHead)").attr('id', (d, i) => 'linkid_' + i);
+ group.append("path")
+ .attr("marker-end", "url(#arrowHead)")
+ .attr('id', (d, i) => 'linkid_' + i)
+ .on("mouseover", function (ev,link) {
+ d3.select(this).classed('hover',true);
+ const nodes = document.getElementsByClassName('node');
+ for(let n of nodes){
+ const d = d3.select(n).datum();
+ if(d == link.target || d == link.source){
+ n.classList.add('linkHover');
+ }
+ }
+ // console.log(l);
+ }).on("mouseout", function (ev, link) {
+ d3.select(this).classed('hover',false);
+ const nodes = document.getElementsByClassName('linkHover');
+ for(let n of nodes){
+ n.classList.remove('linkHover');
+ }
+ // l.classed('hover',false);
+ // l.target.classed('hover',false);
+ // l.source.classed('hover',false);
+ // console.log(l,'l');
+ });
group.filter((l) => l.name != "City").append("text").attr("class", "labelText").text(function (l) {
return l.name;
});
@@ -447,7 +560,7 @@ class NodeMap {
this.simulation.alpha = 0;
this.simulation.restart();
-
+ this.calculateLabels()
}
setWorld(world) {
@@ -526,7 +639,7 @@ JsonToGraph = function (data) {
}
}
})
-
+
console.debug(`Fixed location for ${fixes} nodes`);
console.log(links.length);
diff --git a/www/index.html b/www/index.html
index a2e7a7e..99ba437 100644
--- a/www/index.html
+++ b/www/index.html
@@ -15,14 +15,13 @@
-
-
+
-
-
-
+
+
+