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