|
|
|
console.log('jxr extras to gradually add, by default could refer to branches instead then add as components proper')
|
|
|
|
console.log('arguably utils and additional interactions beyond pinche could be extras rather than core')
|
|
|
|
console.log('extra could also be empty...')
|
|
|
|
|
|
|
|
// from https://glitch.com/edit/#!/zen-pim?path=graph-cyto-headless.html
|
|
|
|
// see also https://observablehq.com/@utopiah/d3-pim-graph
|
|
|
|
// motivated by https://these.arthurperret.fr/chapitre-4.html#cosmographe-et-cosmoscope
|
|
|
|
|
|
|
|
const cytoJson = "https://vatelier.benetou.fr/MyDemo/newtooling/wiki_graph_cyto.json"
|
|
|
|
|
|
|
|
var cy
|
|
|
|
var jsonLoaded
|
|
|
|
|
|
|
|
fetch(cytoJson)
|
|
|
|
.then(function (response) {
|
|
|
|
return response.json();
|
|
|
|
})
|
|
|
|
.then(function (json) {
|
|
|
|
// take nodes from wiki.Nodes then construct an array in elements for cytoscape format
|
|
|
|
// or directly from JSON http://js.cytoscape.org/#notation/elements-json
|
|
|
|
jsonLoaded = json
|
|
|
|
startCytoWithData(json)
|
|
|
|
})
|
|
|
|
.catch(function (err) {
|
|
|
|
console.log(err);
|
|
|
|
});
|
|
|
|
|
|
|
|
// should instead directly use https://vatelier.net/MyDemo/newtooling/wiki_graph_cyto.json
|
|
|
|
// generated from https://vatelier.net/MyDemo/newtooling/build_graph_cytograph.js
|
|
|
|
// cf instead http://js.cytoscape.org/demos/tokyo-railways/tokyo-railways.json more detail
|
|
|
|
// with details on how to load the JSON
|
|
|
|
|
|
|
|
function runCytoAnalysis(){
|
|
|
|
console.log('----------------- network analysis started --------------')
|
|
|
|
if (!cy.nodes('[id = "Analysis.Analysis"]').length) {
|
|
|
|
console.warn('Wrong dataset, cancelling')
|
|
|
|
return
|
|
|
|
}
|
|
|
|
// example of queries
|
|
|
|
var aStar = cy.elements().aStar({ root: '[id = "Analysis.Analysis"]', goal: '[id = "Analysis.CostsAndBenefitsOfSocietalMembership"]' })
|
|
|
|
if ( aStar.path){
|
|
|
|
console.log("Path from Analysis.Analysis to Analysis.CostsAndBenefitsOfSocietalMembership", aStar.path.edges() , aStar.path.nodes() )
|
|
|
|
aStar.path.select()
|
|
|
|
}
|
|
|
|
console.log( cy.nodes('[id = "Analysis.Analysis"]').neighborhood() )
|
|
|
|
before = Date.now()
|
|
|
|
var bc = cy.elements().bc()
|
|
|
|
after = Date.now()
|
|
|
|
console.log("took ", after-before, "ms to run.")
|
|
|
|
console.log( 'bc of j: ' + bc.betweenness('[id = "Analysis.Analysis"]') )
|
|
|
|
console.log( cy.nodes('[id = "Analysis.CostsAndBenefitsOfSocietalMembership"]').neighborhood() )
|
|
|
|
//no need to run cy.elements().bc() again. It's done once for the whole graph.
|
|
|
|
console.log( 'bc of j: ' + bc.betweenness('[id = "Analysis.CostsAndBenefitsOfSocietalMembership"]') )
|
|
|
|
console.log('----------------- network analysis done -----------------')
|
|
|
|
}
|
|
|
|
|
|
|
|
function startCytoWithData(json){
|
|
|
|
cy = cytoscape({
|
|
|
|
headless:true,
|
|
|
|
elements: json.elements
|
|
|
|
})
|
|
|
|
|
|
|
|
//runCytoAnalysis() //quite demanding, skipped for now.
|
|
|
|
|
|
|
|
let defaults = {
|
|
|
|
name: 'euler',
|
|
|
|
springLength: edge => 80,
|
|
|
|
springCoeff: edge => 0.0008,
|
|
|
|
mass: node => 4,
|
|
|
|
gravity: -1.2,
|
|
|
|
pull: 0.001,
|
|
|
|
theta: 0.666,
|
|
|
|
dragCoeff: 0.02,
|
|
|
|
movementThreshold: 1,
|
|
|
|
timeStep: 20,
|
|
|
|
refresh: 10,
|
|
|
|
animate: true,
|
|
|
|
animationDuration: undefined,
|
|
|
|
animationEasing: undefined,
|
|
|
|
maxIterations: 1000,
|
|
|
|
maxSimulationTime: 4000,
|
|
|
|
ungrabifyWhileSimulating: false,
|
|
|
|
fit: true,
|
|
|
|
padding: 30,
|
|
|
|
// Constrain layout bounds with one of
|
|
|
|
// - { x1, y1, x2, y2 }
|
|
|
|
// - { x1, y1, w, h }
|
|
|
|
// - undefined / null : Unconstrained
|
|
|
|
boundingBox: undefined,
|
|
|
|
|
|
|
|
// Layout event callbacks; equivalent to `layout.one('layoutready', callback)` for example
|
|
|
|
ready: function(){ console.log("graph ready", cy.json()) }, // on layoutready
|
|
|
|
stop: stableGraph(), // on layoutstop
|
|
|
|
randomize: false
|
|
|
|
};
|
|
|
|
|
|
|
|
// disabled for tests
|
|
|
|
//cy.layout( defaults ).run(); // too demanding for the entire graph, should limit to a subset
|
|
|
|
}
|
|
|
|
|
|
|
|
function stableGraph(){
|
|
|
|
var exportableJSON = cy.json()
|
|
|
|
console.log( 'exportable JSON', exportableJSON );
|
|
|
|
// not actually stable!
|
|
|
|
// still could be used as a form of caching BUT... would take into account new nodes added since
|
|
|
|
|
|
|
|
var node = "ReadingNotes.ApocalypticAI"
|
|
|
|
if (!cy.nodes('[id = "'+node+'"]').length) {
|
|
|
|
console.warn('Wrong dataset, cancelling')
|
|
|
|
return
|
|
|
|
}
|
|
|
|
console.log('should add/update AFrame nodes e.g', cy.elements('[id = "'+node+'"]').position())
|
|
|
|
console.log('could add all the nodes then their links with proper attributes in order to do select() after')
|
|
|
|
// using e.g. cy.nodes().forEach( n => console.log(n.data(), n.position() ) )
|
|
|
|
// cy.edges().forEach( e => console.log(e.data(), e.sourceEndpoint(), e.targetEndpoint() ))
|
|
|
|
// warning, very costly!
|
|
|
|
// run on entire wiki though whereas previous D3 instance limited to 10 pages and their targets
|
|
|
|
}
|
|
|
|
|
|
|
|
let edges_to_display = []
|
|
|
|
function displayLeafs(graph, graphEl, rootId, rootEl, depth=3){
|
|
|
|
console.log( graph[rootId].Id )
|
|
|
|
if (depth<1) return
|
|
|
|
graph[rootId].Targets.map( l => {
|
|
|
|
console.log( "-", graph[l].Id )
|
|
|
|
let x = addNodeFromGraph(graph[l].Id, "" + (Math.random()*2) + " " + (Math.random()*2) + " -" + (Math.random()*2) )
|
|
|
|
// layout could be done with Cytoscape but somehow seems I can't use it properly, in headless mode or not.
|
|
|
|
x.setAttribute('update-links-on-pinchended', true)
|
|
|
|
x.setAttribute('toggle-links-on-left-pinchended', true)
|
|
|
|
console.log("linking:", rootEl, x)
|
|
|
|
edges_to_display.push({graphel:graphEl, source:rootEl, target:x})
|
|
|
|
displayLeafs( graph, graphEl, graph[l].Id, x, --depth)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
AFRAME.registerComponent('toggle-links-on-left-pinchended', {
|
|
|
|
init: function(){
|
|
|
|
let graphEl = document.querySelector("#graphroot")
|
|
|
|
let el = this.el
|
|
|
|
this.el.addEventListener('lreleased', function (event) {
|
|
|
|
// if it has children (...how knowing that we are not using the hierarchy?)
|
|
|
|
// delete them, including links (should be recursive too)
|
|
|
|
// if not, displayLeafs( mynodes, graphEl, root.Id, rootEl, 1)
|
|
|
|
// assumes not for now
|
|
|
|
displayLeafs( wikiStructure, graphEl, el.getAttribute("value"), el, 1)
|
|
|
|
setTimeout( _ => { edges_to_display.map( i => addEdgeBetweenNodesFromGraph( i.graphel, i.source, i.target ))}, 2000 )
|
|
|
|
})
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
AFRAME.registerComponent('update-links-on-pinchended', {
|
|
|
|
init: function(){
|
|
|
|
let rootEl = document.querySelector("#graphroot")
|
|
|
|
let el = this.el
|
|
|
|
this.el.addEventListener('released', function (event) {
|
|
|
|
Object.keys( rootEl.components ) // get all links
|
|
|
|
.filter( i => i.indexOf(el.id) > -1 ) // keeps links related to the moved node
|
|
|
|
.map( i => { // for each link
|
|
|
|
let newpos = AFRAME.utils.coordinates.stringify( el.getAttribute("position") )
|
|
|
|
let [src,tgt] = i.replace("line__","").split("__to__");
|
|
|
|
srcpos = AFRAME.utils.coordinates.stringify( rootEl.getAttribute(i).start )
|
|
|
|
tgtpos = AFRAME.utils.coordinates.stringify( rootEl.getAttribute(i).end )
|
|
|
|
if (src==el.id){
|
|
|
|
rootEl.setAttribute(i, { start: newpos, end: tgtpos })
|
|
|
|
} else {
|
|
|
|
rootEl.setAttribute(i, { start: srcpos, end: newpos })
|
|
|
|
}
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
let nodes = []
|
|
|
|
let edges = []
|
|
|
|
let wikiStructure = null
|
|
|
|
|
|
|
|
fetch('https://vatelier.benetou.fr/MyDemo/newtooling/wiki_graph.json').then( r => r.json() ).then( r => displayGraph(r.Nodes) );
|
|
|
|
// alternatively there is the issue component that could also display linked issues
|
|
|
|
function displayGraph(mynodes){
|
|
|
|
wikiStructure = mynodes
|
|
|
|
let startingNode = "Wiki.VirtualRealityInterface"
|
|
|
|
let root = mynodes[startingNode]
|
|
|
|
|
|
|
|
let graphEl = document.createElement("a-entity")
|
|
|
|
graphEl.id = "graphroot"
|
|
|
|
AFRAME.scenes[0].appendChild( graphEl )
|
|
|
|
let node_names = Object.keys( mynodes )
|
|
|
|
|
|
|
|
let rootEl = addNodeFromGraph(root.Id, "" + (Math.random()*2-1) + " " + (Math.random()+1) + " -" + (Math.random()*2-0.5) )
|
|
|
|
rootEl.setAttribute('update-links-on-pinchended', true)
|
|
|
|
displayLeafs( mynodes, graphEl, root.Id, rootEl, 2)
|
|
|
|
setTimeout( _ => { edges_to_display.map( i => addEdgeBetweenNodesFromGraph( i.graphel, i.source, i.target ))}, 2000 )
|
|
|
|
// quite unreliable
|
|
|
|
// should listen to an event instead to insure that nodes are all created before
|
|
|
|
}
|
|
|
|
|
|
|
|
function addNodeFromGraph(name, position="0 0 0"){
|
|
|
|
// add sphere, with its name, make it a target
|
|
|
|
// define what "it" is knowing we can't move a children with an offset
|
|
|
|
// consequently parenting should be done by the text
|
|
|
|
let el = addNewNote(name, position, ".1 .1 .1", "node_" +crypto.randomUUID(), "node_from_graph")
|
|
|
|
let sphereEl = document.createElement("a-sphere")
|
|
|
|
sphereEl.setAttribute("radius", .1)
|
|
|
|
sphereEl.setAttribute("segments-height", 4)
|
|
|
|
sphereEl.setAttribute("segments-width", 4)
|
|
|
|
sphereEl.setAttribute("wireframe", true)
|
|
|
|
sphereEl.setAttribute("position", "0 -.1 0")
|
|
|
|
el.appendChild(sphereEl)
|
|
|
|
// position shouldn't have to be offset
|
|
|
|
return el
|
|
|
|
}
|
|
|
|
|
|
|
|
function addEdgeBetweenNodesFromGraph(graphEl, a, b){
|
|
|
|
// a.setAttribute( "line-link-entities", {source: a.id, target: b.id} ) doesn't seem work, back to basics for now
|
|
|
|
graphEl.setAttribute("line__"+a.id+"__to__"+b.id, {
|
|
|
|
start: AFRAME.utils.coordinates.stringify( a.getAttribute("position") ) ,
|
|
|
|
end: AFRAME.utils.coordinates.stringify( b.getAttribute("position") )
|
|
|
|
})
|
|
|
|
// not that this doesn't take into account the parent node moving
|
|
|
|
}
|
|
|
|
|