diff --git a/assets/js/portfolio.js b/assets/js/portfolio.js index ac3d236..a80a6fe 100644 --- a/assets/js/portfolio.js +++ b/assets/js/portfolio.js @@ -715,10 +715,10 @@ function startGraph(graph) { listEl.innerHTML += '
' + getDisplayAttr(attr) + '
' + nodeAttr[_i] + '
'; if (nodeDatum['@type'] == 'https://schema.org/VideoObject') { // console.log(nodeDatum, nodeAttr); - var videoType = nodeDatum['https://schema.org/encodingFormat'] ? 'type=\'' + nodeDatum['https://schema.org/encodingFormat'] + '\'' : ""; + // let videoType = nodeDatum['https://schema.org/encodingFormat'] ? `type='${nodeDatum['https://schema.org/encodingFormat']}'`: ""; + var videoType = ""; var poster = nodeDatum['https://schema.org/thumbnailUrl'] ? 'poster=\'' + nodeDatum['https://schema.org/thumbnailUrl'] + '\'' : ""; - // TODO: enable outplay and make it work (for some reason it does not...) - listEl.innerHTML += '
'; + listEl.innerHTML += '
'; } else { listEl.innerHTML += '
'; } diff --git a/assets/js/portfolio.min.js b/assets/js/portfolio.min.js index 3938d0d..e909dd4 100644 --- a/assets/js/portfolio.min.js +++ b/assets/js/portfolio.min.js @@ -1,2 +1,2 @@ -"use strict";var data,graph,_typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e};function getLabelAttribute(e){if(void 0!==e["https://schema.org/name"])return"https://schema.org/name";switch(e["@type"]){case"https://schema.org/WebSite":if(void 0!==e["https://schema.org/url"])return"https://schema.org/url";break;case"https://schema.org/ImageObject":if(void 0!==e["https://schema.org/caption"])return"https://schema.org/caption";if(void 0!==e["https://schema.org/contentUrl"])return"https://schema.org/contentUrl";break;case"https://schema.org/PostalAddress":if(void 0!==e["https://schema.org/addressLocality"])return"https://schema.org/addressLocality";break;case"https://schema.org/OrganizationRole":if(void 0!==e["https://schema.org/roleName"])return"https://schema.org/roleName"}return"@id"}function getNodeLabel(e){var t=e[getLabelAttribute(e)];return t=void 0===(t=void 0===t?e["@id"]:t)?"":t}function getNodeYear(e){return void 0!==e["https://schema.org/dateCreated"]?9==e["https://schema.org/dateCreated"].length?e["https://schema.org/dateCreated"]:e["https://schema.org/dateCreated"].substr(0,4):void 0!==e["https://schema.org/datePublished"]?e["https://schema.org/datePublished"].substr(0,4):void 0===e["https://schema.org/startDate"]?void 0!==e["https://schema.org/endDate"]?e["https://schema.org/endDate"].substr(0,4):void 0!==e["https://schema.org/foundingDate"]?e["https://schema.org/foundingDate"].substr(0,4):void 0!==e["https://schema.org/temporalCoverage"]&&e["https://schema.org/temporalCoverage"].match(/\d{4}-\d{4}/)?e["https://schema.org/temporalCoverage"].substr(5,4):null:e["https://schema.org/startDate"].substr(0,4)}function getDisplayAttr(e){return e.replace(/.*[#|\/]/,"")}function jsonLdToGraph(e){var t,n,r={},a=[];for(t in e)r[e[t]["@id"]]=e[t];for(n in e){var o,i=e[n],s=i["@id"];for(o in i){var d,c=Array.isArray(i[o])?i[o]:[i[o]];for(d in c)"@id"!==o&&"string"==typeof c[d]&&r[c[d]]?a[a.length]={source:s,target:c[d],name:o}:void 0!==c[d]["@id"]&&(1==Object.keys(c[d]).length&&void 0===r[c[d]["@id"]]||(a[a.length]={source:s,target:c[d]["@id"],name:o}))}}return{nodes:Object.values(r),links:a}}var nodeMap={},linkMap={},breadcrumbs={},weights={},requestPromise=fetch("/assets/js/rubenvandeven.jsonld").then(function(e){return e.json()}),rankingPromise=fetch("/assets/js/ranking.json").then(function(e){return e.json()});function inCircle(e,t,n){var r=Math.abs(e),a=Math.abs(t);return!(n"+getNodeYear(n)+""),r.classList.add("nodeTitle"),r.classList.add("nodeTitleNr"+e),r.addEventListener("click",function(e){var t=P.nodes.indexOf(n);U(t)}),(e=document.createElement("a")).classList.add("nodeType"),e.innerHTML=getDisplayAttr(n["@type"]),e.title=n["@type"],e.addEventListener("click",function(e){I(n["@type"])}),t.appendChild(r),t.appendChild(e),t}function h(t,r){for(document.body.classList.add("detailsOpen"),scrollToY(0,4e3);B.hasChildNodes();)B.removeChild(B.lastChild);var e=[],n=[],a=[],o=document.createElement("div");o.id="nodeDetailsScaler",o.addEventListener("mousedown",function(e){function t(e){B.style.width=window.innerWidth-e.clientX+5+"px"}document.body.addEventListener("mousemove",t),document.body.addEventListener("mouseup",function(){document.body.removeEventListener("mousemove",t)})}),nodeDetails.appendChild(o);var i=document.createElement("ul");i.classList.add("breadcrumbs");var s=!0,d=!1,c=void 0;try{for(var l,u=breadcrumbs[t["@id"]][Symbol.iterator]();!(s=(l=u.next()).done);s=!0)!function(n){var e=document.createElement("li"),t=document.createElement("span");t.classList.add("crumb"),t.addEventListener("click",function(e){var t=P.nodes.indexOf(nodeMap[n]);U(t)}),t.innerHTML=""+getNodeLabel(nodeMap[n]);var r=getNodeYear(nodeMap[n]);null!==r&&(t.innerHTML+=""+r+""),e.appendChild(t),i.appendChild(e),a.push(getNodeLabel(nodeMap[n]))}(l.value)}catch(e){d=!0,c=e}finally{try{!s&&u.return&&u.return()}finally{if(d)throw c}}B.appendChild(i),a.push(getNodeLabel(t)),o=getLabelAttribute(t),(d=document.createElement("h2")).innerHTML=getNodeLabel(t),(c=document.createElement("span")).classList.add("nodeType"),c.innerHTML=getDisplayAttr(t["@type"]),c.title=t["@type"],c.addEventListener("click",function(e){I(t["@type"])}),d.appendChild(c),B.appendChild(d);var h,m=document.createElement("dl"),p=["@id","x","y","index","@type","vy","vx","fx","fy","leftX","rightX"];for(h in"https://schema.org/contentUrl"!==o&&(p[p.length]=o),t)if(-1==p.indexOf(h)){var g,f,v,y=Array.isArray(t[h])?t[h]:[t[h]];for(g in y)"string"==typeof y[g]&&nodeMap[y[g]]||void 0===y[g]["@id"]&&("https://schema.org/url"==h||"http://www.w3.org/2000/01/rdf-schema#seeAlso"==h?m.innerHTML+="
"+getDisplayAttr(h)+"
"+y[g]+"
":"https://schema.org/embedUrl"==h?(m.innerHTML+="
"+getDisplayAttr(h)+"
"+y[g]+"
",m.innerHTML+="
"):"https://schema.org/contentUrl"==h?(m.innerHTML+="
"+getDisplayAttr(h)+"
"+y[g]+"
","https://schema.org/VideoObject"==t["@type"]?(v=t["https://schema.org/encodingFormat"]?"type='"+t["https://schema.org/encodingFormat"]+"'":"",f=t["https://schema.org/thumbnailUrl"]?"poster='"+t["https://schema.org/thumbnailUrl"]+"'":"",m.innerHTML+="
"):m.innerHTML+="
"):(v=y[g].replace(/\n/g,"
"),m.innerHTML+="
"+getDisplayAttr(h)+"
"+v+"
"))}B.appendChild(m);for(var b,L,x=document.createElement("dl"),M=0;M"):T.innerHTML="",x.appendChild(T))}}for(L in e){var C,O=document.createElement("dt");for(C in O.innerHTML=getDisplayAttr(L),x.appendChild(O),e[L].sort(function(e,t){return weights[t["@id"]]-weights[e["@id"]]}),e[L]){var D,j,F,H=e[L][C];x.appendChild(z(H,C)),void 0!==H["https://schema.org/contentUrl"]&&((D=document.createElement("dd")).classList.add("dd-contentobject"),"https://schema.org/VideoObject"==H["@type"]?(j=H["https://schema.org/encodingFormat"]?"type='"+H["https://schema.org/encodingFormat"]+"'":"",F=H["https://schema.org/thumbnailUrl"]?"poster='"+H["https://schema.org/thumbnailUrl"]+"'":"",D.innerHTML+='"):D.innerHTML="",x.appendChild(D))}}B.appendChild(x),X.each(function(e,t,n){t==r?n[t].classList.add("selectedNode"):n[t].classList.remove("selectedNode")}),document.title=a.join(" :: ")}var m,p,g=d3.select("svg"),f=(g.attr("width"),g.attr("height"),g.append("g").attr("id","container")),v=d3.forceSimulation().force("link",d3.forceLink().id(function(e){return e["@id"]}).strength(.005)).force("charge",d3.forceManyBody()).force("collision",d3.forceCollide(function(e){return 1.1*getSizeForNode(e)})),y=f.append("g").attr("class","links").selectAll(".relationship").data(P.links).enter().append("g").attr("class",function(e){return"relationship "+e.name}),b=y.append("line").attr("marker-end","url(#arrowHead)"),L=y.append("text").text(function(e){return getDisplayAttr(e.name)}),X=f.append("g").attr("class","nodes").selectAll(".node").data(P.nodes).enter().append("g").attr("class",function(e){var t,n="node "+e["@type"];return!e["@type"]||-1<(t=e["@type"].lastIndexOf("/"))&&(n+=" "+e["@type"].substr(t+1)),n}),x=function(e,t){var n=l(),r=N();w(),void 0===t&&(t=1==e.length?n[2]/6:n[2]/(4+Math.max(0,2.5-e.length))),d=t;for(var a=n[0]+n[2]/2-r.dx,o=n[1]+n[3]/2-r.dy,i=2*Math.PI/e.length,s=0;s2*getSizeForNode(e)&&a.attr("transform","scale("+2*getSizeForNode(e)/r/1.05+")")}),X.each(function(e){e["https://schema.org/thumbnailUrl"]&&d3.select(this).append("svg:image").attr("xlink:href",e["https://schema.org/thumbnailUrl"]).attr("width",function(e){return 2*getSizeForNode(e)}).attr("height",function(e){return 2*getSizeForNode(e)}).attr("transform",function(e){return"translate(-"+getSizeForNode(e)+" -"+getSizeForNode(e)+")"}).attr("clip-path","url(#clipNodeImage)").attr("preserveAspectRatio","xMidYMid slice")}),X.each(function(e){"https://schema.org/VideoObject"===e["@type"]&&(getSizeForNode(e),d3.select(this).append("svg:polygon").attr("points","-10,-10, -10,10, 10,0").attr("class","play"))}),v.nodes(P.nodes).on("tick",function(){P.nodes.forEach(function(e,t){e.leftX=e.rightX=e.x}),b.each(function(e){var t,n;e.source.rightXe.target.rightX?o=e.target.rightX:o>e.source.rightX?o=e.source.rightX:o"+getNodeYear(n)+""),r.classList.add("nodeTitle"),r.classList.add("nodeTitleNr"+e),r.addEventListener("click",function(e){var t=H.nodes.indexOf(n);B(t)}),(e=document.createElement("a")).classList.add("nodeType"),e.innerHTML=getDisplayAttr(n["@type"]),e.title=n["@type"],e.addEventListener("click",function(e){X(n["@type"])}),t.appendChild(r),t.appendChild(e),t}function h(t,r){for(document.body.classList.add("detailsOpen"),scrollToY(0,4e3);I.hasChildNodes();)I.removeChild(I.lastChild);var e=[],n=[],a=[],o=document.createElement("div");o.id="nodeDetailsScaler",o.addEventListener("mousedown",function(e){function t(e){I.style.width=window.innerWidth-e.clientX+5+"px"}document.body.addEventListener("mousemove",t),document.body.addEventListener("mouseup",function(){document.body.removeEventListener("mousemove",t)})}),nodeDetails.appendChild(o);var i=document.createElement("ul");i.classList.add("breadcrumbs");var s=!0,d=!1,c=void 0;try{for(var l,u=breadcrumbs[t["@id"]][Symbol.iterator]();!(s=(l=u.next()).done);s=!0)!function(n){var e=document.createElement("li"),t=document.createElement("span");t.classList.add("crumb"),t.addEventListener("click",function(e){var t=H.nodes.indexOf(nodeMap[n]);B(t)}),t.innerHTML=""+getNodeLabel(nodeMap[n]);var r=getNodeYear(nodeMap[n]);null!==r&&(t.innerHTML+=""+r+""),e.appendChild(t),i.appendChild(e),a.push(getNodeLabel(nodeMap[n]))}(l.value)}catch(e){d=!0,c=e}finally{try{!s&&u.return&&u.return()}finally{if(d)throw c}}I.appendChild(i),a.push(getNodeLabel(t)),o=getLabelAttribute(t),(d=document.createElement("h2")).innerHTML=getNodeLabel(t),(c=document.createElement("span")).classList.add("nodeType"),c.innerHTML=getDisplayAttr(t["@type"]),c.title=t["@type"],c.addEventListener("click",function(e){X(t["@type"])}),d.appendChild(c),I.appendChild(d);var h,p=document.createElement("dl"),m=["@id","x","y","index","@type","vy","vx","fx","fy","leftX","rightX"];for(h in"https://schema.org/contentUrl"!==o&&(m[m.length]=o),t)if(-1==m.indexOf(h)){var g,f,v=Array.isArray(t[h])?t[h]:[t[h]];for(g in v)"string"==typeof v[g]&&nodeMap[v[g]]||void 0===v[g]["@id"]&&("https://schema.org/url"==h||"http://www.w3.org/2000/01/rdf-schema#seeAlso"==h?p.innerHTML+="
"+getDisplayAttr(h)+"
"+v[g]+"
":"https://schema.org/embedUrl"==h?(p.innerHTML+="
"+getDisplayAttr(h)+"
"+v[g]+"
",p.innerHTML+="
"):"https://schema.org/contentUrl"==h?(p.innerHTML+="
"+getDisplayAttr(h)+"
"+v[g]+"
","https://schema.org/VideoObject"==t["@type"]?(f=t["https://schema.org/thumbnailUrl"]?"poster='"+t["https://schema.org/thumbnailUrl"]+"'":"",p.innerHTML+="
"):p.innerHTML+="
"):(f=v[g].replace(/\n/g,"
"),p.innerHTML+="
"+getDisplayAttr(h)+"
"+f+"
"))}I.appendChild(p);for(var y,b,L=document.createElement("dl"),x=0;x"):k.innerHTML="",L.appendChild(k))}}for(b in e){var E,C=document.createElement("dt");for(E in C.innerHTML=getDisplayAttr(b),L.appendChild(C),e[b].sort(function(e,t){return weights[t["@id"]]-weights[e["@id"]]}),e[b]){var O,D,j,F=e[b][E];L.appendChild(P(F,E)),void 0!==F["https://schema.org/contentUrl"]&&((O=document.createElement("dd")).classList.add("dd-contentobject"),"https://schema.org/VideoObject"==F["@type"]?(D=F["https://schema.org/encodingFormat"]?"type='"+F["https://schema.org/encodingFormat"]+"'":"",j=F["https://schema.org/thumbnailUrl"]?"poster='"+F["https://schema.org/thumbnailUrl"]+"'":"",O.innerHTML+='"):O.innerHTML="",L.appendChild(O))}}I.appendChild(L),z.each(function(e,t,n){t==r?n[t].classList.add("selectedNode"):n[t].classList.remove("selectedNode")}),document.title=a.join(" :: ")}var p,m,g=d3.select("svg"),f=(g.attr("width"),g.attr("height"),g.append("g").attr("id","container")),v=d3.forceSimulation().force("link",d3.forceLink().id(function(e){return e["@id"]}).strength(.005)).force("charge",d3.forceManyBody()).force("collision",d3.forceCollide(function(e){return 1.1*getSizeForNode(e)})),y=f.append("g").attr("class","links").selectAll(".relationship").data(H.links).enter().append("g").attr("class",function(e){return"relationship "+e.name}),b=y.append("line").attr("marker-end","url(#arrowHead)"),L=y.append("text").text(function(e){return getDisplayAttr(e.name)}),z=f.append("g").attr("class","nodes").selectAll(".node").data(H.nodes).enter().append("g").attr("class",function(e){var t,n="node "+e["@type"];return!e["@type"]||-1<(t=e["@type"].lastIndexOf("/"))&&(n+=" "+e["@type"].substr(t+1)),n}),x=function(e,t){var n=l(),r=N();w(),void 0===t&&(t=1==e.length?n[2]/6:n[2]/(4+Math.max(0,2.5-e.length))),d=t;for(var a=n[0]+n[2]/2-r.dx,o=n[1]+n[3]/2-r.dy,i=2*Math.PI/e.length,s=0;s2*getSizeForNode(e)&&a.attr("transform","scale("+2*getSizeForNode(e)/r/1.05+")")}),z.each(function(e){e["https://schema.org/thumbnailUrl"]&&d3.select(this).append("svg:image").attr("xlink:href",e["https://schema.org/thumbnailUrl"]).attr("width",function(e){return 2*getSizeForNode(e)}).attr("height",function(e){return 2*getSizeForNode(e)}).attr("transform",function(e){return"translate(-"+getSizeForNode(e)+" -"+getSizeForNode(e)+")"}).attr("clip-path","url(#clipNodeImage)").attr("preserveAspectRatio","xMidYMid slice")}),z.each(function(e){"https://schema.org/VideoObject"===e["@type"]&&(getSizeForNode(e),d3.select(this).append("svg:polygon").attr("points","-10,-10, -10,10, 10,0").attr("class","play"))}),v.nodes(H.nodes).on("tick",function(){H.nodes.forEach(function(e,t){e.leftX=e.rightX=e.x}),b.each(function(e){var t,n;e.source.rightXe.target.rightX?o=e.target.rightX:o>e.source.rightX?o=e.source.rightX:o r.json());\nconst rankingPromise = fetch('/assets/js/ranking.json').then(r => r.json());\n\nPromise.all([requestPromise, rankingPromise])\n .then(values => {\n if(values[0].hasOwnProperty('@graph')) {\n data = values[0];\n weights = values[1];\n } else {\n data = values[1];\n weights = values[0];\n }\n graph = jsonLdToGraph(data['@graph']);\n // create a map of nodes by id.\n for(let i in graph.nodes) {\n nodeMap[graph.nodes[i]['@id']] = graph.nodes[i];\n }\n startGraph(graph);\n });\n\nfunction inCircle(dx, dy, r) {\n // fastest check if in circle: https://stackoverflow.com/a/7227057\n let dxAbs = Math.abs(dx);\n let dyAbs = Math.abs(dy);\n\n if(dxAbs > r || dyAbs > r) {\n return false;\n } else if(dxAbs + dyAbs <= r){\n return true;\n } else if( Math.pow(dx,2) + Math.pow(dy, 2) <= Math.pow(r,2)){\n return true;\n } else {\n return false;\n }\n}\n\nfunction createLinkMap(graph) {\n let linkMap = {};\n for(let link of graph['links']){\n if(typeof linkMap[link['source']] == 'undefined') {\n linkMap[link['source']] = [];\n }\n linkMap[link['source']][linkMap[link['source']].length] = {'id': link['target'], 'name': link['name']};\n\n\n if(typeof linkMap[link['target']] == 'undefined') {\n linkMap[link['target']] = [];\n }\n\n linkMap[link['target']][linkMap[link['target']].length] = {'id': link['source'], 'name': link['name']};\n }\n return linkMap;\n}\n\n\n // config\nvar nodeSize = 40;\nvar selectedNodeSize = 140;\nvar firstNodeId = \"https://rubenvandeven.com/\";\n\nfunction getSizeForNode(node) {\n if(node.hasOwnProperty('https://schema.org/thumbnailUrl'))\n return nodeSize;\n if(weights[node['@id']])\n return nodeSize * weights[node['@id']];\n if(node['@id'] == firstNodeId)\n return nodeSize*1.2;\n // everynode has at least one link. these should equal 1\n return nodeSize * (.7 + Math.min(20, linkMap[node['@id']].length) / 40)\n return nodeSize;\n}\n\n// TODO: make sure, 'shortest' path is favoured.\nfunction createBreadcrumbs(linkMap, srcId) {\n let crumbs = {};\n\n let createBreadcrumbLayer = function(srcId) {\n let path = crumbs[srcId];\n let newPath = path.slice();\n newPath.push(srcId);\n\n let nextSrcIds = [];\n for (let link of linkMap[srcId]) {\n if(typeof crumbs[link['id']] !== 'undefined') continue;\n crumbs[link['id']] = newPath;\n nextSrcIds.push(link['id']);\n }\n\n return nextSrcIds;\n }\n crumbs[srcId] = [];\n let nextIds = [srcId];\n while(nextIds.length > 0) {\n let newNextIds = [];\n for (let nextId of nextIds) {\n let r = createBreadcrumbLayer(nextId);\n newNextIds = newNextIds.concat(r);\n }\n nextIds = newNextIds;\n }\n return crumbs;\n}\n\nvar nodePositions = {};\nfunction startGraph(graph){\n\n\n// set some vars\nvar currentNodeIdx = 0;\nvar currentNodePositionRadius = 0;\nvar types = {};\n\nlinkMap = createLinkMap(graph);\nbreadcrumbs = createBreadcrumbs(linkMap, firstNodeId);\n\nfor (let nodeIdx in graph['nodes']) {\n let type = graph['nodes'][nodeIdx][\"@type\"];\n if(typeof types[type] == 'undefined') {\n types[type] = [];\n }\n types[type].push(nodeIdx);\n}\nvar graphControlsEl = document.getElementById('graphControls');\nvar typeLinksEl = document.getElementById('typeLinks');\nvar showMoreTypeLinksEl = document.getElementById('showMoreTypeLinks');\nvar moreTypeLinksEl = document.getElementById('moreTypeLinks');\nvar relLinksEl = document.getElementById('relLinks');\n\n// sort types by count:\nvar typeCounts = Object.keys(types).map(function(key) {\n return [key, types[key].length];\n});\ntypeCounts.sort(function(first, second) {\n return second[1] - first[1];\n});\n\n// make controls\nlet i = 0;\nfor (let typeCountIdx in typeCounts) {\n let typeName = typeCounts[typeCountIdx][0];\n let typeLinkEl = document.createElement(\"li\");\n let typeLinkAEl = document.createElement(\"a\");\n let typeLinkCountEl = document.createElement(\"span\");\n typeLinkCountEl.innerHTML = typeCounts[typeCountIdx][1];\n typeLinkCountEl.classList.add('typeCount');\n typeLinkAEl.innerHTML = getDisplayAttr(typeName);\n typeLinkAEl.title = typeName;\n typeLinkAEl.addEventListener('click', function(){\n centerByType(typeName);\n // positionNodesInCenter(types[typeName]);\n });\n typeLinkAEl.addEventListener('mouseover', function() {\n let typeNodeEls = document.getElementsByClassName(typeName);\n for(let typeNodeEl of typeNodeEls) {\n typeNodeEl.classList.add('typeHighlight');\n }\n });\n typeLinkAEl.addEventListener('mouseout', function() {\n let typeNodeEls = document.getElementsByClassName(typeName);\n for(let typeNodeEl of typeNodeEls) {\n typeNodeEl.classList.remove('typeHighlight');\n }\n });\n typeLinkEl.append(typeLinkAEl);\n typeLinkEl.append(typeLinkCountEl);\n (i < 5 ? typeLinksEl: moreTypeLinksEl).appendChild(typeLinkEl);\n i++;\n // typeLinksEl.appendChild(typeLinkEl);\n}\n\nshowMoreTypeLinksEl.addEventListener('click', function () {\n document.body.classList.add('showMoreLinks');\n var hideMoreTypeLinks = function(e) {\n e.preventDefault();\n e.stopPropagation();\n document.body.removeEventListener('mouseup', hideMoreTypeLinks, true);\n document.body.classList.remove('showMoreLinks');\n }\n document.body.addEventListener('mouseup', hideMoreTypeLinks, true);\n}, false)\n\n\n// make svg\nvar svg = d3.select(\"svg\"),\n width = +svg.attr(\"width\"),\n height = +svg.attr(\"height\");\nvar container = svg.append(\"g\")\n .attr(\"id\", \"container\")\n ;\n\nvar simulation = d3.forceSimulation()\n .force(\"link\", d3.forceLink().id(function(d) { return d[\"@id\"]; }).strength(.005))\n .force(\"charge\", d3.forceManyBody()) // doesn't seem necessary?\n .force(\"collision\", d3.forceCollide(function(d){\n return getSizeForNode(d) * 1.1; // avoid overlapping nodes\n }))\n // .force(\"center\", d3.forceCenter(width / 2, height / 2)) // position around center\n\n // .force(\"x\", d3.forceX())\n // .force(\"y\", d3.forceY())\n // .force(\"y\", d3.forceY())\n ;\n\n\nvar link = container.append(\"g\")\n .attr(\"class\", \"links\")\n .selectAll(\".relationship\")\n .data(graph['links'])\n .enter().append(\"g\")\n .attr(\"class\", function(l){return \"relationship \"+l.name;})\n ;\nvar linkLine = link\n // .append(\"line\");\n .append(\"line\").attr(\"marker-end\", \"url(#arrowHead)\")\n ;\nvar linkText = link\n .append(\"text\")\n .text(function(l){\n // l == Object { source: \"https://rubenvandeven.com/#codesandmodes\", target: \"_:b34\", name: \"https://schema.org/location\" }\n return getDisplayAttr(l.name);\n })\n ;\n\n var node = container.append(\"g\")\n .attr(\"class\", \"nodes\")\n .selectAll(\".node\")\n .data(graph.nodes)\n .enter().append(\"g\")\n .attr(\"class\", function(d) {\n let baseClasses = 'node ' + d['@type'];\n if(d['@type']) {\n let slashpos = d['@type'].lastIndexOf('/');\n if(slashpos > -1) {\n baseClasses += ' ' + d['@type'].substr(slashpos + 1);\n }\n }\n return baseClasses;\n })\n ;\nvar getViewbox = function() {\n return svg.attr(\"viewBox\").split(\" \").map(parseFloat);\n}\nvar positionNodesInCenter = function(idxs) {\n setViewboxForceCenter(); // sets forceCx & forceCy\n if(typeof idxs == \"object\" && idxs !== null && idxs.length == 1) {\n idxs = idxs[0];\n }\n\n nodePositions = {}; // reset\n if(idxs === null) {\n return;\n }\n else if(typeof idxs == \"object\") {\n // array or object -> each\n // calculate grid:\n // let itemsX = 4;\n // let itemsY = Math.ceil(idxs.length/itemsX);\n // console.log(itemsX,itemsY);\n // let rowDiffX = viewBox[3] * (1/(itemsX+1));\n // let rowDiffY = viewBox[2] * (1/(itemsY+1));\n // console.log(rowDiffX, rowDiffY);\n // for (var i = 0; i < idxs.length; i++) {\n // nodePositions[idxs[i]] = [\n // cx - itemsX/2*rowDiffX + rowDiffX * ((i % itemsX)),\n // cy - itemsY/2*rowDiffY + rowDiffY * (Math.floor(i / itemsX))\n // ];\n // }\n positionNodesInCircle(idxs);\n // console.log(nodePositions);\n }\n else{\n nodePositions[idxs] = [\n forceCx,\n forceCy\n ];\n // console.log(\"singleNode\", idxs, nodePositions);\n }\n\n node.each(function(d,nIdx,nodeEls){\n if(typeof nodePositions[nIdx] != 'undefined') {\n nodeEls[nIdx].classList.add('centeredNode');\n nodeEls[nIdx].classList.add('visibleNode');\n } else {\n nodeEls[nIdx].classList.remove('centeredNode');\n nodeEls[nIdx].classList.remove('visibleNode');\n }\n });\n\n // restart animation (they call that 'alpha' in d3 force)\n simulation.alpha(1);\n simulation.restart();\n}\nvar positionNodesInCircle = function(idxs, r) {\n let viewBox = getViewbox();\n let zoom = getZoomValues();\n setViewboxForceCenter(); // sets forceCx & forceCy\n if(typeof r == 'undefined') {\n if(idxs.length == 1) {\n r = viewBox[2] / 6;\n } else {\n r = viewBox[2] / (4 + Math.max(0, 2.5 - idxs.length));\n }\n }\n currentNodePositionRadius = r;\n let forceCx = viewBox[0] + viewBox[2]/2 - zoom['dx'];\n let forceCy = viewBox[1] + viewBox[3]/2 - zoom['dy'];\n\n let stepSize = 2*Math.PI / idxs.length;\n\n for (var i = 0; i < idxs.length; i++) {\n nodePositions[idxs[i]] = [\n forceCx + Math.sin(stepSize * i) * r,\n forceCy + Math.cos(stepSize * i) * r\n ];\n }\n\n // restart animation (they call that 'alpha' in d3 force)\n simulation.alpha(1);\n simulation.restart();\n}\nvar centerByType = function(types, updateHistory) {\n if(typeof updateHistory == 'undefined') {\n updateHistory = true;\n }\n if(!Array.isArray(types)) {\n types = [types];\n }\n let idxs = [];\n for(let idx in graph.nodes) {\n if(types.indexOf(graph.nodes[idx]['@type']) > -1) {\n idxs[idxs.length] = idx;\n }\n }\n deselectNode();\n if(updateHistory) {\n // TODO: working\n // console.log(types[0], getDisplayAttr(types[0]),types.map(getDisplayAttr));\n history.pushState({types: types}, \"\", \"/@type/\"+(types.map(getDisplayAttr).join(\"+\")));\n } else {\n history.replaceState({types: types}, \"\", \"/@type/\"+(types.map(getDisplayAttr).join(\"+\")));\n }\n positionNodesInCenter(idxs.length ? idxs : null);\n}\n\nvar selectedNodeTransition = d3.transition()\n .duration(750)\n .ease(d3.easeLinear);\n\nvar nodeDetailEl = document.getElementById(\"nodeDetails\");\n\nvar createRelationshipEl = function(relNode, i) {\n let el = document.createElement(\"dd\");\n el.classList.add('relLink');\n let titleEl = document.createElement('a');\n titleEl.innerHTML = getNodeLabel(relNode)\n let year = getNodeYear(relNode);\n if(year !== null) {\n titleEl.innerHTML += `${getNodeYear(relNode)}`;\n }\n titleEl.classList.add('nodeTitle');\n titleEl.classList.add('nodeTitleNr'+i);\n titleEl.addEventListener('click',function(e){\n let idx = graph.nodes.indexOf(relNode);\n selectNode(idx);\n });\n let typeEl = document.createElement('a');\n typeEl.classList.add('nodeType');\n typeEl.innerHTML = getDisplayAttr(relNode['@type']);\n typeEl.title = relNode['@type'];\n typeEl.addEventListener('click',function(e){\n centerByType(relNode['@type']);\n });\n el.appendChild(titleEl);\n el.appendChild(typeEl);\n return el;\n}\n\nvar setDetails = function(nodeDatum, nodeIdx) {\n document.body.classList.add(\"detailsOpen\");\n scrollToY(0, 4000);\n while (nodeDetailEl.hasChildNodes()) {\n nodeDetailEl.removeChild(nodeDetailEl.lastChild);\n }\n\n // TODO: replace relUp & relDown with linkMap\n let relUp = [];\n let relDown = [];\n let pageTitles = [];\n let nodeDetailScalerEl = document.createElement('div');\n // nodeDetailScalerEl.innerHTML = `
`;\n nodeDetailScalerEl.id = 'nodeDetailsScaler';\n nodeDetailScalerEl.addEventListener('mousedown', function(e){\n // console.log('go');\n let drag = function(e) {\n // 5px for padding\n nodeDetailEl.style.width = (window.innerWidth - e.clientX + 5) +'px';\n }\n document.body.addEventListener('mousemove', drag);\n document.body.addEventListener('mouseup', function(){\n document.body.removeEventListener('mousemove', drag);\n });\n });\n nodeDetails.appendChild(nodeDetailScalerEl);\n\n let breadcrumbsEl = document.createElement('ul');\n breadcrumbsEl.classList.add('breadcrumbs');\n for(let crumbNodeId of breadcrumbs[nodeDatum['@id']]) {\n let crumbWrapEl = document.createElement('li');\n let crumbEl = document.createElement('span');\n crumbEl.classList.add('crumb');\n crumbEl.addEventListener('click', function(e){\n let idx = graph.nodes.indexOf(nodeMap[crumbNodeId]);\n selectNode(idx);\n });\n crumbEl.innerHTML = `${getNodeLabel(nodeMap[crumbNodeId])}`;\n let nodeYear = getNodeYear(nodeMap[crumbNodeId]);\n if(nodeYear !== null) {\n crumbEl.innerHTML += `${nodeYear}`;\n }\n crumbWrapEl.appendChild(crumbEl);\n breadcrumbsEl.appendChild(crumbWrapEl);\n pageTitles.push(getNodeLabel(nodeMap[crumbNodeId]));\n }\n nodeDetailEl.appendChild(breadcrumbsEl);\n pageTitles.push(getNodeLabel(nodeDatum));\n\n let titleAttr = getLabelAttribute(nodeDatum);\n let titleEl = document.createElement('h2');\n titleEl.innerHTML = getNodeLabel(nodeDatum);\n\n let typeEl = document.createElement('span');\n typeEl.classList.add('nodeType')\n typeEl.innerHTML = getDisplayAttr(nodeDatum['@type']);\n typeEl.title = nodeDatum['@type']\n typeEl.addEventListener('click',function(e){\n centerByType(nodeDatum['@type']);\n });\n titleEl.appendChild(typeEl);\n nodeDetailEl.appendChild(titleEl);\n\n let listEl = document.createElement(\"dl\");\n // listEl.innerHTML += `
type
${nodeDatum['@type']}
`;\n\n let skipNodeAttributes = [\n '@id','x','y','index','@type','vy','vx','fx','fy','leftX','rightX'\n ];\n if(titleAttr !== 'https://schema.org/contentUrl') {\n skipNodeAttributes[skipNodeAttributes.length] = titleAttr;\n }\n for (let attr in nodeDatum) {\n if(skipNodeAttributes.indexOf(attr) != -1) {\n continue;\n }\n\n // approach all as array\n let nodeAttr = Array.isArray(nodeDatum[attr]) ? nodeDatum[attr] : [nodeDatum[attr]];\n for (let i in nodeAttr) {\n // check if relationship:\n if(typeof nodeAttr[i] === \"string\" && nodeMap[nodeAttr[i]]) {\n continue;\n } else if(typeof nodeAttr[i]['@id'] !== 'undefined') {\n continue;\n }\n if(attr == 'https://schema.org/url' || attr == 'http://www.w3.org/2000/01/rdf-schema#seeAlso') {\n listEl.innerHTML += `
${getDisplayAttr(attr)}
${nodeAttr[i]}
`;\n } else if(attr == 'https://schema.org/embedUrl') {\n listEl.innerHTML += `
${getDisplayAttr(attr)}
${nodeAttr[i]}
`;\n listEl.innerHTML += `
`;\n } else if(attr == 'https://schema.org/contentUrl') {\n listEl.innerHTML += `
${getDisplayAttr(attr)}
${nodeAttr[i]}
`;\n if(nodeDatum['@type'] == 'https://schema.org/VideoObject') {\n // console.log(nodeDatum, nodeAttr);\n let videoType = nodeDatum['https://schema.org/encodingFormat'] ? `type='${nodeDatum['https://schema.org/encodingFormat']}'`: \"\";\n let poster = nodeDatum['https://schema.org/thumbnailUrl'] ? `poster='${nodeDatum['https://schema.org/thumbnailUrl']}'`: \"\";\n // TODO: enable outplay and make it work (for some reason it does not...)\n listEl.innerHTML += `
`;\n } else{\n listEl.innerHTML += `
`;\n }\n } else {\n let valueHtml = nodeAttr[i].replace(/\\n/g,\"
\");\n listEl.innerHTML += `
${getDisplayAttr(attr)}
${valueHtml}
`;\n }\n }\n }\n nodeDetailEl.appendChild(listEl);\n\n // let relTitleEl = document.createElement(\"h4\");\n // relTitleEl.classList.add('linkTitle');\n // relTitleEl.innerHTML = \"links\";\n // nodeDetailEl.appendChild(relTitleEl);\n\n let relsEl = document.createElement(\"dl\");\n // collect relationships\n for (var i = 0; i < graph.links.length; i++) {\n let link = graph.links[i];\n if(link['source']['@id'] == nodeDatum['@id']) {\n if(typeof relDown[link['name']] == \"undefined\") {\n relDown[link['name']] = [];\n }\n relDown[link['name']][relDown[link['name']].length] = link['target'];\n }\n if(link['target']['@id'] == nodeDatum['@id']) {\n if(typeof relUp[link['name']] == \"undefined\") {\n relUp[link['name']] = [];\n }\n relUp[link['name']][relUp[link['name']].length] = link['source'];\n }\n }\n\n // relationships / links incomming
\n for(let attr in relDown) {\n let attrEl = document.createElement(\"dt\");\n attrEl.innerHTML = getDisplayAttr(attr);\n relsEl.appendChild(attrEl);\n\n // highest pagerank first:\n relDown[attr].sort((a,b) => weights[b['@id']] - weights[a['@id']]);\n\n for(let i in relDown[attr]) {\n let rel = relDown[attr][i];\n relsEl.appendChild(createRelationshipEl(rel));\n if(typeof rel['https://schema.org/contentUrl'] != 'undefined') {\n let ddEl = document.createElement('dd')\n ddEl.classList.add('dd-contentobject');\n if(rel['@type'] == 'https://schema.org/VideoObject') {\n let videoType = rel['https://schema.org/encodingFormat'] ? `type='${rel['https://schema.org/encodingFormat']}'`: \"\";\n let poster = rel['https://schema.org/thumbnailUrl'] ? `poster='${rel['https://schema.org/thumbnailUrl']}'`: \"\";\n ddEl.innerHTML += ``;\n } else{\n ddEl.innerHTML = ``\n }\n relsEl.appendChild(ddEl);\n }\n }\n }\n\n // relationships / links outgoing
\n for(let attr in relUp) {\n let attrEl = document.createElement(\"dt\");\n attrEl.innerHTML = getDisplayAttr(attr);\n relsEl.appendChild(attrEl);\n\n // highest pagerank first:\n relUp[attr].sort((a,b) => weights[b['@id']] - weights[a['@id']]);\n\n for(let i in relUp[attr]) {\n let rel = relUp[attr][i];\n relsEl.appendChild(createRelationshipEl(rel, i));\n if(typeof rel['https://schema.org/contentUrl'] != 'undefined') {\n let ddEl = document.createElement('dd')\n ddEl.classList.add('dd-contentobject');\n if(rel['@type'] == 'https://schema.org/VideoObject') {\n let videoType = rel['https://schema.org/encodingFormat'] ? `type='${rel['https://schema.org/encodingFormat']}'`: \"\";\n let poster = rel['https://schema.org/thumbnailUrl'] ? `poster='${rel['https://schema.org/thumbnailUrl']}'`: \"\";\n ddEl.innerHTML += ``;\n } else{\n ddEl.innerHTML = ``\n }\n relsEl.appendChild(ddEl);\n }\n }\n }\n\n nodeDetailEl.appendChild(relsEl);\n\n node.each(function(d,nIdx,nodeEls){\n if(nIdx == nodeIdx) {\n nodeEls[nIdx].classList.add('selectedNode');\n } else {\n nodeEls[nIdx].classList.remove('selectedNode');\n }\n });\n\n // TODO: update history & title\n document.title = pageTitles.join(\" :: \");\n};\nvar closeDetails = function() {\n document.body.classList.remove(\"detailsOpen\");\n scrollToY(0, 4000); // for mobile\n}\n\n/**\n * Select a node, and center it + show details\n * @param int idx The index of the node in the graph.nodes array\n * @param Element|null nodeEl Optional, provide node element, so loop doesn't have to be used to change the Element\n * @return void\n */\nvar selectNode = function(idx, updateHistory){\n if(typeof updateHistory == 'undefined') {\n updateHistory = true;\n }\n\n let nodeEl = null;\n let nodeDatum = null;\n\n node.each(function(d,nIdx,nodeEls){\n if(nIdx == idx) {\n nodeEl = nodeEls[idx];\n nodeDatum = d;\n }\n });\n if(!nodeEl) {\n return;\n }\n\n\n if(true) { // always set history state, but replace instead of update on 'updatehistory'\n let id = null;\n if(nodeDatum['@id'].startsWith(/*location.origin*/'https://rubenvandeven.com/')){\n id = nodeDatum['@id'].substr(26);\n } else {\n id = '?id=' + nodeDatum['@id'];\n }\n\n if(updateHistory) {\n history.pushState({node: idx}, getNodeLabel(nodeDatum), \"/\"+id);\n } else {\n history.replaceState({node: idx}, getNodeLabel(nodeDatum), \"/\"+id);\n }\n }\n\n // set global var\n positionNodesInCenter(idx);\n\n let currentCrumbs = breadcrumbs[nodeDatum['@id']].slice();\n currentCrumbs[currentCrumbs.length] = nodeDatum['@id'];\n\n // set active links.\n let linkedIdxs = [];\n link.each(function(d,idx,linkEls,q){\n // set nodes 'visible'/highlighted when linked to active node\n if(d.source == nodeDatum || d.target == nodeDatum) {\n linkEls[idx].classList.add('activeLink','visibleLink');\n linkEls[idx].getElementsByTagName(\"line\")[0].setAttribute(\"marker-end\", \"url(#arrowHeadSelected)\");\n node.filter(function(a, fnodeIdx){\n let r = a['@id'] == d.source['@id'] || a['@id'] == d.target['@id']; //connected node: true/false\n if(r && linkedIdxs.indexOf(fnodeIdx) === -1){\n linkedIdxs[linkedIdxs.length] = fnodeIdx;\n }\n return r;\n }).classed('visibleNode', true);\n } else {\n linkEls[idx].classList.remove('activeLink');\n linkEls[idx].getElementsByTagName(\"line\")[0].setAttribute(\"marker-end\", \"url(#arrowHead)\");\n }\n // check if link is part of breadcrumb trail\n let posSrc = currentCrumbs.indexOf(d.source['@id']);\n let posTrg = currentCrumbs.indexOf(d.target['@id']);\n if(posSrc > -1 && posTrg > -1 && Math.abs(posSrc - posTrg) == 1) {\n linkEls[idx].classList.add('breadcrumbLink');\n linkEls[idx].getElementsByTagName(\"line\")[0].setAttribute(\"marker-end\", \"url(#arrowHeadCrumbTrail)\");\n } else {\n linkEls[idx].classList.remove('breadcrumbLink');\n }\n });\n\n let i = linkedIdxs.indexOf(idx);\n\n if(i !== -1) {\n linkedIdxs.splice(i, 1);\n }\n\n positionNodesInCircle(linkedIdxs);\n\n setDetails(nodeDatum ,idx);\n}\nvar deselectNode = function() {\n positionNodesInCenter(null);\n link.each(function(d,idx,linkEls,q){\n linkEls[idx].classList.remove('activeLink');\n linkEls[idx].classList.remove('breadcrumbLink');\n linkEls[idx].getElementsByTagName(\"line\")[0].setAttribute(\"marker-end\", \"url(#arrowHead)\")\n });\n closeDetails();\n}\n\n\nwindow.addEventListener('popstate', function(event) {\n if(event.state.hasOwnProperty('node')) {\n selectNode(event.state['node'], false);\n }\n else {\n // if not sure what to do, fall back to first node (also used to return to opening page)\n let firstNode = graph['nodes'].find(n => n['@id'] === firstNodeId);\n selectNode(graph['nodes'].indexOf(firstNode), false);\n }\n});\n\nvar forceCx, forceCy;\nvar setViewboxForceCenter = function() {\n let viewBox = getViewbox();\n let zoom = getZoomValues();\n forceCx = viewBox[0] + viewBox[2]/2 - zoom['dx'];\n forceCy = viewBox[1] + viewBox[3]/2 - zoom['dy'];\n}\n\nvar getZoomValues = function(){\n let zoomContainer = document.getElementById(\"container\");\n let dx = 0, dy = 0, scale = 1;\n if(zoomContainer.transform.baseVal.length > 0) {\n for(let transform of zoomContainer.transform.baseVal) {\n if(transform.type == SVGTransform.SVG_TRANSFORM_TRANSLATE) {\n dx += transform.matrix.e;\n dy += transform.matrix.f;\n }\n else if (transform.type == SVGTransform.SVG_TRANSFORM_SCALE) {\n scale *= transform.matrix.a; // assume simple scale\n }\n }\n }\n\n return {'dx': dx, 'dy': dy, 'scale': scale};\n}\n\nsetViewboxForceCenter(); // sets forceCx & forceCy\n\nvar graphInitialised = false;\nsimulation.force('centerActive', function force(alpha) {\n // let currentNode = node.selectAll('.detail');\n // console.log(currentNode);\n // console.log(forceCx, forceCy);\n node.each(function(d, idx, nodes){\n let n = d;\n let k = alpha * 0.1;\n n.fx = null;\n n.fy = null;\n if(typeof nodePositions[idx] != 'undefined') {\n if(graphInitialised == false) {\n n.x = nodePositions[idx][0];\n n.y = nodePositions[idx][1];\n n.vx = 0;\n n.vy = 0;\n } else {\n n.vx -= (n.x - nodePositions[idx][0]) * k * 5;\n n.vy -= (n.y - nodePositions[idx][1]) * k * 5;\n }\n } else {\n // if it's not positioned, move it out of the circle\n if(currentNodePositionRadius < 1) {\n return;\n }\n\n let dx = n.x - forceCx;\n let dy = n.y - forceCy;\n if(!inCircle(dx, dy, currentNodePositionRadius)) {\n return;\n }\n\n if(graphInitialised == false) {\n // on init, fixate items outside of circle\n n.fx = n.x + dx * (2+Math.random());\n n.fy = n.y + dy * (2+Math.random());\n } else {\n // if initialised, gradually move them outwards\n n.vx += dx * k*4;\n n.vy += dy * k*4;\n }\n }\n });\n});\n\n//path to curve the tile\nvar nodePath = node.append(\"path\")\n .attr(\"id\", function(d,idx){return \"nodePath\"+idx;})\n .attr(\"d\", function(d){\n var r = getSizeForNode(d) * 0.9;\n var startX = getSizeForNode(d);\n // M cx cy\n // m -r, 0\n // a r,r 0 1,0 (r * 2),0\n // a r,r 0 1,0 -(r * 2),0\n // return 'M' + nodeSize/2 + ' ' + nodeSize/2 + ' ' +\n return 'M' + 0 + ' ' + 0 + ' ' +\n 'm -' + r + ', 0'+' ' +\n 'a ' + r +','+r+' 0 1,0 '+ (r*2) +',0 '+\n 'a ' + r +','+r+' 0 1,0 -'+ (r*2) +',0'\n ;\n // return 'm' + startX + ',' + nodeSize + ' ' +\n // 'a' + r + ',' + r + ' 0 0 0 ' + (2*r) + ',0';\n })\n ;\n\nnode.call(d3.drag()\n .on(\"start\", dragstarted)\n .on(\"drag\", dragged)\n .on(\"end\", dragended))\n .on(\"click\", function(d, idx, nodes){\n let node = nodes[idx];\n selectNode(idx, node, d);\n })\n .on('mouseover', function(n, nIdx){\n link.each(function(l,idx,linkEls,q){\n // set nodes 'visible'/highlighted when linked to active node\n if(l.source == n || l.target == n) {\n linkEls[idx].classList.add('hoverLink');\n }\n });\n })\n .on('mouseout', function(){\n let hoverLinkEls = document.getElementsByClassName('hoverLink');\n while(hoverLinkEls.length > 0){\n hoverLinkEls[0].classList.remove('hoverLink');\n }\n });\n\n// svg.call(d3.drag()\n// .on(\"start\", function(d){\n// if(d3.event.sourceEvent.type == 'touchstart' && d3.event.sourceEvent.touches.length > 1) {\n// } else {\n// d3.event.sourceEvent.stopPropagation();\n// svg.node().classList.add(\"dragging\");\n// }\n// })\n// .on(\"drag\", function(){\n// moveViewboxPx(d3.event.dx, d3.event.dy);\n// })\n// .on(\"end\", function(){\n// svg.node().classList.remove(\"dragging\");\n// }));\nsvg.call(d3.zoom()\n .scaleExtent([0.3,3])\n .on(\"start\", function(){\n svg.node().classList.add(\"dragging\");\n })\n .on(\"end\", function(){\n svg.node().classList.remove(\"dragging\");\n })\n .on(\"zoom\", function(a,b,c){\n container.attr(\"transform\", d3.event.transform);\n })\n);\n\n// svg.call(d3.zoom.transform, d3.zoomIdentity);\n\nnode.append('circle')\n .attr(\"r\", (d) => getSizeForNode(d))\n .attr(\"class\", \"nodeBg\")\n ;\nnode.append('circle')\n .attr(\"r\", (d) => getSizeForNode(d) * 1.08) // nodeSize + margin\n .attr(\"class\", \"highlightCircle\")\n ;\n\nnode.append('text')\n .attr(\"class\", \"nodeType\")\n .text(function(n){\n return n['@type'];\n })\n\nnode.append('text')\n .attr(\"class\", \"nodeYear\")\n .attr(\"y\", \"22\")\n .text(function(n){\n return getNodeYear(n);\n })\n ;\nlet splitText = function(text){\n let characters = [\" \",\"-\",\"\\u00AD\"];\n let charSplitPos = {};\n let mid = Math.floor(text.length / 2);\n let splitPos = false;\n let splitPosChar = false;\n // split sentences\n for(let char of characters) {\n if(text.indexOf(char) < 0) {\n continue;\n }\n let tmid = text.substr(0,mid).lastIndexOf(char);\n if(tmid === -1) {\n tmid = text.indexOf(char);\n }\n tmid += 1; // we want to cut _after_ the character\n // console.log(\"Char\", char, tmid);\n if(splitPos === false || Math.abs(tmid-mid) < Math.abs(splitPos - mid)){\n // console.log(\"least!\");\n splitPos = tmid;\n splitPosChar = char;\n }\n }\n // console.log(\"pos\",splitPos)\n\n\n if(splitPos === false) {\n return false;\n }\n\n let text1 = text.substr(0, splitPos).trim();\n let text2 = text.substr(splitPos).trim();\n\n if(splitPosChar == \"\\u00AD\") {\n text1 += \"-\";\n }\n\n // find most equal split\n return [text1, text2];\n}\nlet nodeTitle = node.append('text')\n .attr(\"class\", \"nodeTitle\")\n .attr(\"y\", \"5\")\n ;\nnodeTitle\n // .append(\"textPath\")\n // .attr( \"xlink:href\",function(d, idx){return '#nodePath'+idx;})\n // .text(getNodeLabel)\n .each(function(node, nodes){\n let textLength;\n let self = d3.select(this);\n let titleText = getNodeLabel(node);\n let titleTexts = false;\n if(titleText.length > 20) {\n titleTexts = splitText(titleText);\n }\n if(titleTexts !== false) {\n let tspan1 = self.append(\"tspan\")\n .text(titleTexts[0])\n .attr(\"y\", \"-10\")\n .attr(\"x\", \"0\")\n ;\n let tspan = self.append(\"tspan\")\n .text(titleTexts[1])\n .attr(\"y\", \"10\")\n .attr(\"x\", \"0\")\n ;\n let textLength1 = tspan.node().getComputedTextLength();\n let textLength2 = tspan.node().getComputedTextLength();\n textLength = Math.max(textLength1, textLength2);\n } else {\n self.text(titleText);\n textLength = self.node().getComputedTextLength();\n }\n\n // scale according to text length:\n if(textLength > getSizeForNode(node) * 2) {\n self.attr('transform', `scale(${(getSizeForNode(node) * 2) / textLength / 1.05})`);\n }\n })\n ;\n\nnode.each(function(d) {\n if(!d['https://schema.org/thumbnailUrl']) {\n return;\n }\n d3.select(this).append('svg:image')\n .attr(\"xlink:href\", d['https://schema.org/thumbnailUrl'])\n .attr(\"width\", (d) => getSizeForNode(d)*2)\n .attr(\"height\", (d) => getSizeForNode(d)* 2)\n .attr(\"transform\",(d) => \"translate(-\"+getSizeForNode(d)+\" -\"+getSizeForNode(d)+\")\")\n .attr(\"clip-path\",\"url(#clipNodeImage)\")\n .attr(\"preserveAspectRatio\",\"xMidYMid slice\")\n ;\n });\nnode.each(function(d) {\n if(d['@type'] !== 'https://schema.org/VideoObject') {\n return;\n }\n const size = getSizeForNode(d);\n d3.select(this).append('svg:polygon')\n .attr('points', \"-10,-10, -10,10, 10,0\")\n .attr(\"class\",\"play\")\n ;\n });\n\nsimulation\n .nodes(graph.nodes)\n .on(\"tick\", ticked);\n\nsimulation.force(\"link\")\n .links(graph.links)\n .distance(function(l){\n switch (l.name) {\n // case 'publishedAt':\n // return 200;\n // case 'image':\n // return 200;\n default:\n return 300;\n }\n }) // distance between the nodes / link length\n // .charge(-100)\n;\n\n// run on each draw\nfunction ticked() {\n graph.nodes.forEach(function (d, idx) {\n d.leftX = d.rightX = d.x;\n\n // fix first node on center\n // if(idx === 0) {\n // d.fx = width/2;\n // d.fy = height/2;\n // return;\n // }\n });\n\n linkLine.each(function (d) {\n var sourceX, targetX, midX, dx, dy, angle;\n\n // This mess makes the arrows exactly perfect.\n // thanks to http://bl.ocks.org/curran/9b73eb564c1c8a3d8f3ab207de364bf4\n if( d.source.rightX < d.target.leftX ){\n sourceX = d.source.rightX;\n targetX = d.target.leftX;\n } else if( d.target.rightX < d.source.leftX ){\n targetX = d.target.rightX;\n sourceX = d.source.leftX;\n } else if (d.target.isCircle) {\n targetX = sourceX = d.target.x;\n } else if (d.source.isCircle) {\n targetX = sourceX = d.source.x;\n } else {\n midX = (d.source.x + d.target.x) / 2;\n if(midX > d.target.rightX){\n midX = d.target.rightX;\n } else if(midX > d.source.rightX){\n midX = d.source.rightX;\n } else if(midX < d.target.leftX){\n midX = d.target.leftX;\n } else if(midX < d.source.leftX){\n midX = d.source.leftX;\n }\n targetX = sourceX = midX;\n }\n\n dx = targetX - sourceX;\n dy = d.target.y - d.source.y;\n angle = Math.atan2(dx, dy);\n\n /* DISABLED\n srcSize = (typeof nodePositions[d.source.index] != 'undefined') ? selectedNodeSize : nodeSize;\n tgtSize = (typeof nodePositions[d.target.index] != 'undefined') ? selectedNodeSize : nodeSize;\n */\n let srcSize = getSizeForNode(d.source)+3.2;\n let tgtSize = getSizeForNode(d.target)+3.2;\n\n // Compute the line endpoint such that the arrow\n // is touching the edge of the node rectangle perfectly.\n d.sourceX = sourceX + Math.sin(angle) * srcSize;\n d.targetX = targetX - Math.sin(angle) * tgtSize;\n d.sourceY = d.source.y + Math.cos(angle) * srcSize;\n d.targetY = d.target.y - Math.cos(angle) * tgtSize;\n })\n .attr(\"x1\", function(d) { return d.sourceX; })\n .attr(\"y1\", function(d) { return d.sourceY; })\n .attr(\"x2\", function(d) { return d.targetX; })\n .attr(\"y2\", function(d) { return d.targetY; });\n linkText.attr(\"transform\", function(d){\n let dx = (d.target.x - d.source.x) /2;\n let dy = (d.target.y - d.source.y) /2;\n let x = d.source.x + dx;\n let y = d.source.y + dy;\n let deg = Math.atan(dy / dx) * 180 / Math.PI;\n // if dx/dy == 0/0 -> deg == NaN\n if(isNaN(deg)) {\n return \"\";\n }\n return \"translate(\"+x+\" \"+y+\") rotate(\"+deg+\") translate(0, -10)\";\n });\n\n node.attr(\"transform\", function(d) { return \"translate(\" + d.x + \",\" + d.y + \")\"; });\n}\n\nfunction dragstarted(d,idx,nodes) {\n if (!d3.event.active) simulation.alphaTarget(0.3).restart();\n let nodeEl = nodes[idx];\n d.fx = d.x;\n d.fy = d.y;\n // nodeEl.style.fill = '#00f';\n nodeEl.classList.add('drag');\n}\n\n// use to validate drag\n// function validate(x, a, b) {\n// if (x =< a) return a;\n// return b;\n// }\n\nfunction dragged(d, idx) {\n d.fx = d3.event.x;\n d.fy = d3.event.y;\n}\n\nfunction dragended(d, idx, nodes) {\n if (!d3.event.active) simulation.alphaTarget(0);\n let nodeEl = nodes[idx];\n d.fx = null;\n d.fy = null;\n nodeEl.classList.remove('drag');\n}\n\nfunction moveViewboxPx(dx, dy){\n let viewBox = svg.attr(\"viewBox\").split(\" \").map(parseFloat);\n viewBox[0] -= dx * 1;\n viewBox[1] -= dy * 1;\n svg.attr(\"viewBox\", viewBox.join(\" \"));\n}\n\n// start by selecting the first node :-)\n// selectNode(currentNodeIdx+1);\n// positionNodesInCenter(currentNodeIdx);\n\nif(location.pathname.startsWith('/@type/')) {\n for(let t in types) {\n if(getDisplayAttr(t) == location.pathname.substr(7)) {\n centerByType(t, false);\n }\n }\n} else{\n let startNodeId = location.search.startsWith(\"?id=\") ? location.search.substr(4) : 'https://rubenvandeven.com'+location.pathname;\n let firstNode = graph['nodes'].find(n => n['@id'] === startNodeId);\n selectNode(graph['nodes'].indexOf(firstNode), false);\n}\n\n\n\n// closeDetails(); // hide details at first\n// positionNodesInCenter(currentNodeIdx+1);\n\n// setTimeout(function(){\n // document.body.classList.add('graphInitialised');\n// }, 10);\n\nlet initPlaceholder = document.getElementById('initPlaceholder');\nsvg.node().removeChild(initPlaceholder);\nsetTimeout(function(){\n graphInitialised = true;\n document.body.classList.add('graphInitialised');\n }, 500);\n}\n\n\n// Detect request animation frame\nvar reqAnimFrame = window.requestAnimationFrame ||\n window.webkitRequestAnimationFrame ||\n window.mozRequestAnimationFrame ||\n window.msRequestAnimationFrame ||\n window.oRequestAnimationFrame ||\n // IE Fallback, you can even fallback to onscroll\n function(callback){ window.setTimeout(callback, 1000/60) };\n// all credits go to https://stackoverflow.com/a/26798337\nfunction scrollToY(scrollTargetY, speed, easing, finishFunction) {\n // scrollTargetY: the target scrollY property of the window\n // speed: time in pixels per second\n // easing: easing equation to use\n\n var scrollY = window.scrollY,\n scrollTargetY = scrollTargetY || 0,\n speed = speed || 2000,\n easing = easing || 'easeOutSine',\n currentTime = 0,\n finishFunction = finishFunction || false;\n\n // min time .1, max time .8 seconds\n let time = Math.max(.1, Math.min(Math.abs(scrollY - scrollTargetY) / speed, .8));\n\n // easing equations from https://github.com/danro/easing-js/blob/master/easing.js\n let PI_D2 = Math.PI / 2,\n easingEquations = {\n easeOutSine: function (pos) {\n return Math.sin(pos * (Math.PI / 2));\n },\n easeInOutSine: function (pos) {\n return (-0.5 * (Math.cos(Math.PI * pos) - 1));\n },\n easeInOutQuint: function (pos) {\n if ((pos /= 0.5) < 1) {\n return 0.5 * Math.pow(pos, 5);\n }\n return 0.5 * (Math.pow((pos - 2), 5) + 2);\n }\n };\n\n // add animation loop\n function tick() {\n currentTime += 1 / 60;\n\n var p = currentTime / time;\n var t = easingEquations[easing](p);\n\n if (p < 1) {\n reqAnimFrame(tick);\n\n window.scrollTo(0, scrollY + ((scrollTargetY - scrollY) * t));\n } else {\n window.scrollTo(0, scrollTargetY);\n if(finishFunction) {\n finishFunction();\n }\n }\n }\n\n // call it once to get started\n tick();\n}\n"]} \ No newline at end of file +{"version":3,"sources":["portfolio.js"],"names":["data","graph","getLabelAttribute","node","getNodeLabel","label","getNodeYear","n","length","substr","match","getDisplayAttr","attr","replace","jsonLdToGraph","nodeId","nodes","links","i","nodeAttr","Array","isArray","key","currentId","name","Object","keys","source","target","nodeMap","linkMap","breadcrumbs","weights","requestPromise","fetch","then","r","json","rankingPromise","dx","dy","dxAbs","Math","abs","values","dyAbs","pow","_iteratorNormalCompletion","_didIteratorError","_iteratorError","undefined","_step","_iterator","Symbol","iterator","next","done","link","value","id","err","return","Promise","hasOwnProperty","startGraph","nodeSize","selectedNodeSize","firstNodeId","getSizeForNode","min","createBreadcrumbs","srcId","crumbs","newNextIds","_iteratorNormalCompletion3","_didIteratorError3","_iteratorError3","_step3","_iterator3","nextIds","newPath","slice","push","nextSrcIds","_iteratorNormalCompletion2","_didIteratorError2","_iteratorError2","_step2","_iterator2","concat","nodePositions","nodeIdx","currentNodePositionRadius","type","types","document","showMoreTypeLinksEl","getElementById","moreTypeLinksEl","typeCounts","map","sort","first","second","typeCountIdx","typeName","typeLinkEl","createElement","typeLinkAEl","typeLinkCountEl","add","title","addEventListener","centerByType","typeNodeEls","getElementsByClassName","_iteratorNormalCompletion7","_didIteratorError7","_iteratorError7","_step7","_iterator7","createBreadcrumbLayer","_iteratorNormalCompletion8","_didIteratorError8","_iteratorError8","_step8","_iterator8","classList","remove","append","typeLinksEl","appendChild","_loop","body","e","preventDefault","stopPropagation","removeEventListener","hideMoreTypeLinks","getViewbox","svg","split","parseFloat","positionNodesInCenter","idxs","setViewboxForceCenter","_typeof","positionNodesInCircle","forceCx","forceCy","each","d","nIdx","nodeEls","simulation","alpha","relNode","el","titleEl","innerHTML","idx","indexOf","selectNode","typeEl","scrollToY","nodeDetailEl","lastChild","relUp","restart","nodeDetailScalerEl","getZoomValues","drag","viewBox","width","window","innerWidth","clientX","max","nodeDetails","breadcrumbsEl","_iteratorNormalCompletion4","_didIteratorError4","_iteratorError4","_step4","_iterator4","crumbNodeId","crumbEl","nodeYear","updateHistory","crumbWrapEl","pageTitles","_loop2","nodeDatum","titleAttr","deselectNode","history","pushState","replaceState","selectedNodeTransition","skipNodeAttributes","listEl","setDetails","poster","valueHtml","relsEl","relDown","a","b","videoType","rel","createRelationshipEl","ddEl","attrEl","join","createLinkMap","d3","forceLink","strength","force","forceManyBody","container","selectAll","enter","l","linkLine","linkText","slashpos","baseClasses","lastIndexOf","typeNodeEl","height","zoom","forceSimulation","forceCollide","cos","stepSize","transition","ease","easeLinear","currentCrumbs","linkedIdxs","nodeEl","linkEls","getElementsByTagName","setAttribute","filter","fnodeIdx","classed","posTrg","posSrc","event","state","firstNode","find","closeDetails","transform","baseVal","_iteratorNormalCompletion5","_didIteratorError5","_iteratorError5","_step5","_iterator5","zoomContainer","SVGTransform","SVG_TRANSFORM_TRANSLATE","matrix","f","SVG_TRANSFORM_SCALE","scale","k","fx","fy","graphInitialised","startsWith","vy","vx","x","y","inCircle","random","call","textLength","self","on","active","alphaTarget","select","q","hoverLinkEls","scaleExtent","c","text","nodePath","textLength2","this","titleText","titleTexts","startX","splitPosChar","_iteratorNormalCompletion6","_didIteratorError6","_iteratorError6","_step6","_iterator6","char","mid","tmid","splitPos","text1","trim","splitText","textLength1","tspan","getComputedTextLength","forEach","leftX","rightX","targetX","sourceX","isCircle","midX","angle","atan2","srcSize","tgtSize","sin","nodeTitle","targetY","sourceY","PI","isNaN","distance","location","pathname","t","startNodeId","search","setTimeout","requestAnimationFrame","callback","speed","easing","finishFunction","currentTime","time","scrollY","scrollTargetY","easingEquations","pos","easeInOutQuint","deg","p","reqAnimFrame","tick","scrollTo"],"mappings":"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","file":"portfolio.min.js","sourcesContent":["var data;\n\nfunction getLabelAttribute(node) {\n if(typeof node['https://schema.org/name'] !== \"undefined\"){\n return 'https://schema.org/name';\n }\n switch (node['@type']) {\n case \"https://schema.org/WebSite\":\n if(typeof node['https://schema.org/url'] !== \"undefined\") {return 'https://schema.org/url';}\n break;\n case \"https://schema.org/ImageObject\":\n if(typeof node['https://schema.org/caption'] !== \"undefined\") {return 'https://schema.org/caption';}\n if(typeof node['https://schema.org/contentUrl'] !== \"undefined\") {return 'https://schema.org/contentUrl';}\n break;\n case \"https://schema.org/PostalAddress\":\n if(typeof node['https://schema.org/addressLocality'] !== \"undefined\") {return 'https://schema.org/addressLocality';}\n break;\n case \"https://schema.org/OrganizationRole\":\n if (typeof node['https://schema.org/roleName'] !== \"undefined\") {\n return 'https://schema.org/roleName';\n }\n break;\n }\n return '@id';\n}\nfunction getNodeLabel(node){\n let labelAttr = getLabelAttribute(node);\n let label = node[labelAttr];\n if(typeof label == \"undefined\") label = node[\"@id\"];\n if(typeof label == \"undefined\") label = \"\";\n return label;\n}\nfunction getNodeYear(n){\n if(typeof n['https://schema.org/dateCreated'] !== 'undefined') {\n if(n['https://schema.org/dateCreated'].length == 9){\n return n['https://schema.org/dateCreated'];\n }\n return n['https://schema.org/dateCreated'].substr(0,4);\n }\n if(typeof n['https://schema.org/datePublished'] !== 'undefined') {\n return n['https://schema.org/datePublished'].substr(0,4);\n }\n if(typeof n['https://schema.org/startDate'] !== 'undefined') {\n // console.log(n['https://schema.org/startDate']);\n const year = n['https://schema.org/startDate'].substr(0,4);\n return year;\n }\n if(typeof n['https://schema.org/endDate'] !== 'undefined') {\n return n['https://schema.org/endDate'].substr(0,4);\n }\n if(typeof n['https://schema.org/foundingDate'] !== 'undefined') {\n return n['https://schema.org/foundingDate'].substr(0,4);\n }\n if(typeof n['https://schema.org/temporalCoverage'] !== 'undefined') {\n if(n['https://schema.org/temporalCoverage'].match(/\\d{4}-\\d{4}/)) {\n return n['https://schema.org/temporalCoverage'].substr(5,4);\n }\n }\n return null;\n}\nfunction getDisplayAttr(attr) {\n return attr.replace(/.*[#|\\/]/, \"\");\n}\n/**\nTransform a flattened jsonld into a d3 compatible graph\n@param Object data flattened jsonld data\n@return Object graph has keys \"nodes\" and \"links\"\n*/\nfunction jsonLdToGraph(data){\n let nodes = {};\n let links = [];\n\n // collect all nodes\n for(let nodeId in data){\n // data[nodeId][\"@type\"][0] = data[nodeId][\"@type\"][0];\n nodes[data[nodeId][\"@id\"]] = data[nodeId];\n }\n\n // collect all links (separate loop as we need to check nodes)\n for(let nodeId in data) {\n let node = data[nodeId];\n let currentId = node[\"@id\"];\n for(let key in node){\n let nodeAttr = Array.isArray(node[key]) ? node[key] : [node[key]];\n // // relations should always be lists (eases assumptions)\n // if(typeof node[key] !== \"Array\" && typeof node[key]['id'] !== \"undefined\") {\n // node[key] = [node[key]];\n // }\n // every attribute is an Array after flatten(), loop them\n for(let i in nodeAttr) {\n if(key !== \"@id\" && typeof nodeAttr[i] === \"string\" && nodes[nodeAttr[i]]) {\n links[links.length] = {\n \"source\": currentId,\n \"target\": nodeAttr[i],\n \"name\": key\n };\n }\n else if(typeof nodeAttr[i][\"@id\"] !== \"undefined\") {\n // if there is just one item, flatten/expand has turned urls in objects with just an id\n // reverse this, as we don't want these separate for this project\n if (Object.keys(nodeAttr[i]).length == 1 && typeof nodes[nodeAttr[i][\"@id\"]] === \"undefined\") {\n // skip\n // nodeAttr = nodeAttr[i][\"id\"];\n } else {\n links[links.length] = {\n \"source\": currentId,\n \"target\": nodeAttr[i][\"@id\"],\n \"name\": key\n };\n }\n }\n }\n }\n }\n return {\n \"nodes\": Object.values(nodes),\n \"links\": links\n };\n}\n\nvar graph;\n// map nodes to their ID\nvar nodeMap = {};\nvar linkMap = {};\nvar breadcrumbs = {};\nvar weights = {};\n\n// load the flattened jsonld file\nconst requestPromise = fetch('/assets/js/rubenvandeven.jsonld').then(r => r.json());\nconst rankingPromise = fetch('/assets/js/ranking.json').then(r => r.json());\n\nPromise.all([requestPromise, rankingPromise])\n .then(values => {\n if(values[0].hasOwnProperty('@graph')) {\n data = values[0];\n weights = values[1];\n } else {\n data = values[1];\n weights = values[0];\n }\n graph = jsonLdToGraph(data['@graph']);\n // create a map of nodes by id.\n for(let i in graph.nodes) {\n nodeMap[graph.nodes[i]['@id']] = graph.nodes[i];\n }\n startGraph(graph);\n });\n\nfunction inCircle(dx, dy, r) {\n // fastest check if in circle: https://stackoverflow.com/a/7227057\n let dxAbs = Math.abs(dx);\n let dyAbs = Math.abs(dy);\n\n if(dxAbs > r || dyAbs > r) {\n return false;\n } else if(dxAbs + dyAbs <= r){\n return true;\n } else if( Math.pow(dx,2) + Math.pow(dy, 2) <= Math.pow(r,2)){\n return true;\n } else {\n return false;\n }\n}\n\nfunction createLinkMap(graph) {\n let linkMap = {};\n for(let link of graph['links']){\n if(typeof linkMap[link['source']] == 'undefined') {\n linkMap[link['source']] = [];\n }\n linkMap[link['source']][linkMap[link['source']].length] = {'id': link['target'], 'name': link['name']};\n\n\n if(typeof linkMap[link['target']] == 'undefined') {\n linkMap[link['target']] = [];\n }\n\n linkMap[link['target']][linkMap[link['target']].length] = {'id': link['source'], 'name': link['name']};\n }\n return linkMap;\n}\n\n\n // config\nvar nodeSize = 40;\nvar selectedNodeSize = 140;\nvar firstNodeId = \"https://rubenvandeven.com/\";\n\nfunction getSizeForNode(node) {\n if(node.hasOwnProperty('https://schema.org/thumbnailUrl'))\n return nodeSize;\n if(weights[node['@id']])\n return nodeSize * weights[node['@id']];\n if(node['@id'] == firstNodeId)\n return nodeSize*1.2;\n // everynode has at least one link. these should equal 1\n return nodeSize * (.7 + Math.min(20, linkMap[node['@id']].length) / 40)\n return nodeSize;\n}\n\n// TODO: make sure, 'shortest' path is favoured.\nfunction createBreadcrumbs(linkMap, srcId) {\n let crumbs = {};\n\n let createBreadcrumbLayer = function(srcId) {\n let path = crumbs[srcId];\n let newPath = path.slice();\n newPath.push(srcId);\n\n let nextSrcIds = [];\n for (let link of linkMap[srcId]) {\n if(typeof crumbs[link['id']] !== 'undefined') continue;\n crumbs[link['id']] = newPath;\n nextSrcIds.push(link['id']);\n }\n\n return nextSrcIds;\n }\n crumbs[srcId] = [];\n let nextIds = [srcId];\n while(nextIds.length > 0) {\n let newNextIds = [];\n for (let nextId of nextIds) {\n let r = createBreadcrumbLayer(nextId);\n newNextIds = newNextIds.concat(r);\n }\n nextIds = newNextIds;\n }\n return crumbs;\n}\n\nvar nodePositions = {};\nfunction startGraph(graph){\n\n\n// set some vars\nvar currentNodeIdx = 0;\nvar currentNodePositionRadius = 0;\nvar types = {};\n\nlinkMap = createLinkMap(graph);\nbreadcrumbs = createBreadcrumbs(linkMap, firstNodeId);\n\nfor (let nodeIdx in graph['nodes']) {\n let type = graph['nodes'][nodeIdx][\"@type\"];\n if(typeof types[type] == 'undefined') {\n types[type] = [];\n }\n types[type].push(nodeIdx);\n}\nvar graphControlsEl = document.getElementById('graphControls');\nvar typeLinksEl = document.getElementById('typeLinks');\nvar showMoreTypeLinksEl = document.getElementById('showMoreTypeLinks');\nvar moreTypeLinksEl = document.getElementById('moreTypeLinks');\nvar relLinksEl = document.getElementById('relLinks');\n\n// sort types by count:\nvar typeCounts = Object.keys(types).map(function(key) {\n return [key, types[key].length];\n});\ntypeCounts.sort(function(first, second) {\n return second[1] - first[1];\n});\n\n// make controls\nlet i = 0;\nfor (let typeCountIdx in typeCounts) {\n let typeName = typeCounts[typeCountIdx][0];\n let typeLinkEl = document.createElement(\"li\");\n let typeLinkAEl = document.createElement(\"a\");\n let typeLinkCountEl = document.createElement(\"span\");\n typeLinkCountEl.innerHTML = typeCounts[typeCountIdx][1];\n typeLinkCountEl.classList.add('typeCount');\n typeLinkAEl.innerHTML = getDisplayAttr(typeName);\n typeLinkAEl.title = typeName;\n typeLinkAEl.addEventListener('click', function(){\n centerByType(typeName);\n // positionNodesInCenter(types[typeName]);\n });\n typeLinkAEl.addEventListener('mouseover', function() {\n let typeNodeEls = document.getElementsByClassName(typeName);\n for(let typeNodeEl of typeNodeEls) {\n typeNodeEl.classList.add('typeHighlight');\n }\n });\n typeLinkAEl.addEventListener('mouseout', function() {\n let typeNodeEls = document.getElementsByClassName(typeName);\n for(let typeNodeEl of typeNodeEls) {\n typeNodeEl.classList.remove('typeHighlight');\n }\n });\n typeLinkEl.append(typeLinkAEl);\n typeLinkEl.append(typeLinkCountEl);\n (i < 5 ? typeLinksEl: moreTypeLinksEl).appendChild(typeLinkEl);\n i++;\n // typeLinksEl.appendChild(typeLinkEl);\n}\n\nshowMoreTypeLinksEl.addEventListener('click', function () {\n document.body.classList.add('showMoreLinks');\n var hideMoreTypeLinks = function(e) {\n e.preventDefault();\n e.stopPropagation();\n document.body.removeEventListener('mouseup', hideMoreTypeLinks, true);\n document.body.classList.remove('showMoreLinks');\n }\n document.body.addEventListener('mouseup', hideMoreTypeLinks, true);\n}, false)\n\n\n// make svg\nvar svg = d3.select(\"svg\"),\n width = +svg.attr(\"width\"),\n height = +svg.attr(\"height\");\nvar container = svg.append(\"g\")\n .attr(\"id\", \"container\")\n ;\n\nvar simulation = d3.forceSimulation()\n .force(\"link\", d3.forceLink().id(function(d) { return d[\"@id\"]; }).strength(.005))\n .force(\"charge\", d3.forceManyBody()) // doesn't seem necessary?\n .force(\"collision\", d3.forceCollide(function(d){\n return getSizeForNode(d) * 1.1; // avoid overlapping nodes\n }))\n // .force(\"center\", d3.forceCenter(width / 2, height / 2)) // position around center\n\n // .force(\"x\", d3.forceX())\n // .force(\"y\", d3.forceY())\n // .force(\"y\", d3.forceY())\n ;\n\n\nvar link = container.append(\"g\")\n .attr(\"class\", \"links\")\n .selectAll(\".relationship\")\n .data(graph['links'])\n .enter().append(\"g\")\n .attr(\"class\", function(l){return \"relationship \"+l.name;})\n ;\nvar linkLine = link\n // .append(\"line\");\n .append(\"line\").attr(\"marker-end\", \"url(#arrowHead)\")\n ;\nvar linkText = link\n .append(\"text\")\n .text(function(l){\n // l == Object { source: \"https://rubenvandeven.com/#codesandmodes\", target: \"_:b34\", name: \"https://schema.org/location\" }\n return getDisplayAttr(l.name);\n })\n ;\n\n var node = container.append(\"g\")\n .attr(\"class\", \"nodes\")\n .selectAll(\".node\")\n .data(graph.nodes)\n .enter().append(\"g\")\n .attr(\"class\", function(d) {\n let baseClasses = 'node ' + d['@type'];\n if(d['@type']) {\n let slashpos = d['@type'].lastIndexOf('/');\n if(slashpos > -1) {\n baseClasses += ' ' + d['@type'].substr(slashpos + 1);\n }\n }\n return baseClasses;\n })\n ;\nvar getViewbox = function() {\n return svg.attr(\"viewBox\").split(\" \").map(parseFloat);\n}\nvar positionNodesInCenter = function(idxs) {\n setViewboxForceCenter(); // sets forceCx & forceCy\n if(typeof idxs == \"object\" && idxs !== null && idxs.length == 1) {\n idxs = idxs[0];\n }\n\n nodePositions = {}; // reset\n if(idxs === null) {\n return;\n }\n else if(typeof idxs == \"object\") {\n // array or object -> each\n // calculate grid:\n // let itemsX = 4;\n // let itemsY = Math.ceil(idxs.length/itemsX);\n // console.log(itemsX,itemsY);\n // let rowDiffX = viewBox[3] * (1/(itemsX+1));\n // let rowDiffY = viewBox[2] * (1/(itemsY+1));\n // console.log(rowDiffX, rowDiffY);\n // for (var i = 0; i < idxs.length; i++) {\n // nodePositions[idxs[i]] = [\n // cx - itemsX/2*rowDiffX + rowDiffX * ((i % itemsX)),\n // cy - itemsY/2*rowDiffY + rowDiffY * (Math.floor(i / itemsX))\n // ];\n // }\n positionNodesInCircle(idxs);\n // console.log(nodePositions);\n }\n else{\n nodePositions[idxs] = [\n forceCx,\n forceCy\n ];\n // console.log(\"singleNode\", idxs, nodePositions);\n }\n\n node.each(function(d,nIdx,nodeEls){\n if(typeof nodePositions[nIdx] != 'undefined') {\n nodeEls[nIdx].classList.add('centeredNode');\n nodeEls[nIdx].classList.add('visibleNode');\n } else {\n nodeEls[nIdx].classList.remove('centeredNode');\n nodeEls[nIdx].classList.remove('visibleNode');\n }\n });\n\n // restart animation (they call that 'alpha' in d3 force)\n simulation.alpha(1);\n simulation.restart();\n}\nvar positionNodesInCircle = function(idxs, r) {\n let viewBox = getViewbox();\n let zoom = getZoomValues();\n setViewboxForceCenter(); // sets forceCx & forceCy\n if(typeof r == 'undefined') {\n if(idxs.length == 1) {\n r = viewBox[2] / 6;\n } else {\n r = viewBox[2] / (4 + Math.max(0, 2.5 - idxs.length));\n }\n }\n currentNodePositionRadius = r;\n let forceCx = viewBox[0] + viewBox[2]/2 - zoom['dx'];\n let forceCy = viewBox[1] + viewBox[3]/2 - zoom['dy'];\n\n let stepSize = 2*Math.PI / idxs.length;\n\n for (var i = 0; i < idxs.length; i++) {\n nodePositions[idxs[i]] = [\n forceCx + Math.sin(stepSize * i) * r,\n forceCy + Math.cos(stepSize * i) * r\n ];\n }\n\n // restart animation (they call that 'alpha' in d3 force)\n simulation.alpha(1);\n simulation.restart();\n}\nvar centerByType = function(types, updateHistory) {\n if(typeof updateHistory == 'undefined') {\n updateHistory = true;\n }\n if(!Array.isArray(types)) {\n types = [types];\n }\n let idxs = [];\n for(let idx in graph.nodes) {\n if(types.indexOf(graph.nodes[idx]['@type']) > -1) {\n idxs[idxs.length] = idx;\n }\n }\n deselectNode();\n if(updateHistory) {\n // TODO: working\n // console.log(types[0], getDisplayAttr(types[0]),types.map(getDisplayAttr));\n history.pushState({types: types}, \"\", \"/@type/\"+(types.map(getDisplayAttr).join(\"+\")));\n } else {\n history.replaceState({types: types}, \"\", \"/@type/\"+(types.map(getDisplayAttr).join(\"+\")));\n }\n positionNodesInCenter(idxs.length ? idxs : null);\n}\n\nvar selectedNodeTransition = d3.transition()\n .duration(750)\n .ease(d3.easeLinear);\n\nvar nodeDetailEl = document.getElementById(\"nodeDetails\");\n\nvar createRelationshipEl = function(relNode, i) {\n let el = document.createElement(\"dd\");\n el.classList.add('relLink');\n let titleEl = document.createElement('a');\n titleEl.innerHTML = getNodeLabel(relNode)\n let year = getNodeYear(relNode);\n if(year !== null) {\n titleEl.innerHTML += `${getNodeYear(relNode)}`;\n }\n titleEl.classList.add('nodeTitle');\n titleEl.classList.add('nodeTitleNr'+i);\n titleEl.addEventListener('click',function(e){\n let idx = graph.nodes.indexOf(relNode);\n selectNode(idx);\n });\n let typeEl = document.createElement('a');\n typeEl.classList.add('nodeType');\n typeEl.innerHTML = getDisplayAttr(relNode['@type']);\n typeEl.title = relNode['@type'];\n typeEl.addEventListener('click',function(e){\n centerByType(relNode['@type']);\n });\n el.appendChild(titleEl);\n el.appendChild(typeEl);\n return el;\n}\n\nvar setDetails = function(nodeDatum, nodeIdx) {\n document.body.classList.add(\"detailsOpen\");\n scrollToY(0, 4000);\n while (nodeDetailEl.hasChildNodes()) {\n nodeDetailEl.removeChild(nodeDetailEl.lastChild);\n }\n\n // TODO: replace relUp & relDown with linkMap\n let relUp = [];\n let relDown = [];\n let pageTitles = [];\n let nodeDetailScalerEl = document.createElement('div');\n // nodeDetailScalerEl.innerHTML = `
`;\n nodeDetailScalerEl.id = 'nodeDetailsScaler';\n nodeDetailScalerEl.addEventListener('mousedown', function(e){\n // console.log('go');\n let drag = function(e) {\n // 5px for padding\n nodeDetailEl.style.width = (window.innerWidth - e.clientX + 5) +'px';\n }\n document.body.addEventListener('mousemove', drag);\n document.body.addEventListener('mouseup', function(){\n document.body.removeEventListener('mousemove', drag);\n });\n });\n nodeDetails.appendChild(nodeDetailScalerEl);\n\n let breadcrumbsEl = document.createElement('ul');\n breadcrumbsEl.classList.add('breadcrumbs');\n for(let crumbNodeId of breadcrumbs[nodeDatum['@id']]) {\n let crumbWrapEl = document.createElement('li');\n let crumbEl = document.createElement('span');\n crumbEl.classList.add('crumb');\n crumbEl.addEventListener('click', function(e){\n let idx = graph.nodes.indexOf(nodeMap[crumbNodeId]);\n selectNode(idx);\n });\n crumbEl.innerHTML = `${getNodeLabel(nodeMap[crumbNodeId])}`;\n let nodeYear = getNodeYear(nodeMap[crumbNodeId]);\n if(nodeYear !== null) {\n crumbEl.innerHTML += `${nodeYear}`;\n }\n crumbWrapEl.appendChild(crumbEl);\n breadcrumbsEl.appendChild(crumbWrapEl);\n pageTitles.push(getNodeLabel(nodeMap[crumbNodeId]));\n }\n nodeDetailEl.appendChild(breadcrumbsEl);\n pageTitles.push(getNodeLabel(nodeDatum));\n\n let titleAttr = getLabelAttribute(nodeDatum);\n let titleEl = document.createElement('h2');\n titleEl.innerHTML = getNodeLabel(nodeDatum);\n\n let typeEl = document.createElement('span');\n typeEl.classList.add('nodeType')\n typeEl.innerHTML = getDisplayAttr(nodeDatum['@type']);\n typeEl.title = nodeDatum['@type']\n typeEl.addEventListener('click',function(e){\n centerByType(nodeDatum['@type']);\n });\n titleEl.appendChild(typeEl);\n nodeDetailEl.appendChild(titleEl);\n\n let listEl = document.createElement(\"dl\");\n // listEl.innerHTML += `
type
${nodeDatum['@type']}
`;\n\n let skipNodeAttributes = [\n '@id','x','y','index','@type','vy','vx','fx','fy','leftX','rightX'\n ];\n if(titleAttr !== 'https://schema.org/contentUrl') {\n skipNodeAttributes[skipNodeAttributes.length] = titleAttr;\n }\n for (let attr in nodeDatum) {\n if(skipNodeAttributes.indexOf(attr) != -1) {\n continue;\n }\n\n // approach all as array\n let nodeAttr = Array.isArray(nodeDatum[attr]) ? nodeDatum[attr] : [nodeDatum[attr]];\n for (let i in nodeAttr) {\n // check if relationship:\n if(typeof nodeAttr[i] === \"string\" && nodeMap[nodeAttr[i]]) {\n continue;\n } else if(typeof nodeAttr[i]['@id'] !== 'undefined') {\n continue;\n }\n if(attr == 'https://schema.org/url' || attr == 'http://www.w3.org/2000/01/rdf-schema#seeAlso') {\n listEl.innerHTML += `
${getDisplayAttr(attr)}
${nodeAttr[i]}
`;\n } else if(attr == 'https://schema.org/embedUrl') {\n listEl.innerHTML += `
${getDisplayAttr(attr)}
${nodeAttr[i]}
`;\n listEl.innerHTML += `
`;\n } else if(attr == 'https://schema.org/contentUrl') {\n listEl.innerHTML += `
${getDisplayAttr(attr)}
${nodeAttr[i]}
`;\n if(nodeDatum['@type'] == 'https://schema.org/VideoObject') {\n // console.log(nodeDatum, nodeAttr);\n // let videoType = nodeDatum['https://schema.org/encodingFormat'] ? `type='${nodeDatum['https://schema.org/encodingFormat']}'`: \"\";\n let videoType=\"\";\n let poster = nodeDatum['https://schema.org/thumbnailUrl'] ? `poster='${nodeDatum['https://schema.org/thumbnailUrl']}'`: \"\";\n listEl.innerHTML += `
`;\n } else{\n listEl.innerHTML += `
`;\n }\n } else {\n let valueHtml = nodeAttr[i].replace(/\\n/g,\"
\");\n listEl.innerHTML += `
${getDisplayAttr(attr)}
${valueHtml}
`;\n }\n }\n }\n nodeDetailEl.appendChild(listEl);\n\n // let relTitleEl = document.createElement(\"h4\");\n // relTitleEl.classList.add('linkTitle');\n // relTitleEl.innerHTML = \"links\";\n // nodeDetailEl.appendChild(relTitleEl);\n\n let relsEl = document.createElement(\"dl\");\n // collect relationships\n for (var i = 0; i < graph.links.length; i++) {\n let link = graph.links[i];\n if(link['source']['@id'] == nodeDatum['@id']) {\n if(typeof relDown[link['name']] == \"undefined\") {\n relDown[link['name']] = [];\n }\n relDown[link['name']][relDown[link['name']].length] = link['target'];\n }\n if(link['target']['@id'] == nodeDatum['@id']) {\n if(typeof relUp[link['name']] == \"undefined\") {\n relUp[link['name']] = [];\n }\n relUp[link['name']][relUp[link['name']].length] = link['source'];\n }\n }\n\n // relationships / links incomming
\n for(let attr in relDown) {\n let attrEl = document.createElement(\"dt\");\n attrEl.innerHTML = getDisplayAttr(attr);\n relsEl.appendChild(attrEl);\n\n // highest pagerank first:\n relDown[attr].sort((a,b) => weights[b['@id']] - weights[a['@id']]);\n\n for(let i in relDown[attr]) {\n let rel = relDown[attr][i];\n relsEl.appendChild(createRelationshipEl(rel));\n if(typeof rel['https://schema.org/contentUrl'] != 'undefined') {\n let ddEl = document.createElement('dd')\n ddEl.classList.add('dd-contentobject');\n if(rel['@type'] == 'https://schema.org/VideoObject') {\n let videoType = rel['https://schema.org/encodingFormat'] ? `type='${rel['https://schema.org/encodingFormat']}'`: \"\";\n let poster = rel['https://schema.org/thumbnailUrl'] ? `poster='${rel['https://schema.org/thumbnailUrl']}'`: \"\";\n ddEl.innerHTML += ``;\n } else{\n ddEl.innerHTML = ``\n }\n relsEl.appendChild(ddEl);\n }\n }\n }\n\n // relationships / links outgoing
\n for(let attr in relUp) {\n let attrEl = document.createElement(\"dt\");\n attrEl.innerHTML = getDisplayAttr(attr);\n relsEl.appendChild(attrEl);\n\n // highest pagerank first:\n relUp[attr].sort((a,b) => weights[b['@id']] - weights[a['@id']]);\n\n for(let i in relUp[attr]) {\n let rel = relUp[attr][i];\n relsEl.appendChild(createRelationshipEl(rel, i));\n if(typeof rel['https://schema.org/contentUrl'] != 'undefined') {\n let ddEl = document.createElement('dd')\n ddEl.classList.add('dd-contentobject');\n if(rel['@type'] == 'https://schema.org/VideoObject') {\n let videoType = rel['https://schema.org/encodingFormat'] ? `type='${rel['https://schema.org/encodingFormat']}'`: \"\";\n let poster = rel['https://schema.org/thumbnailUrl'] ? `poster='${rel['https://schema.org/thumbnailUrl']}'`: \"\";\n ddEl.innerHTML += ``;\n } else{\n ddEl.innerHTML = ``\n }\n relsEl.appendChild(ddEl);\n }\n }\n }\n\n nodeDetailEl.appendChild(relsEl);\n\n node.each(function(d,nIdx,nodeEls){\n if(nIdx == nodeIdx) {\n nodeEls[nIdx].classList.add('selectedNode');\n } else {\n nodeEls[nIdx].classList.remove('selectedNode');\n }\n });\n\n // TODO: update history & title\n document.title = pageTitles.join(\" :: \");\n};\nvar closeDetails = function() {\n document.body.classList.remove(\"detailsOpen\");\n scrollToY(0, 4000); // for mobile\n}\n\n/**\n * Select a node, and center it + show details\n * @param int idx The index of the node in the graph.nodes array\n * @param Element|null nodeEl Optional, provide node element, so loop doesn't have to be used to change the Element\n * @return void\n */\nvar selectNode = function(idx, updateHistory){\n if(typeof updateHistory == 'undefined') {\n updateHistory = true;\n }\n\n let nodeEl = null;\n let nodeDatum = null;\n\n node.each(function(d,nIdx,nodeEls){\n if(nIdx == idx) {\n nodeEl = nodeEls[idx];\n nodeDatum = d;\n }\n });\n if(!nodeEl) {\n return;\n }\n\n\n if(true) { // always set history state, but replace instead of update on 'updatehistory'\n let id = null;\n if(nodeDatum['@id'].startsWith(/*location.origin*/'https://rubenvandeven.com/')){\n id = nodeDatum['@id'].substr(26);\n } else {\n id = '?id=' + nodeDatum['@id'];\n }\n\n if(updateHistory) {\n history.pushState({node: idx}, getNodeLabel(nodeDatum), \"/\"+id);\n } else {\n history.replaceState({node: idx}, getNodeLabel(nodeDatum), \"/\"+id);\n }\n }\n\n // set global var\n positionNodesInCenter(idx);\n\n let currentCrumbs = breadcrumbs[nodeDatum['@id']].slice();\n currentCrumbs[currentCrumbs.length] = nodeDatum['@id'];\n\n // set active links.\n let linkedIdxs = [];\n link.each(function(d,idx,linkEls,q){\n // set nodes 'visible'/highlighted when linked to active node\n if(d.source == nodeDatum || d.target == nodeDatum) {\n linkEls[idx].classList.add('activeLink','visibleLink');\n linkEls[idx].getElementsByTagName(\"line\")[0].setAttribute(\"marker-end\", \"url(#arrowHeadSelected)\");\n node.filter(function(a, fnodeIdx){\n let r = a['@id'] == d.source['@id'] || a['@id'] == d.target['@id']; //connected node: true/false\n if(r && linkedIdxs.indexOf(fnodeIdx) === -1){\n linkedIdxs[linkedIdxs.length] = fnodeIdx;\n }\n return r;\n }).classed('visibleNode', true);\n } else {\n linkEls[idx].classList.remove('activeLink');\n linkEls[idx].getElementsByTagName(\"line\")[0].setAttribute(\"marker-end\", \"url(#arrowHead)\");\n }\n // check if link is part of breadcrumb trail\n let posSrc = currentCrumbs.indexOf(d.source['@id']);\n let posTrg = currentCrumbs.indexOf(d.target['@id']);\n if(posSrc > -1 && posTrg > -1 && Math.abs(posSrc - posTrg) == 1) {\n linkEls[idx].classList.add('breadcrumbLink');\n linkEls[idx].getElementsByTagName(\"line\")[0].setAttribute(\"marker-end\", \"url(#arrowHeadCrumbTrail)\");\n } else {\n linkEls[idx].classList.remove('breadcrumbLink');\n }\n });\n\n let i = linkedIdxs.indexOf(idx);\n\n if(i !== -1) {\n linkedIdxs.splice(i, 1);\n }\n\n positionNodesInCircle(linkedIdxs);\n\n setDetails(nodeDatum ,idx);\n}\nvar deselectNode = function() {\n positionNodesInCenter(null);\n link.each(function(d,idx,linkEls,q){\n linkEls[idx].classList.remove('activeLink');\n linkEls[idx].classList.remove('breadcrumbLink');\n linkEls[idx].getElementsByTagName(\"line\")[0].setAttribute(\"marker-end\", \"url(#arrowHead)\")\n });\n closeDetails();\n}\n\n\nwindow.addEventListener('popstate', function(event) {\n if(event.state.hasOwnProperty('node')) {\n selectNode(event.state['node'], false);\n }\n else {\n // if not sure what to do, fall back to first node (also used to return to opening page)\n let firstNode = graph['nodes'].find(n => n['@id'] === firstNodeId);\n selectNode(graph['nodes'].indexOf(firstNode), false);\n }\n});\n\nvar forceCx, forceCy;\nvar setViewboxForceCenter = function() {\n let viewBox = getViewbox();\n let zoom = getZoomValues();\n forceCx = viewBox[0] + viewBox[2]/2 - zoom['dx'];\n forceCy = viewBox[1] + viewBox[3]/2 - zoom['dy'];\n}\n\nvar getZoomValues = function(){\n let zoomContainer = document.getElementById(\"container\");\n let dx = 0, dy = 0, scale = 1;\n if(zoomContainer.transform.baseVal.length > 0) {\n for(let transform of zoomContainer.transform.baseVal) {\n if(transform.type == SVGTransform.SVG_TRANSFORM_TRANSLATE) {\n dx += transform.matrix.e;\n dy += transform.matrix.f;\n }\n else if (transform.type == SVGTransform.SVG_TRANSFORM_SCALE) {\n scale *= transform.matrix.a; // assume simple scale\n }\n }\n }\n\n return {'dx': dx, 'dy': dy, 'scale': scale};\n}\n\nsetViewboxForceCenter(); // sets forceCx & forceCy\n\nvar graphInitialised = false;\nsimulation.force('centerActive', function force(alpha) {\n // let currentNode = node.selectAll('.detail');\n // console.log(currentNode);\n // console.log(forceCx, forceCy);\n node.each(function(d, idx, nodes){\n let n = d;\n let k = alpha * 0.1;\n n.fx = null;\n n.fy = null;\n if(typeof nodePositions[idx] != 'undefined') {\n if(graphInitialised == false) {\n n.x = nodePositions[idx][0];\n n.y = nodePositions[idx][1];\n n.vx = 0;\n n.vy = 0;\n } else {\n n.vx -= (n.x - nodePositions[idx][0]) * k * 5;\n n.vy -= (n.y - nodePositions[idx][1]) * k * 5;\n }\n } else {\n // if it's not positioned, move it out of the circle\n if(currentNodePositionRadius < 1) {\n return;\n }\n\n let dx = n.x - forceCx;\n let dy = n.y - forceCy;\n if(!inCircle(dx, dy, currentNodePositionRadius)) {\n return;\n }\n\n if(graphInitialised == false) {\n // on init, fixate items outside of circle\n n.fx = n.x + dx * (2+Math.random());\n n.fy = n.y + dy * (2+Math.random());\n } else {\n // if initialised, gradually move them outwards\n n.vx += dx * k*4;\n n.vy += dy * k*4;\n }\n }\n });\n});\n\n//path to curve the tile\nvar nodePath = node.append(\"path\")\n .attr(\"id\", function(d,idx){return \"nodePath\"+idx;})\n .attr(\"d\", function(d){\n var r = getSizeForNode(d) * 0.9;\n var startX = getSizeForNode(d);\n // M cx cy\n // m -r, 0\n // a r,r 0 1,0 (r * 2),0\n // a r,r 0 1,0 -(r * 2),0\n // return 'M' + nodeSize/2 + ' ' + nodeSize/2 + ' ' +\n return 'M' + 0 + ' ' + 0 + ' ' +\n 'm -' + r + ', 0'+' ' +\n 'a ' + r +','+r+' 0 1,0 '+ (r*2) +',0 '+\n 'a ' + r +','+r+' 0 1,0 -'+ (r*2) +',0'\n ;\n // return 'm' + startX + ',' + nodeSize + ' ' +\n // 'a' + r + ',' + r + ' 0 0 0 ' + (2*r) + ',0';\n })\n ;\n\nnode.call(d3.drag()\n .on(\"start\", dragstarted)\n .on(\"drag\", dragged)\n .on(\"end\", dragended))\n .on(\"click\", function(d, idx, nodes){\n let node = nodes[idx];\n selectNode(idx, node, d);\n })\n .on('mouseover', function(n, nIdx){\n link.each(function(l,idx,linkEls,q){\n // set nodes 'visible'/highlighted when linked to active node\n if(l.source == n || l.target == n) {\n linkEls[idx].classList.add('hoverLink');\n }\n });\n })\n .on('mouseout', function(){\n let hoverLinkEls = document.getElementsByClassName('hoverLink');\n while(hoverLinkEls.length > 0){\n hoverLinkEls[0].classList.remove('hoverLink');\n }\n });\n\n// svg.call(d3.drag()\n// .on(\"start\", function(d){\n// if(d3.event.sourceEvent.type == 'touchstart' && d3.event.sourceEvent.touches.length > 1) {\n// } else {\n// d3.event.sourceEvent.stopPropagation();\n// svg.node().classList.add(\"dragging\");\n// }\n// })\n// .on(\"drag\", function(){\n// moveViewboxPx(d3.event.dx, d3.event.dy);\n// })\n// .on(\"end\", function(){\n// svg.node().classList.remove(\"dragging\");\n// }));\nsvg.call(d3.zoom()\n .scaleExtent([0.3,3])\n .on(\"start\", function(){\n svg.node().classList.add(\"dragging\");\n })\n .on(\"end\", function(){\n svg.node().classList.remove(\"dragging\");\n })\n .on(\"zoom\", function(a,b,c){\n container.attr(\"transform\", d3.event.transform);\n })\n);\n\n// svg.call(d3.zoom.transform, d3.zoomIdentity);\n\nnode.append('circle')\n .attr(\"r\", (d) => getSizeForNode(d))\n .attr(\"class\", \"nodeBg\")\n ;\nnode.append('circle')\n .attr(\"r\", (d) => getSizeForNode(d) * 1.08) // nodeSize + margin\n .attr(\"class\", \"highlightCircle\")\n ;\n\nnode.append('text')\n .attr(\"class\", \"nodeType\")\n .text(function(n){\n return n['@type'];\n })\n\nnode.append('text')\n .attr(\"class\", \"nodeYear\")\n .attr(\"y\", \"22\")\n .text(function(n){\n return getNodeYear(n);\n })\n ;\nlet splitText = function(text){\n let characters = [\" \",\"-\",\"\\u00AD\"];\n let charSplitPos = {};\n let mid = Math.floor(text.length / 2);\n let splitPos = false;\n let splitPosChar = false;\n // split sentences\n for(let char of characters) {\n if(text.indexOf(char) < 0) {\n continue;\n }\n let tmid = text.substr(0,mid).lastIndexOf(char);\n if(tmid === -1) {\n tmid = text.indexOf(char);\n }\n tmid += 1; // we want to cut _after_ the character\n // console.log(\"Char\", char, tmid);\n if(splitPos === false || Math.abs(tmid-mid) < Math.abs(splitPos - mid)){\n // console.log(\"least!\");\n splitPos = tmid;\n splitPosChar = char;\n }\n }\n // console.log(\"pos\",splitPos)\n\n\n if(splitPos === false) {\n return false;\n }\n\n let text1 = text.substr(0, splitPos).trim();\n let text2 = text.substr(splitPos).trim();\n\n if(splitPosChar == \"\\u00AD\") {\n text1 += \"-\";\n }\n\n // find most equal split\n return [text1, text2];\n}\nlet nodeTitle = node.append('text')\n .attr(\"class\", \"nodeTitle\")\n .attr(\"y\", \"5\")\n ;\nnodeTitle\n // .append(\"textPath\")\n // .attr( \"xlink:href\",function(d, idx){return '#nodePath'+idx;})\n // .text(getNodeLabel)\n .each(function(node, nodes){\n let textLength;\n let self = d3.select(this);\n let titleText = getNodeLabel(node);\n let titleTexts = false;\n if(titleText.length > 20) {\n titleTexts = splitText(titleText);\n }\n if(titleTexts !== false) {\n let tspan1 = self.append(\"tspan\")\n .text(titleTexts[0])\n .attr(\"y\", \"-10\")\n .attr(\"x\", \"0\")\n ;\n let tspan = self.append(\"tspan\")\n .text(titleTexts[1])\n .attr(\"y\", \"10\")\n .attr(\"x\", \"0\")\n ;\n let textLength1 = tspan.node().getComputedTextLength();\n let textLength2 = tspan.node().getComputedTextLength();\n textLength = Math.max(textLength1, textLength2);\n } else {\n self.text(titleText);\n textLength = self.node().getComputedTextLength();\n }\n\n // scale according to text length:\n if(textLength > getSizeForNode(node) * 2) {\n self.attr('transform', `scale(${(getSizeForNode(node) * 2) / textLength / 1.05})`);\n }\n })\n ;\n\nnode.each(function(d) {\n if(!d['https://schema.org/thumbnailUrl']) {\n return;\n }\n d3.select(this).append('svg:image')\n .attr(\"xlink:href\", d['https://schema.org/thumbnailUrl'])\n .attr(\"width\", (d) => getSizeForNode(d)*2)\n .attr(\"height\", (d) => getSizeForNode(d)* 2)\n .attr(\"transform\",(d) => \"translate(-\"+getSizeForNode(d)+\" -\"+getSizeForNode(d)+\")\")\n .attr(\"clip-path\",\"url(#clipNodeImage)\")\n .attr(\"preserveAspectRatio\",\"xMidYMid slice\")\n ;\n });\nnode.each(function(d) {\n if(d['@type'] !== 'https://schema.org/VideoObject') {\n return;\n }\n const size = getSizeForNode(d);\n d3.select(this).append('svg:polygon')\n .attr('points', \"-10,-10, -10,10, 10,0\")\n .attr(\"class\",\"play\")\n ;\n });\n\nsimulation\n .nodes(graph.nodes)\n .on(\"tick\", ticked);\n\nsimulation.force(\"link\")\n .links(graph.links)\n .distance(function(l){\n switch (l.name) {\n // case 'publishedAt':\n // return 200;\n // case 'image':\n // return 200;\n default:\n return 300;\n }\n }) // distance between the nodes / link length\n // .charge(-100)\n;\n\n// run on each draw\nfunction ticked() {\n graph.nodes.forEach(function (d, idx) {\n d.leftX = d.rightX = d.x;\n\n // fix first node on center\n // if(idx === 0) {\n // d.fx = width/2;\n // d.fy = height/2;\n // return;\n // }\n });\n\n linkLine.each(function (d) {\n var sourceX, targetX, midX, dx, dy, angle;\n\n // This mess makes the arrows exactly perfect.\n // thanks to http://bl.ocks.org/curran/9b73eb564c1c8a3d8f3ab207de364bf4\n if( d.source.rightX < d.target.leftX ){\n sourceX = d.source.rightX;\n targetX = d.target.leftX;\n } else if( d.target.rightX < d.source.leftX ){\n targetX = d.target.rightX;\n sourceX = d.source.leftX;\n } else if (d.target.isCircle) {\n targetX = sourceX = d.target.x;\n } else if (d.source.isCircle) {\n targetX = sourceX = d.source.x;\n } else {\n midX = (d.source.x + d.target.x) / 2;\n if(midX > d.target.rightX){\n midX = d.target.rightX;\n } else if(midX > d.source.rightX){\n midX = d.source.rightX;\n } else if(midX < d.target.leftX){\n midX = d.target.leftX;\n } else if(midX < d.source.leftX){\n midX = d.source.leftX;\n }\n targetX = sourceX = midX;\n }\n\n dx = targetX - sourceX;\n dy = d.target.y - d.source.y;\n angle = Math.atan2(dx, dy);\n\n /* DISABLED\n srcSize = (typeof nodePositions[d.source.index] != 'undefined') ? selectedNodeSize : nodeSize;\n tgtSize = (typeof nodePositions[d.target.index] != 'undefined') ? selectedNodeSize : nodeSize;\n */\n let srcSize = getSizeForNode(d.source)+3.2;\n let tgtSize = getSizeForNode(d.target)+3.2;\n\n // Compute the line endpoint such that the arrow\n // is touching the edge of the node rectangle perfectly.\n d.sourceX = sourceX + Math.sin(angle) * srcSize;\n d.targetX = targetX - Math.sin(angle) * tgtSize;\n d.sourceY = d.source.y + Math.cos(angle) * srcSize;\n d.targetY = d.target.y - Math.cos(angle) * tgtSize;\n })\n .attr(\"x1\", function(d) { return d.sourceX; })\n .attr(\"y1\", function(d) { return d.sourceY; })\n .attr(\"x2\", function(d) { return d.targetX; })\n .attr(\"y2\", function(d) { return d.targetY; });\n linkText.attr(\"transform\", function(d){\n let dx = (d.target.x - d.source.x) /2;\n let dy = (d.target.y - d.source.y) /2;\n let x = d.source.x + dx;\n let y = d.source.y + dy;\n let deg = Math.atan(dy / dx) * 180 / Math.PI;\n // if dx/dy == 0/0 -> deg == NaN\n if(isNaN(deg)) {\n return \"\";\n }\n return \"translate(\"+x+\" \"+y+\") rotate(\"+deg+\") translate(0, -10)\";\n });\n\n node.attr(\"transform\", function(d) { return \"translate(\" + d.x + \",\" + d.y + \")\"; });\n}\n\nfunction dragstarted(d,idx,nodes) {\n if (!d3.event.active) simulation.alphaTarget(0.3).restart();\n let nodeEl = nodes[idx];\n d.fx = d.x;\n d.fy = d.y;\n // nodeEl.style.fill = '#00f';\n nodeEl.classList.add('drag');\n}\n\n// use to validate drag\n// function validate(x, a, b) {\n// if (x =< a) return a;\n// return b;\n// }\n\nfunction dragged(d, idx) {\n d.fx = d3.event.x;\n d.fy = d3.event.y;\n}\n\nfunction dragended(d, idx, nodes) {\n if (!d3.event.active) simulation.alphaTarget(0);\n let nodeEl = nodes[idx];\n d.fx = null;\n d.fy = null;\n nodeEl.classList.remove('drag');\n}\n\nfunction moveViewboxPx(dx, dy){\n let viewBox = svg.attr(\"viewBox\").split(\" \").map(parseFloat);\n viewBox[0] -= dx * 1;\n viewBox[1] -= dy * 1;\n svg.attr(\"viewBox\", viewBox.join(\" \"));\n}\n\n// start by selecting the first node :-)\n// selectNode(currentNodeIdx+1);\n// positionNodesInCenter(currentNodeIdx);\n\nif(location.pathname.startsWith('/@type/')) {\n for(let t in types) {\n if(getDisplayAttr(t) == location.pathname.substr(7)) {\n centerByType(t, false);\n }\n }\n} else{\n let startNodeId = location.search.startsWith(\"?id=\") ? location.search.substr(4) : 'https://rubenvandeven.com'+location.pathname;\n let firstNode = graph['nodes'].find(n => n['@id'] === startNodeId);\n selectNode(graph['nodes'].indexOf(firstNode), false);\n}\n\n\n\n// closeDetails(); // hide details at first\n// positionNodesInCenter(currentNodeIdx+1);\n\n// setTimeout(function(){\n // document.body.classList.add('graphInitialised');\n// }, 10);\n\nlet initPlaceholder = document.getElementById('initPlaceholder');\nsvg.node().removeChild(initPlaceholder);\nsetTimeout(function(){\n graphInitialised = true;\n document.body.classList.add('graphInitialised');\n }, 500);\n}\n\n\n// Detect request animation frame\nvar reqAnimFrame = window.requestAnimationFrame ||\n window.webkitRequestAnimationFrame ||\n window.mozRequestAnimationFrame ||\n window.msRequestAnimationFrame ||\n window.oRequestAnimationFrame ||\n // IE Fallback, you can even fallback to onscroll\n function(callback){ window.setTimeout(callback, 1000/60) };\n// all credits go to https://stackoverflow.com/a/26798337\nfunction scrollToY(scrollTargetY, speed, easing, finishFunction) {\n // scrollTargetY: the target scrollY property of the window\n // speed: time in pixels per second\n // easing: easing equation to use\n\n var scrollY = window.scrollY,\n scrollTargetY = scrollTargetY || 0,\n speed = speed || 2000,\n easing = easing || 'easeOutSine',\n currentTime = 0,\n finishFunction = finishFunction || false;\n\n // min time .1, max time .8 seconds\n let time = Math.max(.1, Math.min(Math.abs(scrollY - scrollTargetY) / speed, .8));\n\n // easing equations from https://github.com/danro/easing-js/blob/master/easing.js\n let PI_D2 = Math.PI / 2,\n easingEquations = {\n easeOutSine: function (pos) {\n return Math.sin(pos * (Math.PI / 2));\n },\n easeInOutSine: function (pos) {\n return (-0.5 * (Math.cos(Math.PI * pos) - 1));\n },\n easeInOutQuint: function (pos) {\n if ((pos /= 0.5) < 1) {\n return 0.5 * Math.pow(pos, 5);\n }\n return 0.5 * (Math.pow((pos - 2), 5) + 2);\n }\n };\n\n // add animation loop\n function tick() {\n currentTime += 1 / 60;\n\n var p = currentTime / time;\n var t = easingEquations[easing](p);\n\n if (p < 1) {\n reqAnimFrame(tick);\n\n window.scrollTo(0, scrollY + ((scrollTargetY - scrollY) * t));\n } else {\n window.scrollTo(0, scrollTargetY);\n if(finishFunction) {\n finishFunction();\n }\n }\n }\n\n // call it once to get started\n tick();\n}\n"]} \ No newline at end of file diff --git a/src/js/portfolio.js b/src/js/portfolio.js index 1b0c630..f7206ef 100644 --- a/src/js/portfolio.js +++ b/src/js/portfolio.js @@ -598,10 +598,10 @@ var setDetails = function(nodeDatum, nodeIdx) { listEl.innerHTML += `
${getDisplayAttr(attr)}
${nodeAttr[i]}
`; if(nodeDatum['@type'] == 'https://schema.org/VideoObject') { // console.log(nodeDatum, nodeAttr); - let videoType = nodeDatum['https://schema.org/encodingFormat'] ? `type='${nodeDatum['https://schema.org/encodingFormat']}'`: ""; + // let videoType = nodeDatum['https://schema.org/encodingFormat'] ? `type='${nodeDatum['https://schema.org/encodingFormat']}'`: ""; + let videoType=""; let poster = nodeDatum['https://schema.org/thumbnailUrl'] ? `poster='${nodeDatum['https://schema.org/thumbnailUrl']}'`: ""; - // TODO: enable outplay and make it work (for some reason it does not...) - listEl.innerHTML += `
`; + listEl.innerHTML += `
`; } else{ listEl.innerHTML += `
`; }