diff --git a/index.html b/index.html
index d6993f9..07f1d76 100644
--- a/index.html
+++ b/index.html
@@ -41,6 +41,7 @@
+
@@ -54,9 +55,9 @@
diff --git a/src/js/portfolio.js b/src/js/portfolio.js
index fdb7c19..7c95a20 100644
--- a/src/js/portfolio.js
+++ b/src/js/portfolio.js
@@ -21,6 +21,12 @@ function getTitleAttribute(node) {
function getNodeTitle(node){
return node[getTitleAttribute(node)];
}
+function getNodeYear(n){
+ if(typeof n['dateCreated'] !== 'undefined') {
+ return n['dateCreated'].substr(0,4);
+ }
+ return null;
+}
/**
Transform a flattened jsonld into a d3 compatible graph
@param Object data flattened jsonld data
@@ -81,9 +87,7 @@ function jsonLdToGraph(data){
var graph;
// map nodes to their ID
var nodeMap = {};
-// TODO: map node IDs to their linked node IDs
var linkMap = {};
-// TODO: use linkMap to create breadcrumbs per node
var breadcrumbs = {};
// load the flattened jsonld file
const requestPromise = fetch('/assets/js/rubenvandeven.jsonld')
@@ -133,20 +137,32 @@ function createLinkMap(graph) {
// TODO: make sure, 'shortest' path is favoured.
function createBreadcrumbs(linkMap, srcId) {
let crumbs = {};
- let path = [];
- let collectLinks = function(srcId, path){
- if(typeof crumbs[srcId] !== 'undefined') {
- return;
- }
- crumbs[srcId] = path.slice();
- path[path.length] = srcId;
- let links = linkMap[srcId];
- // collect links, append given list & skip srcId
- for(let link of links) {
- collectLinks(link['id'], path.slice());
+
+ let createBreadcrumbLayer = function(srcId) {
+ let path = crumbs[srcId];
+ let newPath = path.slice();
+ newPath.push(srcId);
+
+ let nextSrcIds = [];
+
+ for (let link of linkMap[srcId]) {
+ if(typeof crumbs[link['id']] !== 'undefined') continue;
+ crumbs[link['id']] = newPath;
+ nextSrcIds.push(link['id']);
}
+
+ return nextSrcIds;
+ }
+ crumbs[srcId] = [];
+ let nextIds = [srcId];
+ while(nextIds.length > 0) {
+ let newNextIds = [];
+ for (let nextId of nextIds) {
+ let r = createBreadcrumbLayer(nextId);
+ newNextIds = newNextIds.concat(r);
+ }
+ nextIds = newNextIds;
}
- collectLinks(srcId, path);
return crumbs;
}
@@ -172,25 +188,48 @@ for (let nodeIdx in graph['nodes']) {
if(typeof types[type] == 'undefined') {
types[type] = [];
}
- types[type][types[type].length] = nodeIdx;
+ types[type].push(nodeIdx);
}
var graphControlsEl = document.getElementById('graphControls');
var typeLinksEl = document.getElementById('typeLinks');
+var showMoreTypeLinksEl = document.getElementById('showMoreTypeLinks');
var moreTypeLinksEl = document.getElementById('moreTypeLinks');
var relLinksEl = document.getElementById('relLinks');
+// sort types by count:
+var typeCounts = Object.keys(types).map(function(key) {
+ return [key, types[key].length];
+});
+typeCounts.sort(function(first, second) {
+ return second[1] - first[1];
+});
+
// make controls
let i = 0;
-for (let typeName in types) {
+for (let typeCountIdx in typeCounts) {
+ let typeName = typeCounts[typeCountIdx][0];
let typeLinkEl = document.createElement("li");
let typeLinkAEl = document.createElement("a");
let typeLinkCountEl = document.createElement("span");
- typeLinkCountEl.innerHTML = types[typeName].length;
+ typeLinkCountEl.innerHTML = typeCounts[typeCountIdx][1];
+ typeLinkCountEl.classList.add('typeCount');
typeLinkAEl.innerHTML = typeName;
typeLinkAEl.addEventListener('click', function(){
centerByType(typeName);
// positionNodesInCenter(types[typeName]);
});
+ typeLinkAEl.addEventListener('mouseover', function() {
+ let typeNodeEls = document.getElementsByClassName(typeName);
+ for(let typeNodeEl of typeNodeEls) {
+ typeNodeEl.classList.add('typeHighlight');
+ }
+ });
+ typeLinkAEl.addEventListener('mouseout', function() {
+ let typeNodeEls = document.getElementsByClassName(typeName);
+ for(let typeNodeEl of typeNodeEls) {
+ typeNodeEl.classList.remove('typeHighlight');
+ }
+ });
typeLinkEl.append(typeLinkAEl);
typeLinkEl.append(typeLinkCountEl);
(i < 5 ? typeLinksEl: moreTypeLinksEl).appendChild(typeLinkEl);
@@ -198,6 +237,19 @@ for (let typeName in types) {
// typeLinksEl.appendChild(typeLinkEl);
}
+showMoreTypeLinksEl.addEventListener('click', function () {
+ console.log('showMore');
+ document.body.classList.add('showMoreLinks');
+ var hideMoreTypeLinks = function(e) {
+ console.log('removeMore');
+ e.preventDefault();
+ e.stopPropagation();
+ document.body.removeEventListener('mouseup', hideMoreTypeLinks, true);
+ document.body.classList.remove('showMoreLinks');
+ }
+ document.body.addEventListener('mouseup', hideMoreTypeLinks, true);
+}, false)
+
// make svg
var svg = d3.select("svg"),
@@ -247,9 +299,7 @@ var getViewbox = function() {
return svg.attr("viewBox").split(" ").map(parseFloat);
}
var positionNodesInCenter = function(idxs) {
- let viewBox = getViewbox();
- let cx = viewBox[0] + viewBox[2]/2;
- let cy = viewBox[1] + viewBox[3]/2;
+ setViewboxForceCenter(); // sets forceCx & forceCy
if(typeof idxs == "object" && idxs !== null && idxs.length == 1) {
idxs = idxs[0];
@@ -279,8 +329,8 @@ var positionNodesInCenter = function(idxs) {
}
else{
nodePositions[idxs] = [
- cx,
- cy
+ forceCx,
+ forceCy
];
}
@@ -300,6 +350,7 @@ var positionNodesInCenter = function(idxs) {
}
var positionNodesInCircle = function(idxs, r) {
let viewBox = getViewbox();
+ setViewboxForceCenter(); // sets forceCx & forceCy
if(typeof r == 'undefined') {
if(idxs.length == 1) {
r = viewBox[2] / 6;
@@ -308,14 +359,14 @@ var positionNodesInCircle = function(idxs, r) {
}
}
currentNodePositionRadius = r;
- let cx = viewBox[0] + viewBox[2]/2;
- let cy = viewBox[1] + viewBox[3]/2;
+ let forceCx = viewBox[0] + viewBox[2]/2;
+ let forceCy = viewBox[1] + viewBox[3]/2;
let stepSize = 2*Math.PI / idxs.length;
for (var i = 0; i < idxs.length; i++) {
nodePositions[idxs[i]] = [
- cx + Math.sin(stepSize * i) * r,
- cy + Math.cos(stepSize * i) * r
+ forceCx + Math.sin(stepSize * i) * r,
+ forceCy + Math.cos(stepSize * i) * r
];
}
@@ -348,7 +399,11 @@ var createRelationshipEl = function(relNode, i) {
let el = document.createElement("dd");
el.classList.add('relLink');
let titleEl = document.createElement('a');
- titleEl.innerHTML = getNodeTitle(relNode);
+ titleEl.innerHTML = getNodeTitle(relNode)
+ let year = getNodeYear(relNode);
+ if(year !== null) {
+ titleEl.innerHTML += `${getNodeYear(relNode)}`;
+ }
titleEl.classList.add('nodeTitle');
titleEl.classList.add('nodeTitleNr'+i);
titleEl.addEventListener('click',function(e){
@@ -376,10 +431,39 @@ var setDetails = function(nodeDatum, nodeIdx) {
// TODO: replace relUp & relDown with linkMap
let relUp = [];
let relDown = [];
- let breadcrumbsEl = document.createElement('div');
+ let nodeDetailScalerEl = document.createElement('div');
+ // nodeDetailScalerEl.innerHTML = ``;
+ nodeDetailScalerEl.id = 'nodeDetailsScaler';
+ nodeDetailScalerEl.addEventListener('mousedown', function(e){
+ // console.log('go');
+ let drag = function(e) {
+ // 5px for padding
+ nodeDetailEl.style.width = (window.innerWidth - e.clientX + 5) +'px';
+ }
+ document.body.addEventListener('mousemove', drag);
+ document.body.addEventListener('mouseup', function(){
+ document.body.removeEventListener('mousemove', drag);
+ });
+ });
+ nodeDetails.appendChild(nodeDetailScalerEl);
+
+ let breadcrumbsEl = document.createElement('ul');
breadcrumbsEl.classList.add('breadcrumbs');
for(let crumbNodeId of breadcrumbs[nodeDatum['id']]) {
- breadcrumbsEl.innerHTML += ` ${getNodeTitle(nodeMap[crumbNodeId])}`;
+ let crumbWrapEl = document.createElement('li');
+ let crumbEl = document.createElement('span');
+ crumbEl.classList.add('crumb');
+ crumbEl.addEventListener('click', function(e){
+ let idx = graph.nodes.indexOf(nodeMap[crumbNodeId]);
+ selectNode(idx);
+ });
+ crumbEl.innerHTML = `${getNodeTitle(nodeMap[crumbNodeId])}`;
+ let nodeYear = getNodeYear(nodeMap[crumbNodeId]);
+ if(nodeYear !== null) {
+ crumbEl.innerHTML += `${nodeYear}`;
+ }
+ crumbWrapEl.appendChild(crumbEl);
+ breadcrumbsEl.appendChild(crumbWrapEl);
}
nodeDetailEl.appendChild(breadcrumbsEl);
@@ -398,10 +482,15 @@ var setDetails = function(nodeDatum, nodeIdx) {
let listEl = document.createElement("dl");
// listEl.innerHTML += `type${nodeDatum['type']}`;
+
+ let skipNodeAttributes = [
+ 'id','x','y','index','type','vy','vx','fx','fy','leftX','rightX'
+ ];
+ if(titleAttr !== 'contentUrl') {
+ skipNodeAttributes[skipNodeAttributes.length] = titleAttr;
+ }
for (let attr in nodeDatum) {
- if([
- 'id','x','y','index','type','vy','vx','fx','fy','leftX','rightX', titleAttr
- ].indexOf(attr) != -1) {
+ if(skipNodeAttributes.indexOf(attr) != -1) {
continue;
}
@@ -417,6 +506,7 @@ var setDetails = function(nodeDatum, nodeIdx) {
if(attr == 'url') {
listEl.innerHTML += `${attr}${nodeAttr[i]}`;
} else if(attr == 'contentUrl') {
+ console.log('test', attr);
listEl.innerHTML += `${attr}${nodeAttr[i]}`;
listEl.innerHTML += ``;
} else {
@@ -545,7 +635,7 @@ var selectNode = function(idx){
let posTrg = currentCrumbs.indexOf(d.target['id']);
if(posSrc > -1 && posTrg > -1 && Math.abs(posSrc - posTrg) == 1) {
linkEls[idx].classList.add('breadcrumbLink');
- // linkEls[idx].getElementsByTagName("line")[0].setAttribute("marker-end", "url(#arrowHeadSelected)");
+ linkEls[idx].getElementsByTagName("line")[0].setAttribute("marker-end", "url(#arrowHeadCrumbTrail)");
} else {
linkEls[idx].classList.remove('breadcrumbLink');
}
@@ -570,9 +660,18 @@ var deselectNode = function() {
closeDetails();
}
+var forceCx, forceCy;
+var setViewboxForceCenter = function() {
+ let viewBox = getViewbox();
+ forceCx = viewBox[0] + viewBox[2]/2;
+ forceCy = viewBox[1] + viewBox[3]/2;
+}
+setViewboxForceCenter(); // sets forceCx & forceCy
+
simulation.force('centerActive', function force(alpha) {
// let currentNode = node.selectAll('.detail');
// console.log(currentNode);
+ // console.log(forceCx, forceCy);
node.each(function(d, idx, nodes){
let n = d;
let k = alpha * 0.1;
@@ -585,12 +684,10 @@ simulation.force('centerActive', function force(alpha) {
return;
}
- let viewBox = getViewbox();
- let cx = viewBox[0] + viewBox[2]/2;
- let cy = viewBox[1] + viewBox[3]/2;
- let dx = n.x - cx;
- let dy = n.y - cy;
+
+ let dx = n.x - forceCx;
+ let dy = n.y - forceCy;
if(!inCircle(dx, dy, currentNodePositionRadius)) {
return;
}
@@ -656,9 +753,33 @@ node.append('circle')
;
node.append('text')
- .append("textPath")
- .attr( "xlink:href",function(d, idx){return '#nodePath'+idx;})
- .text(getNodeTitle);
+ .attr("class", "nodeType")
+ .text(function(n){
+ return n['type'];
+ })
+
+node.append('text')
+ .attr("class", "nodeYear")
+ .attr("y", "22")
+ .text(function(n){
+ return getNodeYear(n);
+ })
+ ;
+node.append('text')
+ .attr("class", "nodeTitle")
+ .attr("y", "5")
+ // .append("textPath")
+ // .attr( "xlink:href",function(d, idx){return '#nodePath'+idx;})
+ .text(getNodeTitle)
+ .each(function(){
+ let self = d3.select(this),
+ textLength = self.node().getComputedTextLength()
+ ;
+ if(textLength > nodeSize * 2) {
+ self.attr('transform', `scale(${(nodeSize * 2) / textLength})`);
+ }
+ })
+ ;
node.each(function(d) {
if(!d.contentUrl) {
@@ -685,12 +806,12 @@ simulation.force("link")
.links(graph.links)
.distance(function(l){
switch (l.name) {
- case 'publishedAt':
- return 400;
- case 'image':
- return 100;
+ // case 'publishedAt':
+ // return 200;
+ // case 'image':
+ // return 200;
default:
- return 200;
+ return 300;
}
}) // distance between the nodes / link length
// .charge(-100)
diff --git a/src/scss/portfolio.scss b/src/scss/portfolio.scss
index 5450e69..914665a 100644
--- a/src/scss/portfolio.scss
+++ b/src/scss/portfolio.scss
@@ -1,6 +1,6 @@
$detailsPadding: 20px;
-$detailsWidth: 700px;
-$detailSlide: -1 * ($detailsWidth + 2 * $detailsPadding);
+$detailsWidth: 740px;
+$detailSlide: -1 * ($detailsWidth);
$detailSlideMobile: -30vh;
@@ -64,17 +64,41 @@ g.node{
stroke-dasharray: 3 2;
}
- &:hover .highlightCircle{
- stroke-width: 1px;
- stroke: yellow;
+ &.typeHighlight {
+ .highlightCircle{
+ stroke-width: 1px;
+ stroke: yellow;
+ }
+ }
+
+ &:hover{
+ .highlightCircle{
+ stroke-width: 1px;
+ stroke: yellow;
+ }
+ .nodeBg{
+ fill: yellow;
+ }
}
&.drag{
cursor: grabbing;
}
- text{
- text-anchor: start;
+ text.nodeType{
+ text-anchor: middle;
+ font-size: 10pt;
+ display:none;
+ }
+
+ text.nodeYear{
+ transition: transform .5s;
+ text-anchor: middle;
+ font-size: 8pt;
+ }
+ text.nodeTitle{
+ text-anchor: middle;
+ // text-anchor: start;
font-family: "CMU Bright", sans-serif;
font-size: 10pt;
}
@@ -183,6 +207,24 @@ text{
transition: opacity 1s, right 1s;
height: 100%;
overflow-y: auto;
+ box-sizing: border-box; // Because of the scaler: we can just set width now
+
+ #nodeDetailsScaler{
+ position:absolute;
+ top:0;
+ left:0;
+ bottom:0;
+ width: 20px;
+ cursor: col-resize;
+ // background: #ccc;
+ padding: 5px;
+ #scalarbar{
+ height:100%;
+ border-right:solid 1px black;
+ border-left:solid 1px #333;
+ width:0;
+ }
+ }
.nodeTitle{
@@ -199,10 +241,55 @@ text{
color:blue;
}
}
+
+ ul.breadcrumbs{
+ list-style: none;
+ margin:0;
+ padding: 0;
+ li{
+ display:inline-block;
+ &:not(:first-child)::before{
+ content: "::";
+ color: black;
+ text-decoration: none;
+ margin: 0 10px;
+ }
+ }
+
+ .crumb{
+ cursor: pointer;
+ color: blue;
+ &:hover{
+ text-decoration: underline;
+ }
+ }
+ }
+
+ span.nodeYear{
+ margin-left:15px;
+
+ &::before{
+ content:'(';
+ }
+ &::after{
+ content:')';
+ }
+ }
+
+ h4{
+ border-top: solid 1px black;
+ padding-top: 40px;
+ font-size: 120%;
+ }
+
dt{
float:left;
width: 120px;
font-weight:bold;
+ min-height:25px;
+ }
+ dd{
+ min-height:25px;
}
dd:not(.nodeTitleNr1) {
margin-left: 130px;
@@ -244,21 +331,67 @@ svg#portfolioGraph {
height: auto;
background: white;
padding: 10px;
+
+ .typeCount::before{
+ content: "(";
+ padding-left: 5px;
+ }
+ .typeCount::after{
+ content: ")";
+ }
+
+ ul#typeLinks{
+ margin:0;
+ padding: 0;
+ display:inline-block;
+
+ li{
+ list-style:none;
+ display: inline-block;
+ margin: 10px 10px;
+ cursor: pointer;
+ }
+
+ }
+
+ #showMoreTypeLinks{
+ display:inline-block;
+ width:20px;
+ text-align: right;
+ cursor: pointer;
+ &::before{
+ content:"...";
+ }
+ &:hover{
+ text-decoration:unline;
+ }
+ .showMoreLinks & {
+ pointer-events:none;
+ &::before{
+ content:"x";
+ }
+ }
+ }
+
+ #moreTypeLinks {
+ position: absolute;
+ right: 0;
+ background: white;
+ list-style: none;
+ padding: 20px 30px;
+ text-align: left;
+ margin: 0;
+ display:none;
+ .showMoreLinks & {
+ display: block;
+ }
+ }
+
+ .typeJump{
+ font-weight:bold;
+ }
}
-#graphControls .typeJump{
- font-weight:bold;
-}
-#graphControls ul{
- margin:0;
- padding: 0;
- display:inline-block;
-}
-#graphControls li{
- list-style:none;
- display: inline-block;
- margin: 10px 10px;
- cursor: pointer;
-}
+
@media (max-width: 1000px) {
body{