parent
a3042794db
commit
ffc8494062
@ -1,3 +1,221 @@ |
||||
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
|
||||
} |
||||
|
||||
|
Loading…
Reference in new issue