From d0b3be2209214fe6c0183cbbf325504eb2c15817 Mon Sep 17 00:00:00 2001 From: Ruben van de Ven Date: Wed, 22 Aug 2018 14:18:46 +0200 Subject: [PATCH] More changes --- portfolio.css | 221 +++++++++++++ portfolio.js | 786 +++++++++++++++++++++++++++++++++++++++++++ test3.html | 900 +------------------------------------------------- 3 files changed, 1012 insertions(+), 895 deletions(-) create mode 100644 portfolio.css create mode 100644 portfolio.js diff --git a/portfolio.css b/portfolio.css new file mode 100644 index 0000000..b3e04e0 --- /dev/null +++ b/portfolio.css @@ -0,0 +1,221 @@ +body{ + margin:0;overflow: hidden; + font-family: sans-serif; +/* background: #fceabb; +background: -moz-linear-gradient(-45deg, #fceabb 0%, #fccd4d 50%, #f8b500 51%, #fbdf93 100%); +background: -webkit-linear-gradient(-45deg, #fceabb 0%,#fccd4d 50%,#f8b500 51%,#fbdf93 100%); +background: linear-gradient(135deg, #fceabb 0%,#fccd4d 50%,#f8b500 51%,#fbdf93 100%); +filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#fceabb', endColorstr='#fbdf93',GradientType=1 ); */ +/*background: #f7fbfc; +background: -moz-linear-gradient(45deg, #f7fbfc 0%, #d9edf2 40%, #add9e4 100%); +background: -webkit-linear-gradient(45deg, #f7fbfc 0%,#d9edf2 40%,#add9e4 100%); +background: linear-gradient(45deg, #f7fbfc 0%,#d9edf2 40%,#add9e4 100%); +filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#f7fbfc', endColorstr='#add9e4',GradientType=1 );*/ +/*background: #e2e2e2; +background: -moz-linear-gradient(45deg, #e2e2e2 0%, #dbdbdb 50%, #d1d1d1 51%, #fefefe 100%); +background: -webkit-linear-gradient(45deg, #e2e2e2 0%,#dbdbdb 50%,#d1d1d1 51%,#fefefe 100%); +background: linear-gradient(45deg, #e2e2e2 0%,#dbdbdb 50%,#d1d1d1 51%,#fefefe 100%); +filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#e2e2e2', endColorstr='#fefefe',GradientType=1 );*/ +/*background: #d2dfed; +background: -moz-linear-gradient(45deg, #d2dfed 0%, #c8d7eb 26%, #bed0ea 51%, #a6c0e3 51%, #afc7e8 62%, #bad0ef 75%, #99b5db 88%, #799bc8 100%); +background: -webkit-linear-gradient(45deg, #d2dfed 0%,#c8d7eb 26%,#bed0ea 51%,#a6c0e3 51%,#afc7e8 62%,#bad0ef 75%,#99b5db 88%,#799bc8 100%); +background: linear-gradient(45deg, #d2dfed 0%,#c8d7eb 26%,#bed0ea 51%,#a6c0e3 51%,#afc7e8 62%,#bad0ef 75%,#99b5db 88%,#799bc8 100%); +filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#d2dfed', endColorstr='#799bc8',GradientType=1 );*/ +background: #d2dfed; +background: -moz-linear-gradient(45deg, #d2dfed 0%, #afc1d8 13%, #d5e0ef 28%, #bed0ea 51%, #a8c0dd 51%, #c0d0e5 63%, #bad0ef 75%, #a2bad8 88%, #799bc8 100%); +background: -webkit-linear-gradient(45deg, #d2dfed 0%,#afc1d8 13%,#d5e0ef 28%,#bed0ea 51%,#a8c0dd 51%,#c0d0e5 63%,#bad0ef 75%,#a2bad8 88%,#799bc8 100%); +background: linear-gradient(45deg, #d2dfed 0%,#afc1d8 13%,#d5e0ef 28%,#bed0ea 51%,#a8c0dd 51%,#c0d0e5 63%,#bad0ef 75%,#a2bad8 88%,#799bc8 100%); +filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#d2dfed', endColorstr='#799bc8',GradientType=1 ); + +/*-moz-animation: bgShift 5s infinite; +background-size: 400% 400%;*/ +} + +/*@-moz-keyframes bgShift{ + 0%{background-position:0% 50%} + 50%{background-position:100% 50%} + 100%{background-position:0% 50%} +}*/ +svg{ + width:100vw; + height: calc(100vh - 40px); + cursor: dragging; +} +svg.dragging{ + cursor: grabbing; +} +g.node{ + cursor: pointer; + stroke: blue; + stroke-width: 0; + transition: stroke-width .5s; + opacity: 0; + pointer-events: none; + transition: opacity 1s; +} +g.node.visibleNode/* , g.node.ImageObject */ +{ + opacity: 1; + pointer-events: auto; +} +g.node circle.highlightCircle{ + fill: none; +} +g.node:hover circle.highlightCircle{ + stroke-width: 2px; +} +g.node.centeredNode circle.highlightCircle{ + stroke-width:1px; + stroke: green; + stroke-dasharray: 3 2; +} +g.node.selectedNode circle.highlightCircle{ + color: red; +} +g.node.drag{ + cursor:grabbing; +} +.node text{ + text-anchor: start; +} +.relationship{ + display:none; +} +.relationship.visibleLink{ + display:block; +} +.relationship line{ + fill:none; + stroke: #999; + stroke-width: 2px;\; +} +.relationship text{ + fill:black; + /* text-transform: lowercase; */ + font-size: 75%; + display:none; +} +.relationship.activeLink text{ + fill:white; + display:block; +} +.relationship.activeLink line{ + stroke: white; +} +circle.nodeBg{ + fill: white; + /*stroke-width:.2em;*/ + fill:url(#blueGrad); +} +text{ + text-anchor: middle; +} + +.drag{ + fill:#00f; +} + +.MediaObject circle.nodeBg{ + fill:url(#orangeGrad); +} +/* .ImageObject circle.nodeBg{ + stroke:lightgreen; +}*/ +.Person circle.nodeBg{ + fill:url(#redGrad); +} +.PublicationEvent circle.nodeBg{ + fill:url(#limeGrad); +} +/*.Place circle{ + stroke:darkgreen; +} +.Country circle{ + stroke:yellow; +}*/ +.relationship.address line{ + /* stroke:#90F7FE; */ +} +.relationship.location line{ + /* stroke:darkgreen; */ +} +.relationship.contributor line{ + /* stroke:orange; */ + /* stroke-width:.4em; */ +} +#nodeDetails{ + position: absolute; + top: 0; + right: -720px; + bottom: 10px; + width: 700px; + background: white; + padding: 20px; + /* opacity: 0; */ + transition: opacity 1s, right 1s; + max-height: 100%; + overflow-y: auto; +} +body.detailsOpen #nodeDetails{ + /* opacity:1; */ + right: 0px; +} +svg#portfolioGraph { + position: relative; + right: 0; + transition: right 1s; +} +body.detailsOpen svg#portfolioGraph{ + right: calc(720px / 2); +} +#nodeDetails .nodeTitle{ + +} +#nodeDetails .nodeType{ + font-size:80%; + text-transform: uppercase; + color: #999; + margin-left:10px; +} +#nodeDetails .nodeType:hover{ + cursor:pointer; + color:blue; +} +/* #nodeDetails .nodeType::before{ + content:"("; +} +#nodeDetails .nodeType::after{ + content:")"; +} */ +#nodeDetails dt{ + float:left; + width: 120px; + font-weight:bold; +} +#nodeDetails dd:not(.nodeTitleNr1) { + margin-left: 120px; +} +#nodeDetails dt.dt-description{ + float:none; +} +#nodeDetails dd.dd-description{ + margin-left:0; +} +#graphControls{ + position:fixed; + left:0; + bottom:0; + right:0; + height:40px; + background:white; +} +#graphControls ul{ + margin:0; + padding: 0; +} +#graphControls li{ + list-style:none; + display: inline-block; + margin: 9px 10px 0; + cursor: pointer; +} diff --git a/portfolio.js b/portfolio.js new file mode 100644 index 0000000..7a98372 --- /dev/null +++ b/portfolio.js @@ -0,0 +1,786 @@ +var data; + +function getTitleAttribute(node) { + if(typeof node['name'] !== "undefined"){ + return 'name'; + } + switch (node['type']) { + case "WebSite": + if(typeof node['url'] !== "undefined") {return 'url';} + break; + case "ImageObject": + if(typeof node['caption'] !== "undefined") {return 'caption';} + break; + break; + case "PostalAddress": + if(typeof node['addressLocality'] !== "undefined") {return 'addressLocality';} + break; + } + return 'id'; +} +function getNodeTitle(node){ + return node[getTitleAttribute(node)]; +} +/** +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){ + let nodes = {}; + let links = []; + // collect all nodes + for(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(nodeId in data) { + let node = data[nodeId]; + currentId = node["id"]; + for(key in node){ + let 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(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; +var nodeMap = {}; +jsonld.flatten(window.location.protocol + "//" + window.location.host + "/rubenvandeven.jsonld", {"@context": "https://schema.org/"},(err, flattened)=> { + console.log(err); + data = flattened; + graph = jsonLdToGraph(flattened['@graph']); + // create a map of nodes by id. + for(let i in graph.nodes) { + nodeMap[graph.nodes[i]['id']] = graph.nodes[i]; + } + // console.log(graph); + startGraph(graph); +}); + + +function inCircle(dx, dy, r) { + // fastest check if in circle: https://stackoverflow.com/a/7227057 + let dxAbs = Math.abs(dx); + let 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 startGraph(graph){ + + + // config +var nodeSize = 40; +var selectedNodeSize = 140; + +// set some vars +var currentNodeIdx = 0; +var currentNodePositionRadius = 0; +var nodePositions = {}; +var types = {}; +for (nodeIdx in graph['nodes']) { + let type = graph['nodes'][nodeIdx]["type"]; + if(typeof types[type] == 'undefined') { + types[type] = []; + } + types[type][types[type].length] = nodeIdx; +} +var graphControlsEl = document.getElementById('graphControls'); +var typeLinksEl = document.getElementById('typeLinks'); +var relLinksEl = document.getElementById('relLinks'); + +// make controls +for (let typeName in types) { + let typeLinkEl = document.createElement("li"); + typeLinkEl.innerHTML = typeName; + typeLinkEl.addEventListener('click', function(){ + centerByType(typeName); + // positionNodesInCenter(types[typeName]); + }); + typeLinksEl.appendChild(typeLinkEl); +} + + +// make svg +var svg = d3.select("svg"), + width = +svg.attr("width"), + height = +svg.attr("height"); + +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(nodeSize * 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 = svg.append("g") + .attr("class", "links") + .selectAll(".relationship") + .data(graph['links']) + .enter().append("g") + .attr("class", function(d){return "relationship "+d.name;}) + ; +linkLine = link + // .append("line"); + .append("line").attr("marker-end", "url(#arrowHead)") + ; +linkText = link + .append("text") + .text(function(d){ + return d.name; + // snake_case: return d.name.replace(/(?:^|\.?)([A-Z])/g, function (x,y){return "_" + y.toLowerCase()}).replace(/^_/, ""); + }) + + ; + + var node = svg.append("g") + .attr("class", "nodes") + .selectAll(".node") + .data(graph.nodes) + .enter().append("g") + .attr("class", function(d) { return 'node ' + d['type']; }) + ; +var getViewbox = function() { + return svg.attr("viewBox").split(" ").map(parseFloat); +} +var positionNodesInCenter = function(idxs) { + let viewBox = getViewbox(); + let cx = viewBox[0] + viewBox[2]/2; + let cy = viewBox[1] + viewBox[3]/2; + + if(typeof idxs == "object" && idxs !== null && idxs.length == 1) { + idxs = idxs[0]; + } + + nodePositions = {}; // reset + if(idxs === null) { + return; + } + else if(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] = [ + cx, + cy + ]; + } + + 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'); + } + }); + + // restart animation (they call that 'alpha' in d3 force) + simulation.alpha(1); +} +var positionNodesInCircle = function(idxs, r) { + let viewBox = getViewbox(); + 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; + let cx = viewBox[0] + viewBox[2]/2; + let cy = viewBox[1] + viewBox[3]/2; + + stepSize = 2*Math.PI / idxs.length; + for (var i = 0; i < idxs.length; i++) { + nodePositions[idxs[i]] = [ + cx + Math.sin(stepSize * i) * r, + cy + Math.cos(stepSize * i) * r + ]; + } + + // restart animation (they call that 'alpha' in d3 force) + simulation.alpha(1); +} +var centerByType = function(types) { + if(!Array.isArray(types)) { + types = [types]; + } + let idxs = []; + for(let idx in graph.nodes) { + if(types.indexOf(graph.nodes[idx]['type']) > -1) { + idxs[idxs.length] = idx; + } + } + deselectNode(); + positionNodesInCenter(idxs.length ? idxs : null); +} + +d3Selection=""; +var selectedNodeTransition = d3.transition() + .duration(750) + .ease(d3.easeLinear); + +var nodeDetailEl = document.getElementById("nodeDetails"); + +var createRelationshipEl = function(relNode, i) { + let el = document.createElement("dd"); + el.classList.add('relLink'); + let titleEl = document.createElement('span'); + titleEl.innerHTML = getNodeTitle(relNode); + titleEl.classList.add('nodeTitle'); + titleEl.classList.add('nodeTitleNr'+i); + titleEl.addEventListener('click',function(e){ + let idx = graph.nodes.indexOf(relNode); + selectNode(idx); + }); + let typeEl = document.createElement('span'); + typeEl.classList.add('nodeType') + typeEl.innerHTML = relNode['type'] + typeEl.addEventListener('click',function(e){ + centerByType(relNode['type']); + }); + el.appendChild(titleEl); + el.appendChild(typeEl); + // el.innerHTML = `${getNodeTitle(relNode)} (${relNode['type']})`; + return el; +} + +var setDetails = function(nodeDatum, nodeIdx) { + document.body.classList.add("detailsOpen"); + while (nodeDetailEl.hasChildNodes()) { + nodeDetailEl.removeChild(nodeDetailEl.lastChild); + } + + + let relUp = []; + let relDown = []; + let titleAttr = getTitleAttribute(nodeDatum); + let titleEl = document.createElement('h2'); + titleEl.innerHTML = getNodeTitle(nodeDatum); + + let typeEl = document.createElement('span'); + typeEl.classList.add('nodeType') + typeEl.innerHTML = nodeDatum['type'] + typeEl.addEventListener('click',function(e){ + centerByType(nodeDatum['type']); + }); + titleEl.appendChild(typeEl); + nodeDetailEl.appendChild(titleEl); + + let listEl = document.createElement("dl"); + // listEl.innerHTML += `
type
${nodeDatum['type']}
`; + for (let attr in nodeDatum) { + if([ + 'id','x','y','index','type','vy','vx','fx','fy','leftX','rightX', titleAttr + ].indexOf(attr) != -1) { + continue; + } + + // approach all as array + let nodeAttr = Array.isArray(nodeDatum[attr]) ? nodeDatum[attr] : [nodeDatum[attr]]; + for (let 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 == 'url') { + listEl.innerHTML += `
${attr}
${nodeAttr[i]}
`; + } else if(attr == 'contentUrl') { + listEl.innerHTML += `
${attr}
${nodeAttr[i]}
`; + } else { + listEl.innerHTML += `
${attr}
${nodeAttr[i]}
`; + } + } + } + nodeDetailEl.appendChild(listEl); + + let relTitleEl = document.createElement("h4"); + relTitleEl.classList.add('linkTitle'); + relTitleEl.innerHTML = "links"; + nodeDetailEl.appendChild(relTitleEl); + + let relsEl = document.createElement("dl"); + // collect relationships + for (var i = 0; i < graph.links.length; i++) { + let 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(let attr in relDown) { + let attrEl = document.createElement("dt"); + attrEl.innerHTML = attr; + relsEl.appendChild(attrEl); + for(let i in relDown[attr]) { + let rel = relDown[attr][i]; + relsEl.appendChild(createRelationshipEl(rel)); + // html += `
${rel['type']}: ${rel['id']}
`; + } + } + + for(let attr in relUp) { + let attrEl = document.createElement("dt"); + attrEl.innerHTML = attr; + relsEl.appendChild(attrEl); + for(let i in relUp[attr]) { + let rel = relUp[attr][i]; + relsEl.appendChild(createRelationshipEl(rel, i)); + // html += `
${rel['type']}: ${rel['id']}
`; + } + } + + nodeDetailEl.appendChild(relsEl); + + node.each(function(d,nIdx,nodeEls){ + if(nIdx == nodeIdx) { + nodeEls[nIdx].classList.add('selectedNode'); + } else { + nodeEls[nIdx].classList.remove('selectedNode'); + } + }); +}; +var closeDetails = function() { + document.body.classList.remove("detailsOpen"); +} + +/** + * 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(idx){ + let nodeEl = null; + let nodeDatum = null; + node.each(function(d,nIdx,nodeEls){ + if(nIdx == idx) { + nodeEl = nodeEls[idx]; + nodeDatum = d; + } + }); + if(!nodeEl) { + return; + } + + // set global var + positionNodesInCenter(idx); + + /* DISABLED: Make selected nodes bigger + // update collision: the selected node should get plenty of space. + simulation.force("collision").radius(function(d, idx){ + if(typeof nodePositions[idx] != 'undefined'){ + return selectedNodeSize * 1.2; + } + return nodeSize * 1.1; + }); + node.each(function(d, idx, nodeEls){ + let nodeEl = nodeEls[idx]; + let nodeD3 = d3.select(nodeEl); + let circleD3 = nodeD3.select('circle'); + + if(typeof nodePositions[idx] !== 'undefined') { + circleD3.transition(selectedNodeTransition).attr('r', selectedNodeSize); + // nodeEl.getElementsByTagName("circle")[0].attributes.r.value = selectedNodeSize; + } else { + circleD3.transition(selectedNodeTransition).attr('r', nodeSize); + // nodeEl.getElementsByTagName("circle")[0].attributes.r.value = nodeSize; + } + }); + */ + linkedIdxs = []; + link.each(function(d,idx,linkEls,q){ + 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){ + let 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)"); + } + }); + + i = linkedIdxs.indexOf(idx); + + if(i !== -1) { + linkedIdxs.splice(i, 1); + } + + positionNodesInCircle(linkedIdxs); + + setDetails(nodeDatum ,idx); +} +var deselectNode = function() { + positionNodesInCenter(null); + link.each(function(d,idx,linkEls,q){ + linkEls[idx].classList.remove('activeLink'); + linkEls[idx].getElementsByTagName("line")[0].setAttribute("marker-end", "url(#arrowHead)") + }); + closeDetails(); +} + +simulation.force('centerActive', function force(alpha) { + // let currentNode = node.selectAll('.detail'); + // console.log(currentNode); + node.each(function(d, idx, nodes){ + let n = d; + let k = alpha * 0.1; + if(typeof nodePositions[idx] != 'undefined') { + 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; + } + + let viewBox = getViewbox(); + let cx = viewBox[0] + viewBox[2]/2; + let cy = viewBox[1] + viewBox[3]/2; + + let dx = n.x - cx; + let dy = n.y - cy; + if(!inCircle(dx, dy, currentNodePositionRadius)) { + return; + } + + n.vx += dx * k /5; + n.vy += dy * k /5; + } + }); +}); + +//path to curve the tile +nodePath = node.append("path") + .attr("id", function(d,idx){return "nodePath"+idx;}) + .attr("d", function(){ + var r = nodeSize * 0.9; + var startX = nodeSize; + // 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){ + let node = nodes[idx]; + // if(typeof nodePositions[idx] == 'undefined') { + selectNode(idx, node, d); + // } else { + // node.parentNode.classList.toggle('detail'); + // } + }); + +svg.call(d3.drag() + .on("start", function(){ + svg.node().classList.add("dragging"); + }) + .on("drag", function(){ + moveViewboxPx(d3.event.dx, d3.event.dy); + }) + .on("end", function(){ + svg.node().classList.remove("dragging"); + })); + +node.append('circle') + .attr("r", nodeSize) + .attr("class", "nodeBg") + ; +node.append('circle') + .attr("r", nodeSize * 1.08) // nodeSize + margin + .attr("class", "highlightCircle") + ; + +node.append('text') + .append("textPath") + .attr( "xlink:href",function(d, idx){return '#nodePath'+idx;}) + .text(getNodeTitle); + +node.each(function(d) { + if(!d.contentUrl) { + return; + } + d3.select(this).append('svg:image') + .attr("xlink:href", d.contentUrl) + .attr("width", nodeSize*2) + .attr("height", nodeSize*2) + .attr("transform","translate(-"+nodeSize+" -"+nodeSize+")") + .attr("clip-path","url(#clipNodeImage)") + .attr("preserveAspectRatio","xMidYMid slice") + ; + }); + +// node.append("title") +// .text(function(d) { return d.id; }); + +simulation + .nodes(graph.nodes) + .on("tick", ticked); + +simulation.force("link") + .links(graph.links) + .distance(function(l){ + switch (l.name) { + case 'publishedAt': + return 400; + case 'image': + return 100; + default: + return 200; + } + }) // 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, dy, 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; + */ + srcSize = nodeSize; + tgtSize = nodeSize; + + // 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){ + let dx = (d.target.x - d.source.x) /2; + let dy = (d.target.y - d.source.y) /2; + let x = d.source.x + dx; + let y = d.source.y + dy; + let deg = Math.atan(dy / dx) * 180 / Math.PI; + return "translate("+x+" "+y+") rotate("+deg+") translate(0, -10)"; + }); + // linkPath.attr("d", function(d) { + // var x1 = d.source.x, + // y1 = d.source.y, + // x2 = d.target.x, + // y2 = d.target.y, + // dx = x2 - x1, + // dy = y2 - y1, + // dr = Math.sqrt(dx * dx + dy * dy), + + // // Defaults for normal edge. + // drx = dr, + // dry = dr, + // xRotation = 0, // degrees + // largeArc = 0, // 1 or 0 + // sweep = 1; // 1 or 0 + + // // Self edge. + // if ( x1 === x2 && y1 === y2 ) { + // // Fiddle with this angle to get loop oriented. + // xRotation = -45; + + // // Needs to be 1. + // largeArc = 1; + + // // Change sweep to change orientation of loop. + // //sweep = 0; + + // // Make drx and dry different to get an ellipse + // // instead of a circle. + // drx = 30; + // dry = 20; + + // // For whatever reason the arc collapses to a point if the beginning + // // and ending points of the arc are the same, so kludge it. + // x2 = x2 + 1; + // y2 = y2 + 1; + // } + + // return "M" + x1 + "," + y1 + "A" + drx + "," + dry + " " + xRotation + "," + largeArc + "," + sweep + " " + x2 + "," + y2; + // }); + + 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(); + let 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); + let nodeEl = nodes[idx]; + d.fx = null; + d.fy = null; + nodeEl.classList.remove('drag'); +} + +function moveViewboxPx(dx, dy){ + let 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); +selectNode(currentNodeIdx); deselectNode(); +// positionNodesInCenter(currentNodeIdx+1); +} diff --git a/test3.html b/test3.html index 8246009..f7fe496 100644 --- a/test3.html +++ b/test3.html @@ -3,219 +3,10 @@ - + - + @@ -248,8 +39,8 @@ background-size: 400% 400%;*/ - - + + @@ -263,688 +54,7 @@ background-size: 400% 400%;*/ - - - +