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 @@
- Show: + Types: - ... +
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{