SpaSca : open SCAffolding to SPAcially and textualy explore interfaces
https://fabien.benetou.fr/pub/home/future_of_text_demo/engine/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
221 lines
8.7 KiB
221 lines
8.7 KiB
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
|
|
}
|
|
|
|
|