Show tooltip and select moves into view

This commit is contained in:
Ruben van de Ven 2021-04-29 15:36:27 +02:00
parent 4dec76a325
commit 07503883d3
3 changed files with 362 additions and 64 deletions

View file

@ -17,9 +17,9 @@
--color9: #577590; --color9: #577590;
--color10: #277da1; --color10: #277da1;
--hover-color: var(--color1); --hover-color: var(--color1);
--hover-related-color: #d1bce9;
/* --hover-color: var(darkblue); */ /* --hover-color: var(darkblue); */
--selected-color: var(--color1); --selected-color: var(--color1);
--selected-color: var(--color1);
} }
body { body {
@ -41,16 +41,20 @@ svg.dragging {
} }
#arrowHead { #arrowHead {
fill: #9df32c; fill: #b0e99a;
} }
#arrowHeadSelected { #arrowHeadSelected {
fill: var(--hover-color);; fill: var(--hover-color);;
} }
#arrowHeadSelectedRelated {
fill: var(--hover-related-color);;
}
svg .links line, svg .links path { svg .links line, svg .links path {
stroke: #f3722c; /* stroke: #f3722c; */
stroke: #9df32c; /* stroke: #9df32c; */
stroke: #b0e99a;
stroke-width: 6; stroke-width: 6;
fill: none; fill: none;
transition: stroke-width 1s; transition: stroke-width 1s;
@ -58,6 +62,18 @@ svg .links line, svg .links path {
} }
svg .links line.hover, svg .links path.hover { svg .links line.hover, svg .links path.hover {
stroke: var(--hover-color);
/* stroke-width: 12; */
marker-end: url(#arrowHeadSelected);
}
svg .links .linkedHover path{
stroke: var(--hover-related-color);
stroke-width: 12;
marker-end: url(#arrowHeadSelectedRelated);
}
svg .links .linkedSelected path{
stroke: var(--hover-color); stroke: var(--hover-color);
stroke-width: 12; stroke-width: 12;
marker-end: url(#arrowHeadSelected); marker-end: url(#arrowHeadSelected);
@ -67,10 +83,9 @@ 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: 2;
stroke-width: 4; stroke-width: 4;
} } */
svg .title { svg .title {
font-size: 200; font-size: 200;
@ -88,7 +103,7 @@ svg #countries .country.eu_country {
fill: black; fill: black;
} }
svg #header #titlePath, svg #header #subtitlePath { svg #header #titlePath, svg #header #title2Path, svg #header #subtitlePath {
stroke: none; stroke: none;
fill: none; fill: none;
} }
@ -104,8 +119,8 @@ svg #header text {
} }
svg #header text:nth-of-type(2) { svg #header text:nth-of-type(2) {
dominant-baseline: hanging; /* dominant-baseline: hanging; */
transform: translate(10px, 25px); /* transform: translate(10px, 25px); */
} }
svg #header text#subtitle { svg #header text#subtitle {
@ -131,13 +146,15 @@ svg #header text#subtitle {
/* font-size: 16pt; */ /* font-size: 16pt; */
/*Set this in JS*/ /*Set this in JS*/
transition: font-size .4s, opacity 1s; transition: font-size .4s, opacity 1s;
fill: white; fill: white; /*also when hovering node*/
opacity: 1; opacity: 1;
pointer-events: none; pointer-events: none;
/*prevent mouse glitches*/ /*prevent mouse glitches*/
} }
.node:not(:hover):not(.linkHover) text.nodeTitle.overlapping { /* .node:not(:hover):not(.linkHover) text.nodeTitle.overlapping { */
.node text.nodeTitle.overlapping {
/* used to be shown on hover, but disabled now that we have a tooltip */
opacity: 0; opacity: 0;
} }
@ -155,26 +172,30 @@ svg.zoomed.zoomed2 .node text.nodeTitle {
/* Whenever a connected link is hovered */ /* Whenever a connected link is hovered */
.node.linkHover circle, .node.linkHover path, label:hover .node path { .node.linkHover circle, .node.linkHover path, .node.linkedHover path, label:hover .node path {
fill: var(--hover-color) !important; fill: var(--hover-related-color) !important;
stroke: var(--hover-color); stroke: var(--hover-related-color);
stroke-width: 5px; stroke-width: 5px;
} }
.node.linkedSelected path {
fill: var(--hover-related-color) !important;
/* same as linkHover/linkedHover but without border */
}
.node.linkHover text.nodeTitle.overlapping { .node.linkHover text.nodeTitle.overlapping {
transition: opacity 0s; transition: opacity 0s;
} }
.node:hover circle, .node:hover 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: 5px;
} }
/*
.node:hover text { .node:hover text {
transition: none; transition: none;
fill: var(--hover-color); fill: var(--hover-color);
} } */
.node.selected circle, .node.selected path { .node.selected circle, .node.selected path {
fill: var(--selected-color) !important; fill: var(--selected-color) !important;
@ -248,6 +269,7 @@ svg.zoomed.zoomed2 .node text.nodeTitle {
display: none; display: none;
} }
#nodeInfo h2 { #nodeInfo h2 {
margin: 0; margin: 0;
padding: 0; padding: 0;
@ -258,6 +280,45 @@ svg.zoomed.zoomed2 .node text.nodeTitle {
height: calc(100vh - 40px - 20px - 30px); height: calc(100vh - 40px - 20px - 30px);
} }
#tooltip{
position:absolute;
z-index: 100;
opacity: 1;
transition: opacity .3s;
background:white;
padding: 20px 10px;
border-radius: 5px;
box-shadow: 2px 2px 5px rgba(0, 0, 0, .5);
}
#tooltip:not(.visible){
position:absolute;
z-index: 100;
opacity:0;
pointer-events: none;
}
#tooltip h3{
margin: 5px 0;
text-align: center;;
}
#tooltip .category{
display: block;
color: black;
text-align: center;;
}
#tooltip .category::before{
content:'· '
}
#tooltip .category::after{
content:' ·'
}
#tooltip .clickForMore{
display: block;
color: gray;
text-align: center;;
}
#closeInfo { #closeInfo {
cursor: pointer; cursor: pointer;
position: absolute; position: absolute;
@ -283,6 +344,7 @@ header {
right: 0; right: 0;
background: white; background: white;
padding: 10px; padding: 10px;
border-top-left-radius: 5px;
} }
h1 { h1 {
@ -353,3 +415,30 @@ p.subtitle {
#alluvial .flow_label text { #alluvial .flow_label text {
font-size: 30; font-size: 30;
} }
body.light{
background:white;
}
body.light #map .borders{
stroke: white;
}
body.light svg #countries .country{
fill:white;
stroke:lightgray;
stroke-width: 5;;
}
body.light svg #countries .country.eu_country{
fill:rgb(235, 226, 236);
}
body.light .node text.nodeTitle {
fill:black;
}
body.light #arrowHead{
fill:#577590;
}
body.light svg .links line, body.light svg .links path {
stroke:#577590;
}

View file

@ -4,7 +4,7 @@ const CONFIG = {
'subtitle': "Connections in the European Union & beyond", 'subtitle': "Connections in the European Union & beyond",
// 'nodeSize': 8, // 'nodeSize': 8,
'nodeRadius': 5, 'nodeRadius': 5,
'nodeRepositionPadding': 8, 'nodeRepositionPadding': 10,
'baseUrl': 'https://www.securityvision.io/wiki/index.php/', 'baseUrl': 'https://www.securityvision.io/wiki/index.php/',
'dataUrl': 'result.json', 'dataUrl': 'result.json',
'preSimulate': false, // run simulation before starting, so we don't start with lines jumping around 'preSimulate': false, // run simulation before starting, so we don't start with lines jumping around
@ -51,7 +51,16 @@ const CONFIG = {
"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"],
"Funding": ["Funded by"], "Funding": ["Funded by"],
} },
"zoom": {
"scale_min": .2,
"scale_max": 20,
},
"cases": [
"Data-lab Burglary-Free Neighbourhood",
"Dragonfly Project",
]
}; };
// let width = window.innerWidth; // let width = window.innerWidth;
@ -59,7 +68,7 @@ const CONFIG = {
function getSymbolForCategories(classes) { function getSymbolForCategories(classes) {
if(!Array.isArray(classes)) { if (!Array.isArray(classes)) {
classes = [classes]; classes = [classes];
} }
if (classes.includes('Institution')) { if (classes.includes('Institution')) {
@ -194,12 +203,19 @@ function getClasses(obj) {
return 'node ' + classes.join(' '); return 'node ' + classes.join(' ');
} }
function getLinkId(link) {
return "link_" + link.nr;
// return "link-" + link.source.id + '-' + link.target.id + '-' + slugify(link.name);
}
class NodeMap { class NodeMap {
constructor(parent) { constructor(parent) {
this.root = d3.select(parent); this.root = d3.select(parent);
this.resizeEvent = window.addEventListener('resize', this.resize.bind(this)); this.resizeEvent = window.addEventListener('resize', this.resize.bind(this));
this.tooltipEl = document.getElementById('tooltip');
this.selectedNode = null;
} }
resize() { resize() {
@ -247,7 +263,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> 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>
<!--Sketching:--> <!--Sketching:-->
<defs> <defs>
<filter id="tint"> <filter id="tint">
@ -315,34 +331,40 @@ class NodeMap {
.attr("d", this.proj(this.borders)) .attr("d", this.proj(this.borders))
let zoomTimeout = null; let zoomTimeout = null;
const zoom = d3.zoom().scaleExtent([0.2, 10]).on("start", () => { this.zoom = d3.zoom()
this.svg.node().classList.add("dragging"); .scaleExtent([CONFIG.zoom.scale_min, CONFIG.zoom.scale_max])
}).on("end", () => { .on("start", () => {
this.svg.node().classList.remove("dragging"); this.svg.node().classList.add("dragging");
}).on("zoom", ({ transform }) => { }).on("end", () => {
this.container.attr("transform", transform); this.svg.node().classList.remove("dragging");
const oldZoom = this.svg.classed('zoomed'); }).on("zoom", (evt) => {
const newZoom = transform.k > 2.0; this.container.attr("transform", evt.transform);
if (zoomTimeout) { const oldZoom = this.svg.classed('zoomed');
clearTimeout(zoomTimeout) const newZoom = evt.transform.k > 2.0;
} if (zoomTimeout) {
zoomTimeout = setTimeout(() => { clearTimeout(zoomTimeout)
this.g_nodes.attr('style', `font-size:${22000 / this.height / transform.k}pt`) }
setTimeout(() => { zoomTimeout = setTimeout(() => {
this.calculateLabels(); this.g_nodes.attr('style', `font-size:${22000 / this.height / evt.transform.k}pt`)
}, 500); setTimeout(() => {
}, 500); this.calculateLabels();
if (oldZoom != newZoom) { }, 500);
this.svg.classed('zoomed', newZoom); }, 250);
if (oldZoom != newZoom) {
this.svg.classed('zoomed', newZoom);
} }
}); });
this.title = this.container.append('g').attr('id', 'header'); this.title = this.container.append('g').attr('id', 'header');
const titleFeature = { const titleFeature = {
"type": "LineString", "type": "LineString",
"coordinates": [] "coordinates": []
}; };
const title2Feature = {
"type": "LineString",
"coordinates": []
};
const subtitleFeature = { const subtitleFeature = {
"type": "LineString", "type": "LineString",
"coordinates": [] "coordinates": []
@ -351,12 +373,17 @@ class NodeMap {
// projection apparently tries to find the shortest path between two points // projection apparently tries to find the shortest path between two points
// which is NOT following a lat/lon line on the globe // which is NOT following a lat/lon line on the globe
titleFeature.coordinates.push([index, 52]); titleFeature.coordinates.push([index, 52]);
title2Feature.coordinates.push([index, 50.5]);
subtitleFeature.coordinates.push([index, 49]); subtitleFeature.coordinates.push([index, 49]);
} }
this.title.append("path") this.title.append("path")
.attr("id", "titlePath") .attr("id", "titlePath")
.attr("d", this.proj(titleFeature)) .attr("d", this.proj(titleFeature))
; ;
this.title.append("path")
.attr("id", "title2Path")
.attr("d", this.proj(title2Feature))
;
this.title.append("path") this.title.append("path")
.attr("id", "subtitlePath") .attr("id", "subtitlePath")
.attr("d", this.proj(subtitleFeature)) .attr("d", this.proj(subtitleFeature))
@ -364,7 +391,7 @@ class NodeMap {
this.title.append("text") this.title.append("text")
.html('<textPath xlink:href="#titlePath">Biometric</textPath>') .html('<textPath xlink:href="#titlePath">Biometric</textPath>')
this.title.append("text") this.title.append("text")
.html('<textPath xlink:href="#titlePath">Mass Surveillance</textPath>') .html('<textPath xlink:href="#title2Path">Mass Surveillance</textPath>')
this.title.append("text") this.title.append("text")
.attr("id", "subtitle") .attr("id", "subtitle")
.html('<textPath xlink:href="#subtitlePath">' + CONFIG.subtitle + '</textPath>') .html('<textPath xlink:href="#subtitlePath">' + CONFIG.subtitle + '</textPath>')
@ -448,8 +475,8 @@ class NodeMap {
; ;
this.svg this.svg
.call(zoom) .call(this.zoom)
.call(zoom.transform, d3.zoomIdentity.scale(.5, .5)); .call(this.zoom.transform, d3.zoomIdentity.scale(.5, .5));
this.update(); this.update();
@ -457,6 +484,14 @@ class NodeMap {
setTimeout(() => this.calculateLabels(), 1000); setTimeout(() => this.calculateLabels(), 1000);
} }
resetZoom() {
this.deselectNode();
this.svg
.transition()
.duration(2000) // milliseconds
.call(this.zoom.transform, d3.zoomIdentity.scale(.5, .5));
}
getSizeForNode(node) { getSizeForNode(node) {
return this.nodeSize; return this.nodeSize;
} }
@ -532,17 +567,111 @@ class NodeMap {
console.log(`moved ${moved} nodes`); console.log(`moved ${moved} nodes`);
} }
showTooltip(el, node, links) {
// TODO: make links optional (otherwise collect links here)
this.tooltipEl.innerHTML = `
<span class='category'>${getCategories(node)[0]}</span>
<h3>${node.fulltext}</h3>
`;
if (links.length) {
const rels = links.length === 1 ? 'relationship' : 'relationships';
this.tooltipEl.innerHTML += `
<span class='clickForMore'>Click to examine ${links.length} ${rels}</span>
`;
}
const rect = el.getBoundingClientRect()
const rectTT = this.tooltipEl.getBoundingClientRect();
this.tooltipEl.style.top = (rect.top - rectTT.height) + 'px';
this.tooltipEl.style.left = (rect.left + rect.width / 2 - rectTT.width / 2) + 'px';
// console.log(el, node, rect.top);
this.tooltipEl.classList.add('visible');
}
hideTooltip() {
this.tooltipEl.classList.remove('visible');
}
selectNode(node) {
this.deselectNode(); // remove potential old selection
this.selectedNode = node;
let links = [];
let connectedNodes = [];
for (let link of this.graph.links) {
if (link.source == node || link.target == node) {
links.push(link);
const otherNode = node == link.target ? link.source : link.target;
connectedNodes.push(otherNode);
}
}
let allNodes = [...connectedNodes, node];
this.zoomFit(allNodes);
document.getElementById(node.id).classList.add('selected');
connectedNodes.forEach(n => document.getElementById(n.id).classList.add('linkedSelected'));
links.forEach(l => document.getElementById(getLinkId(l)).classList.add('linkedSelected'));
// TODO: show details;
// alert('not yet implemented');
}
deselectNode() {
this.selectedNode = null;
let nodeEls = document.getElementsByClassName('selected');
while (nodeEls.length) {
nodeEls[0].classList.remove('selected');
}
let els = document.getElementsByClassName('linkedSelected');
while (els.length) {
els[0].classList.remove('linkedSelected');
}
}
update() { update() {
console.log(this.graph) // console.log(this.graph)
this.resolveOverlaps(); this.resolveOverlaps();
// see also: https://www.createwithdata.com/enter-exit-with-d3-join/ // see also: https://www.createwithdata.com/enter-exit-with-d3-join/
this.node = this.node.data(this.graph.nodes, d => d.id) this.node = this.node.data(this.graph.nodes, d => d.id)
.join((enter) => { .join((enter) => {
let group = enter.append("g").attr("class", getClasses); let group = enter.append("g")
.attr("class", getClasses)
.attr("id", (n) => getIdForTitle(n.fulltext));
// group.call(drag(simulation)); // group.call(drag(simulation));
group.on("click", (evt, n) => selectNode(evt, n, node)); group.on("click", (evt, n) => this.selectNode(n));
group.on("mouseover", (evt, n) => {
// d3.select(this).classed('hover', true);
const links = document.getElementsByClassName('link');
const linkedLinks = [];
for (let link of links) {
const l = d3.select(link).datum();
if (n == l.target || n == l.source) {
link.classList.add('linkedHover');
// make sure it's the last element, so it's drawn on top
// link.parentNode.appendChild(link); .. causes gliches
// find related related node:
const otherNode = n == l.target ? l.source : l.target;
const otherNodeEl = document.getElementById(otherNode.id);
otherNodeEl.classList.add('linkedHover');
linkedLinks.push(l);
}
}
this.showTooltip(evt.target, n, linkedLinks);
});
group.on("mouseout", (evt, n) => {
this.hideTooltip();
const links = document.getElementsByClassName('linkedHover');
while (links.length) {
links[0].classList.remove('linkedHover');
}
});
// group.append('circle').attr("r", 5 /*this.nodeSize*/); // group.append('circle').attr("r", 5 /*this.nodeSize*/);
group.append('path') group.append('path')
.attr('d', (n) => { .attr('d', (n) => {
@ -637,10 +766,11 @@ class NodeMap {
.join( .join(
enter => { enter => {
let group = enter.append("g") let group = enter.append("g")
.attr("class", (l) => "link " + slugify(l.name)); .attr("class", (l) => "link " + slugify(l.name))
.attr("id", getLinkId);
group.append("path") group.append("path")
.attr("marker-end", "url(#arrowHead)") .attr("marker-end", "url(#arrowHead)")
.attr('id', (d, i) => 'linkid_' + i) .attr('id', (d, i) => 'linkpath_' + i)
.on("mouseover", function (ev, link) { .on("mouseover", function (ev, link) {
d3.select(this).classed('hover', true); d3.select(this).classed('hover', true);
const nodes = document.getElementsByClassName('node'); const nodes = document.getElementsByClassName('node');
@ -654,14 +784,17 @@ class NodeMap {
}).on("mouseout", function (ev, link) { }).on("mouseout", function (ev, link) {
d3.select(this).classed('hover', false); d3.select(this).classed('hover', false);
const nodes = document.getElementsByClassName('linkHover'); const nodes = document.getElementsByClassName('linkHover');
for (let n of nodes) { while (nodes.length) {
n.classList.remove('linkHover'); nodes[0].classList.remove('linkHover');
} }
// l.classed('hover',false); // l.classed('hover',false);
// l.target.classed('hover',false); // l.target.classed('hover',false);
// l.source.classed('hover',false); // l.source.classed('hover',false);
// console.log(l,'l'); // console.log(l,'l');
}); }).on("click", (ev, link) => {
this.selectNode(link.source);
})
;
group.filter((l) => l.name != "City").append("text").attr("class", "labelText").text(function (l) { group.filter((l) => l.name != "City").append("text").attr("class", "labelText").text(function (l) {
return l.name; return l.name;
}); });
@ -795,6 +928,60 @@ class NodeMap {
redraw() { redraw() {
this.update() this.update()
} }
// viewBox + preserveAspectRatio can lead to a visible area that is larger than
// the viewBox. Try to get this
getVisibleBox() {
const svgEl = this.svg.node()
const vbRatio = svgEl.viewBox.baseVal.width / svgEl.viewBox.baseVal.height;
const wRatio = this.width / this.height;
if (wRatio > vbRatio) {
// wider
return {
width: (wRatio / vbRatio) * svgEl.viewBox.baseVal.width,
height: svgEl.viewBox.baseVal.height
}
} else {
// taller
return {
width: svgEl.viewBox.baseVal.width,
height: (vbRatio / wRatio) * svgEl.viewBox.baseVal.height
}
}
}
zoomFit(nodes, paddingPercent = 0.8, transitionDuration = 2000) {
// var bounds = root.node().getBBox();
const x0 = Math.min(...nodes.map(n => n.x - CONFIG.nodeRadius));
const x1 = Math.max(...nodes.map(n => n.x + CONFIG.nodeRadius));
const y0 = Math.min(...nodes.map(n => n.y - CONFIG.nodeRadius));
const y1 = Math.max(...nodes.map(n => n.y + CONFIG.nodeRadius));
const width = x1 - x0;
const height = y1 - y0;
const visibleBox = this.getVisibleBox();
const fullWidth = visibleBox.width, //2000, //this.width, TODO: use viewbox now, but consider the overflowing of the vbox
fullHeight = visibleBox.height; //2000; //this.height;
const viewBox = this.svg.node().viewBox.baseVal;
const midX = x0 + width / 2,
midY = y0 + height / 2;
if (width == 0 || height == 0) return; // nothing to fit
let scale = paddingPercent / Math.max(width / fullWidth, height / fullHeight);
scale = Math.min(CONFIG.zoom.scale_max, scale);
const translate = [fullWidth / 2 - midX, fullHeight / 2 - midY];
const transform = d3.zoomIdentity.translate(
-midX * scale + viewBox.width / 2,
-midY * scale + viewBox.height / 2)
.scale(scale);
// console.log("zoomFit", x0, x1, y0, y1, translate, scale, transform);
this.svg
.transition()
.duration(transitionDuration || 0) // milliseconds
.call(this.zoom.transform, transform);
// .call(this.zoom.translateTo, midX, midY)
// .call(this.zoom.scaleTo, scale);
}
} }
class AlluvialMap { class AlluvialMap {
@ -838,7 +1025,7 @@ class AlluvialMap {
.nodeWidth(40) // height .nodeWidth(40) // height
.nodePadding(10) .nodePadding(10)
.extent([[1, 5], [2000 - 1, 2000 - 5]]); .extent([[1, 5], [2000 - 1, 2000 - 5]]);
console.log(this.graph); // console.log(this.graph);
let s = this.sankey({ let s = this.sankey({
nodes: this.graph.nodes.map(d => Object.assign({}, d)), nodes: this.graph.nodes.map(d => Object.assign({}, d)),
links: this.graph.links.map(d => Object.assign({}, d)) links: this.graph.links.map(d => Object.assign({}, d))
@ -938,16 +1125,27 @@ class AlluvialMap {
} }
titleIdMap = {};
function getIdForTitle(title) {
if (!titleIdMap.hasOwnProperty(title)) {
titleIdMap[title] = slugify(title) + `-${Object.keys(titleIdMap).length}`
}
return titleIdMap[title];
}
JsonToGraph = function (data) { JsonToGraph = function (data) {
let nodes = []; let nodes = [];
let links = []; let links = [];
let smwBugFixLocationMaps = {}; let smwBugFixLocationMaps = {};
console.log(data) console.log(data)
let i = 0;
let linkI = 0;
for (const node_id in data.results) { for (const node_id in data.results) {
if (Object.hasOwnProperty.call(data.results, node_id)) { if (Object.hasOwnProperty.call(data.results, node_id)) {
i++;
let node = data.results[node_id]; let node = data.results[node_id];
node.id = node.fulltext; //node_id; node.id = getIdForTitle(node.fulltext); //node_id;
nodes.push(node); nodes.push(node);
// console.log(node_id, node); // console.log(node_id, node);
@ -967,9 +1165,9 @@ JsonToGraph = function (data) {
} }
for (const target_node of node.printouts[prop]) { for (const target_node of node.printouts[prop]) {
links.push({ links.push({
"source": node_id, "source": node.id,
"target": target_node.fulltext, "target": getIdForTitle(target_node.fulltext),
"name": prop "name": prop,
}) })
} }
} }
@ -998,14 +1196,18 @@ JsonToGraph = function (data) {
console.debug(`Fixed location for ${fixes} nodes`); console.debug(`Fixed location for ${fixes} nodes`);
console.log(links.length);
nodeMap = Object.fromEntries(nodes.map(d => [d['id'], d])); nodeMap = Object.fromEntries(nodes.map(d => [d['id'], d]));
links = links.filter(l => nodeMap[l.source] && nodeMap[l.target]).map(l => { links = links.filter(l => nodeMap[l.source] && nodeMap[l.target]).map(l => {
l.source = nodeMap[l.source]; l.source = nodeMap[l.source];
l.target = nodeMap[l.target]; l.target = nodeMap[l.target];
l.nr = linkI++;
return l; return l;
}); }).filter((link, index, self) =>
// remove incidental duplicates
index === self.findIndex((l) => (
l.source.id === link.source.id && l.target.id === link.target.id && l.name === link.name
))
);
@ -1318,9 +1520,9 @@ class Store {
let inputEl = document.createElement('input') let inputEl = document.createElement('input')
let textEl = document.createElement('span'); let textEl = document.createElement('span');
let svg = d3.select(labelEl).append('svg') let svg = d3.select(labelEl).append('svg')
.attr("viewBox", [-12,-12,24,24]); .attr("viewBox", [-12, -12, 24, 24]);
svg.append('g') svg.append('g')
.attr("class", "node "+ f) .attr("class", "node " + f)
.append('path') .append('path')
.attr('d', getSymbolForCategories(f)()); .attr('d', getSymbolForCategories(f)());
inputEl.type = "checkbox"; inputEl.type = "checkbox";

View file

@ -8,6 +8,7 @@
<body> <body>
<div id='tooltip'></div>
<div id='map'></div> <div id='map'></div>
<!-- <div id='alluvial'></div> --> <!-- <div id='alluvial'></div> -->
@ -31,6 +32,12 @@
<script src="https://unpkg.com/d3-sankey@0"></script> <script src="https://unpkg.com/d3-sankey@0"></script>
<!-- <script src="//unpkg.com/d3fc@14.0.1"></script> --> <!-- <script src="//unpkg.com/d3fc@14.0.1"></script> -->
<script src="graph.js"></script> <script src="graph.js"></script>
<script>
if(window.location.hash == '#light') {
document.body.classList.add('light');
}
</script>
</body> </body>
</html> </html>