From 42472e9663af4d7a4c6a93d4c67ff7ae2850e936 Mon Sep 17 00:00:00 2001 From: Ruben van de Ven Date: Fri, 13 Aug 2021 11:20:44 +0200 Subject: [PATCH] styling, closable filters, icons follow scale sqrt --- www/graph.css | 112 +++++++++++++++++++++++++++++++--------- www/graph.js | 136 +++++++++++++++++++++++++++++++------------------ www/index.html | 4 +- 3 files changed, 178 insertions(+), 74 deletions(-) diff --git a/www/graph.css b/www/graph.css index c4cad0f..cfe9a63 100644 --- a/www/graph.css +++ b/www/graph.css @@ -7,12 +7,12 @@ :root { --color1: #9741f9; - --color2: #f3722c; - --color3: #f8961e; + --color2: #fd7159; + --color3: #4c9be6; /* --color4: #f9844a; */ - --color5: #f9c74f; - --color6: #90be6d; - --color7: #43aa8b; + --color5: #24C3B2; + --color6: #7bc748; + --color7: #e4a02b; --color8: #4d908e; --color9: #577590; --color10: #277da1; @@ -65,28 +65,36 @@ svg .links line, svg .links path { /* stroke: #f3722c; */ /* stroke: #9df32c; */ stroke: var(--link-color); - stroke-width: calc(10px / var(--zoom)); + stroke-width: calc(14px / var(--zoom)); fill: none; /* transition: stroke-width 1s; */ cursor: pointer; + stroke-linecap: round; +} + +svg .selectedNode .links line, svg .selectedNode .links path { + opacity: .3; } svg .links line.hover, svg .links path.hover { stroke: var(--link-hover-color) !important; + opacity: 1; /* stroke-width: 12; */ - marker-end: url(#arrowHeadSelected) !important; + /* marker-end: url(#arrowHeadSelected) !important; */ } svg .links .linkedHover path{ stroke: var(--link-hover-related-color); + opacity: 1; stroke-width: calc(12px / var(--zoom)); - marker-end: url(#arrowHeadSelectedRelated); + /* marker-end: url(#arrowHeadSelectedRelated); */ } svg .links .linkedSelected path{ stroke: var(--link-hover-related-color); + opacity: 1; stroke-width: calc(12px / var(--zoom)); - marker-end: url(#arrowHeadSelectedRelated); + /* marker-end: url(#arrowHeadSelectedRelated); */ } /* svg.zoomed .links line, svg.zoomed .links path { @@ -165,7 +173,7 @@ svg #header text#subtitle { /* font-size: 16pt; */ /*Set this in JS*/ transition: opacity 1s; - fill: #5d5d5f; /*also when hovering node*/ + fill: black; /*also when hovering node*/ opacity: 1; /* pointer-events: none; */ /*prevent mouse glitches*/ @@ -178,6 +186,12 @@ svg #header text#subtitle { pointer-events: none; } +.selectedNode .node:not(.linkedSelected):not(.selected) text.nodeTitle{ + /* used to be shown on hover, but disabled now that we have a tooltip */ + opacity: 0; + pointer-events: none; +} + svg.zoomed .node text.nodeTitle { /* font-size: 6pt; */ } @@ -188,6 +202,12 @@ svg.zoomed.zoomed2 .node text.nodeTitle { .node circle, .node path { fill: lightgray; + transform: scale(calc(1.5 / var(--zoom-sqrt))); +} + + +#filters .node circle, #filters .node path { + transform: none; } /* Whenever a connected link is hovered */ @@ -197,6 +217,9 @@ svg.zoomed.zoomed2 .node text.nodeTitle { stroke: var(--hover-related-color); stroke-width: 2px; } +.node.linkHover text, .node.linkedHover text{ + fill: var(--hover-related-color) !important; +} .selectedNode .node:not(.linkedSelected) path { fill: lightgray !important; /* same as linkHover/linkedHover but without border */ @@ -211,6 +234,11 @@ svg.zoomed.zoomed2 .node text.nodeTitle { stroke: var(--hover-color); stroke-width: 2px; } + + +.node:hover text, .node.selected text { + fill: var(--hover-color) !important; +} /* .node:hover text { transition: none; @@ -254,7 +282,8 @@ svg.zoomed.zoomed2 .node text.nodeTitle { .node.Institution circle, .node.Institution path { /* fill: lightcoral; */ - fill: #11F999; + /* fill: #11F999; */ + fill: darkgray; } .node.Dataset circle, .node.Dataset path { @@ -266,26 +295,26 @@ svg.zoomed.zoomed2 .node text.nodeTitle { opacity: 1 !important; } -.node.Institution.Institution-company path{ - fill: #45F68A; +.node.Institution.Institution-company path, .node.Institution.Institution-company text{ + fill: var(--color2); } -.node.Institution.Institution-government path{ - fill: #60F37B; +.node.Institution.Institution-government path, .node.Institution.Institution-government text{ + fill: var(--color3); } .node.Institution.Institution-local-government path{ fill: #75F06D; } -.node.Institution.Institution-law-enforcement path{ - fill: #87EC60; +.node.Institution.Institution-law-enforcement path, .node.Institution.Institution-law-enforcement text{ + fill: var(--color7); } -.node.Institution.Institution-ngo path{ - fill: #97E853; +.node.Institution.Institution-ngo path, .node.Institution.Institution-ngo text{ + fill: var(--color5); } .node.Institution.Institution-university path{ fill: #A6E447; } -.node.Institution.Institution-research path{ - fill: #B4E03C; +.node.Institution.Institution-research path, .node.Institution.Institution-research text{ + fill: var(--color6); } .node.Institution.Institution-project path{ fill: #C1DB31; @@ -419,6 +448,7 @@ header { background: white; padding: 10px; border-top-left-radius: 5px; + min-width: 300px; } h1 { @@ -443,7 +473,7 @@ p.subtitle { #filters label { cursor: pointer; display: block; - padding: 10px; + padding: 5px 10px; } #filters label svg { @@ -474,6 +504,43 @@ p.subtitle { /* box-shadow: inset 0 0 5px black; */ } +#filters .institution_types{ + margin-left: 30px; +} + +#filter-items.filter-institution .institution_types{ + /* display:none */ + color: darkgray; + pointer-events: none; +} + +#filter-items.filter-institution .institution_types span{ + text-decoration: line-through; +} + +#filters h3{ + cursor: pointer; +} +#filters h3::before{ + display: inline-block; + float: left; + content: '\fe3f'; /*⬆*/ + transform: rotate(180deg); + transition: transform .8s; +} +#filters.hide h3::before{ + transform: rotate(0deg); +} + +#filter-items{ + max-height: 1000px; + transition: max-height .8s; +} +#filters.hide #filter-items{ + max-height: 0px; +} + + #map .borders { stroke-width: 6px; stroke: rgb(221, 210, 210); @@ -489,4 +556,3 @@ p.subtitle { #alluvial .flow_label text { font-size: 30; } - diff --git a/www/graph.js b/www/graph.js index e025dd0..34bc7c1 100644 --- a/www/graph.js +++ b/www/graph.js @@ -6,7 +6,7 @@ const CONFIG = { 'nodeRadius': 5, 'nodeRepositionPadding': 12, 'baseUrl': 'https://www.securityvision.io/wiki/index.php/', - 'dataUrl': 'result2.json', + 'dataUrl': 'result3.json', 'preSimulate': false, // run simulation before starting, so we don't start with lines jumping around 'labels': { 'rotate': true, @@ -17,6 +17,16 @@ const CONFIG = { 'lonMax': 35, 'center': [11, 47], }, + "institution_map": { + "Local Government": "Government", + "Regional Government": "Government", + "International Organization": "Government", + "Watchdog": "Government", + // "NGO": "Organisation", + "Foundation": "NGO", + "University": "Research", + "Expert Group": "Research", + }, "filters": { "Institution": { "label": "Institution", @@ -29,23 +39,11 @@ const CONFIG = { // [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": { - // "label": "Institution", - "type": "institution_types", - }, - "NGO": { - // "label": "Institution", - "type": "institution_types", - }, "Government": { // "label": "Institution", "type": "institution_types", }, - "Regional Government": { - // "label": "Institution", - "type": "institution_types", - }, - "Local Government": { + "Law Enforcement": { // "label": "Institution", "type": "institution_types", }, @@ -53,38 +51,51 @@ const CONFIG = { // "label": "Institution", "type": "institution_types", }, - "Foundation": { - // "label": "Institution", - "type": "institution_types", - }, - "University": { + "NGO": { // "label": "Institution", "type": "institution_types", }, + + // "Regional Government": { + // // "label": "Institution", + // "type": "institution_types", + // }, + // "Local Government": { + // // "label": "Institution", + // "type": "institution_types", + // }, + // "Foundation": { + // // "label": "Institution", + // "type": "institution_types", + // }, + // "University": { + // // "label": "Institution", + // "type": "institution_types", + // }, "Research": { // "label": "Institution", "type": "institution_types", }, - "Labour Union": { - // "label": "Institution", - "type": "institution_types", - }, - "Watchdog": { - // "label": "Institution", - "type": "institution_types", - }, - "Expert Group": { - // "label": "Institution", - "type": "institution_types", - }, - "International Organization": { - // "label": "Institution", - "type": "institution_types", - }, - "Art Project": { - // "label": "Institution", - "type": "institution_types", - }, + // "Labour Union": { + // // "label": "Institution", + // "type": "institution_types", + // }, + // "Watchdog": { + // // "label": "Institution", + // "type": "institution_types", + // }, + // "Expert Group": { + // // "label": "Institution", + // "type": "institution_types", + // }, + // "International Organization": { + // // "label": "Institution", + // "type": "institution_types", + // }, + // "Art Project": { + // // "label": "Institution", + // "type": "institution_types", + // }, "Deployments": { "label": "Deployment", "type": "categories", @@ -173,7 +184,7 @@ const CONFIG = { }, "zoom": { "scale_min": .2, - "scale_max": 20, + "scale_max": 10, }, "cases": [ @@ -332,6 +343,7 @@ function getCategories(obj) { cats.push(getInstitutionClass(type.fulltext)); }); } + // return return cats; } function getInstitutionClass(name) { @@ -374,8 +386,16 @@ class NodeMap { this.render(); } + // calculate which text labels overlap. calculateLabels() { - const els = document.querySelectorAll('.node text') + let els; + if(this.selectedNode === null) { + els = document.querySelectorAll('.node text') + } else { + // only related texts are visible + // so only these need to be considered + els = document.querySelectorAll('.node.linkedSelected text, .node.selected text') + } for (let i = 0; i < els.length; i++) { const el = els[i]; let overlapping = false; @@ -507,6 +527,7 @@ class NodeMap { clearTimeout(zoomTimeout) } document.querySelector(':root').style.setProperty('--zoom', evt.transform.k); + document.querySelector(':root').style.setProperty('--zoom-sqrt', Math.pow(evt.transform.k, 1/3)); zoomTimeout = setTimeout(() => { this.g_nodes.attr('style', `font-size:${22000 / this.height / evt.transform.k}pt`) setTimeout(() => { @@ -846,6 +867,7 @@ class NodeMap { // see also: https://www.createwithdata.com/enter-exit-with-d3-join/ this.node = this.node.data(this.graph.nodes, d => d.id) .join((enter) => { + // let group = enter.insert("g", ":first-child") let group = enter.append("g") .attr("class", getClasses) .attr("id", (n) => getIdForTitle(n.fulltext)); @@ -980,7 +1002,7 @@ class NodeMap { .attr("class", (l) => "link " + slugify(l.name)) .attr("id", getLinkId); group.append("path") - .attr("marker-end", "url(#arrowHead)") + // .attr("marker-end", "url(#arrowHead)") .attr('id', (d, i) => 'linkpath_' + i) .on("mouseover", (ev, link) => { d3.select(ev.target).classed('hover', true); @@ -1166,6 +1188,7 @@ class NodeMap { } } + // zoom & translate the graph to fit the provided nodes zoomFit(nodes, paddingPercent = 0.8, transitionDuration = 2000) { // var bounds = root.node().getBBox(); const x0 = Math.min(...nodes.map(n => n.x - CONFIG.nodeRadius)); @@ -1363,6 +1386,17 @@ JsonToGraph = function (data) { i++; let node = data.results[node_id]; node.id = getIdForTitle(node.fulltext); //node_id; + // group institution types + if(node.printouts['Institution Type'].length ){ + for (let idx = 0; idx < node.printouts['Institution Type'].length; idx++) { + const type = node.printouts['Institution Type'][idx]; + if(CONFIG.institution_map.hasOwnProperty(type.fulltext)){ + node.printouts['Institution Type'][idx].fulltext = CONFIG.institution_map[type.fulltext]; + } + } + // node.printouts['Institution Type'] = CONFIG.institution_map[node.printouts['Institution Type']]; + } + nodes.push(node); // console.log(node_id, node); @@ -1636,8 +1670,6 @@ class Store { 'institution_types': [], } - - this.filter(); } @@ -1747,15 +1779,17 @@ class Store { Object.keys(CONFIG.filters).forEach(f => { const settings = CONFIG.filters[f]; - if (settings.type == 'institution_types') - // TODO; For now, skip - return; + // if (settings.type == 'institution_types') + // // TODO; For now, skip + // return; let categories = [f]; if (settings.type == 'institution_types') categories = ['Institution', getInstitutionClass(f)]; - let labelEl = document.createElement('label') + let labelEl = document.createElement('label'); + labelEl.setAttribute('id', 'filter-'+slugify(f)); + labelEl.classList.add(settings.type) let inputEl = document.createElement('input') let textEl = document.createElement('span'); let svg = d3.select(labelEl).append('svg') @@ -1765,7 +1799,7 @@ class Store { .append('path') .attr('d', getSymbolForCategories(categories)()); inputEl.type = "checkbox"; - textEl.innerText = f; + textEl.innerText = settings.hasOwnProperty('label') ? settings.label : f; labelEl.appendChild(inputEl); labelEl.appendChild(textEl); @@ -1780,10 +1814,12 @@ class Store { this.filters[settings.type].splice(i, 1); } }); + this.root.classList.remove('filter-'+slugify(f)) } else { if (!this.filters[settings.type].includes(f)) { this.filters[settings.type].push(f); } + this.root.classList.add('filter-'+slugify(f)) } this.filter(); this.update(); @@ -1808,7 +1844,7 @@ Promise.all([fetch(req_data), fetch(req_world)]) }) .then(([data, world]) => { var graph = JsonToGraph(data); - var store = new Store(graph, '#filters'); + var store = new Store(graph, '#filter-items'); mapGraph.setWorld(world); mapGraph.setStore(store); diff --git a/www/index.html b/www/index.html index c5a2db1..832dac8 100644 --- a/www/index.html +++ b/www/index.html @@ -9,6 +9,7 @@
+
@@ -18,7 +19,8 @@