Commit 82a36d00 authored by Romain Loth's avatar Romain Loth

finish inits restructuration allowing repeated graph load

old initListeners split in three: initGUIListeners and initSearchListeners that are run once at page load, and initSigmaListeners that is run for each new graph open from local file... also env function and CSS for the local graph file input
parent 279768e3
......@@ -437,6 +437,8 @@
<div id="topPapers"></div>
<div id="localInput"></div>
<div id="information"></div>
<!--
......
......@@ -269,6 +269,21 @@ p.micromessage{
font-size:80%;
}
#localInput {
font-size: 80%;
padding: 10px;
}
#localgraphfile {
margin-left: auto ;
margin-right: auto ;
}
.comment {
padding: 10px
}
.centered {
text-align: center;
}
......
......@@ -40,7 +40,8 @@ var TW = {}
TW.SystemStates = {}
TW.SystemStates.level = true;
TW.SystemStates.type = [ true ] //[ true , false ]; //social activated!
// TW.SystemStates.type = [ true ] //[ true , false ]; //social activated!
TW.SystemStates.type = [ true, false ] //[ true , false ]; //social activated!
TW.SystemStates.selections = [];
TW.SystemStates.opposites = [];
TW.catSoc = "Document";
......@@ -119,7 +120,7 @@ TW.customLegendsBins = {
TW.debugFlags = {
initialShowAll: false, // show all nodes on bipartite case init (docs + terms)
initialShowAll: false, // show all nodes on bipartite case init (docs + terms in one view)
// show verbose console logs...
logFetchers: false, // ...about ajax/fetching of graph data
......
......@@ -470,28 +470,14 @@ TinaWebJS = function ( sigmacanvas ) {
if (TW.debugFlags.logSettings) console.log('tw renderers registered in sigma module')
}
this.SearchListeners = function () {
this.initSearchListeners = function () {
// REFA tempo expose
// var SelInst = new SelectionEngine();
SelInst = new SelectionEngine();
//~ $.ui.autocomplete.prototype._renderItem = function(ul, item) {
//~ var searchVal = $("#searchinput").val();
//~ var desc = extractContext(item.desc, searchVal);
//~ return $('<li onclick=\'var s = "'+item.label+'"; search(s);$("#searchinput").val(strSearchBar);\'></li>')
//~ .data('item.autocomplete', item)
//~ .append("<a><span class=\"labelresult\">" + item.label + "</span></a>" )
//~ .appendTo(ul);
//~ };
var SelInst = new SelectionEngine();
$('input#searchinput').autocomplete({
source: function(request, response) {
// console.log("in autocomplete:")
// labels initialized in settings, filled in updateSearchLabels
// console.log(labels.length)
// console.log(" - - - - - - - - - ")
matches = [];
var matcher = new RegExp($.ui.autocomplete.escapeRegex(request.term), "i");
// grep at heart
......@@ -646,18 +632,8 @@ TinaWebJS = function ( sigmacanvas ) {
});
}
// external usage: SelectorEngine*() , MultipleSelection2() ,
// enviroment.js:changeType()|changeLevel()|NodeWeightFilter()|EdgeWeightFilter
this.initListeners = function (categories, partialGraph) {
var SelInst = new SelectionEngine();
document.getElementById('edges-switch').checked = TW.customSettings.drawEdges
// £TODO test if still needed
$("#semLoader").hide();
$("#closeloader").click();
// to init handlers for tina GUI environment (run once on page load)
this.initGUIListeners = function () {
var body=document.getElementsByTagName('body')[0];
body.style.paddingTop="41px";
......@@ -738,16 +714,8 @@ TinaWebJS = function ( sigmacanvas ) {
}
});
// £TODO test if still needed
pushSWClick("social");
cancelSelection(false);
$("#tips").html(getTips());
// a bit costly, TODO make conditional or deprecated
// showMeSomeLabels(6);
......@@ -756,181 +724,12 @@ TinaWebJS = function ( sigmacanvas ) {
// #saveAs => toggle #savemodal initialized in html + bootstrap-native
this.SearchListeners();
// button CENTER
$("#lensButton").click(function () {
// new sigma.js
partialGraph.camera.goTo({x:0, y:0, ratio:1.2})
});
// ---------------------------------------------------------------------
// new sigma.js: sigma events bindings
// ---------------------
// cf. https://github.com/jacomyal/sigma.js/wiki/Events-API
// cases:
// 'click' - simple click, early event
// used for area (with global: cursor_size)
// 'clickNode'- simple click, second event if one node
// POSS easy in new sigma.js:
// add doubleClick to select node + neighboors
// when circle area select
// ========================
// 1st event, even before we know if there are nodes
TW.partialGraph.bind('click', function(e) {
// console.log("sigma click event e", e)
// case with a selector circle cursor handled here
if (cursor_size > 0) {
// actual click position, but in graph coords
var x = e.data.x
var y = e.data.y
// convert
var camCoords = TW.cam.cameraPosition(x,y)
// retrieve area nodes, using indexed quadtree and global cursor_size
var circleNodes = circleGetAreaNodes(
camCoords.x,
camCoords.y
)
// 1) clear previous while keeping its list (useful iff 'Add' checkBox)
var previousSelection = selections
cancelSelection(false)
// 2) show selection + do all related effects
var targeted = SelInst.SelectorEngine( {
addvalue:checkBox,
currsels:circleNodes,
prevsels:previousSelection
} )
if(targeted.length>0) {
SelInst.MultipleSelection2( {nodes:targeted} )
}
}
})
// when one node and normal click
// ===============================
TW.partialGraph.bind('clickNode', function(e) {
// console.log("clickNode event e", e.data.node)
// new sigma.js gives easy access to clicked node!
var theNodeId = e.data.node.id
// we keep the global selections and then clear it and all its effects
var previousSelection = selections
cancelSelection(false, {norender:true}); // no need to render before MS2
if (cursor_size == 0) {
var targeted = SelInst.SelectorEngine( {
addvalue:checkBox,
currsels:[theNodeId],
prevsels:previousSelection
} )
if(targeted.length>0) {
SelInst.MultipleSelection2( {nodes:targeted} )
}
}
// case with a selector circle cursor handled
// just before, at click event
})
// when click in the empty background
// ==================================
if (TW.deselectOnclickStage) {
TW.partialGraph.bind('clickStage', function(e) {
// console.log("clickStage event e", e)
if (! e.data.captor.isDragging
&& Object.keys(selections).length
&& ! cursor_size) {
// we clear selections and all its effects
cancelSelection(false);
}
})
}
// for all TW.cam.goTo (move/zoom) events
// ===============
var zoomTimeoutId = null
TW.cam.bind('coordinatesUpdated', function(e) {
$("#zoomSlider").slider("value",1/TW.cam.ratio)
})
// ---------------------------------------------------------------------
// POSS: bind to captors (0=>mouse, 1=>touch)
// TW.rend.captors[0].bind('mousemove', function(e) {
// console.log("mousemove event e", e.data.node)
//
// })
// ---------------------------------------------------------------------
// raw events (non-sigma): handlers attached to the container
// ==========
$("#sigma-contnr")
.mousemove(function(e){
if(!isUndef(partialGraph)) {
// show/move selector circle cursor
if(cursor_size>0) circleTrackMouse(e);
}
})
// POSSible for the future: add tools to contextmenu
// .contextmenu(function(){
// return false;
// })
// sliders events
// ==============
$("#zoomSlider").slider({
orientation: "vertical",
// new sigma.js current zoom ratio
value: partialGraph.camera.ratio,
min: 1 / sigmaJsMouseProperties.maxRatio, // ex x.5
max: 1 / sigmaJsMouseProperties.minRatio, // ex x32
// range: true,
step: .2,
value: 1,
slide: function( event, ui ) {
partialGraph.camera.goTo({
// POSS: make a transform to increase detail around x = 1
ratio: 1 / ui.value
});
}
});
$("#zoomPlusButton").click(function () {
var newRatio = TW.cam.ratio * .75
if (newRatio >= sigmaJsMouseProperties.minRatio) {
// triggers coordinatesUpdated which sets the slider cursor
partialGraph.camera.goTo({ratio: newRatio});
return false;
}
});
$("#zoomMinusButton").click(function () {
var newRatio = TW.cam.ratio * 1.25
if (newRatio <= sigmaJsMouseProperties.maxRatio) {
// triggers coordinatesUpdated which sets the slider cursor
partialGraph.camera.goTo({ratio: newRatio});
return false;
}
});
$("#layoutButton").click(function () {
sigma_utils.smartForceAtlas()
});
......@@ -964,77 +763,6 @@ TinaWebJS = function ( sigmacanvas ) {
});
if (TW.filterSliders) {
// args: for display: target div ,
// for context: family/type prop value,
// for values: the property to filter
NodeWeightFilter ( "#slidercat0nodesweight" ,
categories[0] ,
"size"
);
EdgeWeightFilter("#slidercat0edgesweight",
getCurrentTypeString(),
"weight"
);
}
$("#category1").hide();
//finished
var labelSizeTimeout = null
$("#sliderlabelsize").freshslider({
step:.25,
min:0,
max:5,
value: TW.partialGraph.settings('labelSizeRatio'),
bgcolor:"#27c470",
onchange:function(value){
if (labelSizeTimeout) {
clearTimeout(labelSizeTimeout)
}
labelSizeTimeout = setTimeout(function(){
if (TW.partialGraph.settings('labelSizeRatio') != value) {
var adaptedLabelThreshold = 7 - value
// console.log("value", value, "thres", adaptedLabelThreshold)
TW.partialGraph.settings('labelSizeRatio', value)
TW.partialGraph.settings('labelThreshold', adaptedLabelThreshold)
TW.partialGraph.render()
}
}, 200)
}
});
// //finished
// $("#slidercat1nodessize").freshslider({
// step:1,
// min:-20,
// max:20,
// value:0,
// bgcolor:"#FFA500",
// onchange:function(value){
// setTimeout(function (){
// // new sigma.js loop on nodes POSS optimize
// nds = TW.partialGraph.graph.nodes()
// console.log("init: slider resize")
// for(j=0 ; j<TW.partialGraph.nNodes ; j++){
// if (nds[j]
// && nds[j].type == TW.catSem) {
// var n = nds[j]
// var newval = parseFloat(TW.Nodes[n.id].size) + parseFloat((value-1))*0.3
// n.size = (newval<1.0)?1:newval;
// sizeMult[TW.catSem] = parseFloat(value-1)*0.3;
// }
// }
// partialGraph.render()
// },
// 100);
// }
// });
//Cursor Size slider
var cursorSlider = $("#unranged-value").freshslider({
step: 1,
......@@ -1052,14 +780,33 @@ TinaWebJS = function ( sigmacanvas ) {
cursorSlider.setValue(0)
});
// general listener: shift key in the window <=> add to selection
$(document).on('keyup keydown', function(e){
// changes the global boolean ("add node to selection" status) if keydown and SHIFT
checkBox = manuallyChecked || e.shiftKey
// //finished
// $("#slidercat1nodessize").freshslider({
// step:1,
// min:-20,
// max:20,
// value:0,
// bgcolor:"#FFA500",
// onchange:function(value){
// setTimeout(function (){
// // new sigma.js loop on nodes POSS optimize
// nds = TW.partialGraph.graph.nodes()
// console.log("init: slider resize")
// for(j=0 ; j<TW.partialGraph.nNodes ; j++){
// if (nds[j]
// && nds[j].type == TW.catSem) {
// var n = nds[j]
// var newval = parseFloat(TW.Nodes[n.id].size) + parseFloat((value-1))*0.3
// n.size = (newval<1.0)?1:newval;
// sizeMult[TW.catSem] = parseFloat(value-1)*0.3;
// }
// }
// partialGraph.render()
// },
// 100);
// }
// });
// show it in the real checkbox too
$('#checkboxdiv').prop("checked", manuallyChecked || e.shiftKey)
} );
// costly entire refresh (~400ms) only after stopped resizing for 3s
// NB: rescale middleware already reacted and, except for large win size changes, it handles the resize fine
......@@ -1071,13 +818,243 @@ TinaWebJS = function ( sigmacanvas ) {
}
winResizeTimeout = setTimeout(function() {
console.log('did refresh')
TW.partialGraph.refresh()
if (window.TW.partialGraph && window.TW.partialGraph.refresh) {
window.TW.partialGraph.refresh()
}
if (theHtml.classList) {
theHtml.classList.remove('waiting');
}
}, 3000)
}, true)
} // finish initListeners
// general listener: shift key in the window <=> add to selection
$(document).on('keyup keydown', function(e){
// changes the global boolean ("add node to selection" status) if keydown and SHIFT
checkBox = manuallyChecked || e.shiftKey
// show it in the real checkbox too
$('#checkboxdiv').prop("checked", manuallyChecked || e.shiftKey)
} );
} // finish envListeners
// to init local, instance-related listeners (need to run at new sigma instance)
// args: @partialGraph = a sigma instance
this.SigmaListeners = function(partialGraph) {
var SelInst = new SelectionEngine();
// sigma events bindings
// ---------------------
// cf. https://github.com/jacomyal/sigma.js/wiki/Events-API
// cases:
// 'click' - simple click, early event
// used for area (with global: cursor_size)
// 'clickNode'- simple click, second event if one node
// POSS easy in new sigma.js:
// add doubleClick to select node + neighboors
// when circle area select
// ========================
// 1st event, even before we know if there are nodes
partialGraph.bind('click', function(e) {
// console.log("sigma click event e", e)
// case with a selector circle cursor handled here
if (cursor_size > 0) {
// actual click position, but in graph coords
var x = e.data.x
var y = e.data.y
// convert
var camCoords = TW.cam.cameraPosition(x,y)
// retrieve area nodes, using indexed quadtree and global cursor_size
var circleNodes = circleGetAreaNodes(
camCoords.x,
camCoords.y
)
// 1) clear previous while keeping its list (useful iff 'Add' checkBox)
var previousSelection = selections
cancelSelection(false)
// 2) show selection + do all related effects
var targeted = SelInst.SelectorEngine( {
addvalue:checkBox,
currsels:circleNodes,
prevsels:previousSelection
} )
if(targeted.length>0) {
SelInst.MultipleSelection2( {nodes:targeted} )
}
}
})
// when one node and normal click
// ===============================
partialGraph.bind('clickNode', function(e) {
// console.log("clickNode event e", e.data.node)
// new sigma.js gives easy access to clicked node!
var theNodeId = e.data.node.id
// we keep the global selections and then clear it and all its effects
var previousSelection = selections
cancelSelection(false, {norender:true}); // no need to render before MS2
if (cursor_size == 0) {
var targeted = SelInst.SelectorEngine( {
addvalue:checkBox,
currsels:[theNodeId],
prevsels:previousSelection
} )
if(targeted.length>0) {
SelInst.MultipleSelection2( {nodes:targeted} )
}
}
// case with a selector circle cursor handled
// just before, at click event
})
// when click in the empty background
// ==================================
if (TW.deselectOnclickStage) {
partialGraph.bind('clickStage', function(e) {
// console.log("clickStage event e", e)
if (! e.data.captor.isDragging
&& Object.keys(selections).length
&& ! cursor_size) {
// we clear selections and all its effects
cancelSelection(false);
}
})
}
// for all TW.cam.goTo (move/zoom) events
// ===============
var zoomTimeoutId = null
TW.cam.bind('coordinatesUpdated', function(e) {
$("#zoomSlider").slider("value",1/TW.cam.ratio)
})
// ---------------------------------------------------------------------
// POSS: bind to captors (0=>mouse, 1=>touch)
// TW.rend.captors[0].bind('mousemove', function(e) {
// console.log("mousemove event e", e.data.node)
//
// })
// ---------------------------------------------------------------------
// raw events (non-sigma): handlers attached to the container
// ==========
$("#sigma-contnr")
.mousemove(function(e){
if(!isUndef(partialGraph)) {
// show/move selector circle cursor
if(cursor_size>0) circleTrackMouse(e);
}
})
// POSSible for the future: add tools to contextmenu
// .contextmenu(function(){
// return false;
// })
// sliders events
// ==============
$("#zoomSlider").slider({
orientation: "vertical",
// new sigma.js current zoom ratio
value: partialGraph.camera.ratio,
min: 1 / sigmaJsMouseProperties.maxRatio, // ex x.5
max: 1 / sigmaJsMouseProperties.minRatio, // ex x32
// range: true,
step: .2,
value: 1,
slide: function( event, ui ) {
partialGraph.camera.goTo({
// POSS: make a transform to increase detail around x = 1
ratio: 1 / ui.value
});
}
});
$("#zoomPlusButton").click(function () {
var newRatio = TW.cam.ratio * .75
if (newRatio >= sigmaJsMouseProperties.minRatio) {
// triggers coordinatesUpdated which sets the slider cursor
partialGraph.camera.goTo({ratio: newRatio});
return false;
}
});
$("#zoomMinusButton").click(function () {
var newRatio = TW.cam.ratio * 1.25
if (newRatio <= sigmaJsMouseProperties.maxRatio) {
// triggers coordinatesUpdated which sets the slider cursor
partialGraph.camera.goTo({ratio: newRatio});
return false;
}
});
if (TW.filterSliders) {
// args: for display: target div ,
// for context: family/type prop value,
// for values: the property to filter
NodeWeightFilter ( "#slidercat0nodesweight" ,
TW.categories[0] ,
"size"
);
EdgeWeightFilter("#slidercat0edgesweight",
getCurrentTypeString(),
"weight"
);
}
// node's label size
var labelSizeTimeout = null
$("#sliderlabelsize").freshslider({
step:.25,
min:0,
max:5,
value: sigmaJsDrawingProperties['labelSizeRatio'] || 1,
bgcolor:"#27c470",
onchange:function(value){
if (labelSizeTimeout) {
clearTimeout(labelSizeTimeout)
}
labelSizeTimeout = setTimeout(function(){
if (TW.partialGraph.settings('labelSizeRatio') != value) {
var adaptedLabelThreshold = 7 - value
// console.log("value", value, "thres", adaptedLabelThreshold)
TW.partialGraph.settings('labelSizeRatio', value)
TW.partialGraph.settings('labelThreshold', adaptedLabelThreshold)
TW.partialGraph.render()
}
}, 200)
}
});
document.getElementById('edges-switch').checked = TW.customSettings.drawEdges
cancelSelection(false);
}
};
......@@ -7,6 +7,71 @@ function writeBrand (brandString) {
document.getElementById('twbrand').innerHTML = brandString
}
function createFilechooserEl () {
var inputComment = document.createElement("p")
inputComment.innerHTML = `<strong>Choose a graph from your filesystem (gexf or json).</strong>`
inputComment.classList.add('comment')
inputComment.classList.add('centered')
var graphFileInput = document.createElement('input')
graphFileInput.id = 'localgraphfile'
graphFileInput.type = 'file'
graphFileInput.accept = 'application/xml,application/gexf,application/json'
graphFileInput.classList.add('centered')
// NB file input will trigger mainStartGraph() when the user chooses something
graphFileInput.onchange = function() {
if (this.files && this.files[0]) {
let clientLocalGraphFile = this.files[0]
// determine the format
let theFormat
if (/\.(?:gexf|xml)$/.test(clientLocalGraphFile.name)) {
theFormat = 'gexf'
}
else if (/\.json$/.test(clientLocalGraphFile.name)) {
theFormat = 'json'
}
else {
alert('unrecognized file format')
}
// retrieving the content
let rdr = new FileReader()
rdr.onload = function() {
if (! rdr.result || !rdr.result.length) {
alert('the selected file is not readable')
}
else {
// we might have a previous graph opened
if (TW.partialGraph && TW.partialGraph.graph) {
TW.partialGraph.graph.clear()
TW.partialGraph.refresh()
selections = []
}
// run
if (theFormat == 'json')
mainStartGraph(theFormat, JSON.parse(rdr.result), TW.instance)
else
mainStartGraph(theFormat, rdr.result, TW.instance)
}
}
rdr.readAsText(clientLocalGraphFile)
}
}
var filechooserBox = document.createElement('div')
filechooserBox.appendChild(inputComment)
filechooserBox.appendChild(graphFileInput)
return filechooserBox
}
//============================ < NEW BUTTONS > =============================//
// Documentation Level: *****
......
......@@ -98,6 +98,10 @@ TW.instance = new TinaWebJS('#sigma-contnr');
// add once our tw rendering and index customizations into sigma module
TW.instance.init()
// init the button, sliders and search handlers, also only once
TW.instance.initGUIListeners();
TW.instance.initSearchListeners();
// show the custom name of the app
writeBrand(TW.branding)
......@@ -108,58 +112,19 @@ console.log("Starting TWJS")
// if page is being run locally ==> only possible source shall be via file input
if (window.location.protocol == 'file:') {
// POSS we could actually provide this local file chooser in all cases
var graphFileInput = document.createElement('input')
graphFileInput.id = 'localgraphfile'
graphFileInput.type = 'file'
graphFileInput.accept = 'application/xml,application/gexf,application/json'
document.getElementById('gexfs').appendChild(graphFileInput)
// NB file input will trigger mainStartGraph() when the user chooses something
graphFileInput.onchange = function() {
if (this.files && this.files[0]) {
let clientLocalGraphFile = this.files[0]
// determine the format
let theFormat
if (/\.(?:gexf|xml)$/.test(clientLocalGraphFile.name)) {
theFormat = 'gexf'
}
else if (/\.json$/.test(clientLocalGraphFile.name)) {
theFormat = 'json'
}
else {
alert('unrecognized file format')
}
// retrieving the content
let rdr = new FileReader()
let inputDiv = document.getElementById('localInput')
inputDiv.style.display = 'block'
rdr.onload = function() {
if (! rdr.result || !rdr.result.length) {
alert('the selected file is not readable')
}
else {
// we might have a previous graph opened
if (TW.partialGraph && TW.partialGraph.graph) {
TW.partialGraph.graph.clear()
TW.partialGraph.refresh()
selections = []
}
var remark = document.createElement("p")
remark.innerHTML = `You're running project explorer as a local html file (no syncing).`
remark.classList.add('comment')
remark.classList.add('centered')
inputDiv.appendChild(remark)
// run
if (theFormat == 'json')
mainStartGraph(theFormat, JSON.parse(rdr.result), TW.instance)
else
mainStartGraph(theFormat, rdr.result, TW.instance)
}
}
rdr.readAsText(clientLocalGraphFile)
}
}
// user can open a gexf or json from his fs
// POSS we could actually provide this local file chooser in all cases
var graphFileInput = createFilechooserEl()
inputDiv.appendChild(graphFileInput)
}
// traditional cases: remote read from API or prepared server-side file
else {
......@@ -627,6 +592,9 @@ function mainStartGraph(inFormat, inData, twInstance) {
// by default category0 is the initial type
$(".category1").hide();
// now that we have a sigma instance, let's bind our handlers to it
TW.instance.SigmaListeners(TW.partialGraph)
// [ / Poblating the Sigma-Graph ]
......@@ -780,7 +748,8 @@ function mainStartGraph(inFormat, inData, twInstance) {
// REFA new sigma.js
TW.partialGraph.camera.goTo({x:0, y:0, ratio:0.9, angle: 0})
twInstance.initListeners(TW.categories , TW.partialGraph);
$("#category1").hide();
// mostly json data are extracts provided by DB apis => no positions
if (inFormat == "json") TW.fa2enabled = true
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment