Commit d10bdadf authored by Romain Loth's avatar Romain Loth

WIP: experimenting with input param syntax

parent 180e3f09
......@@ -8,11 +8,11 @@
"gexfs": {
"ProgrammeDesCandidats.enrichi.gexf": {
"social": {},
"semantic": {}
"semantic": {"table":"terms"}
},
"ProgrammeDesCandidats.gexf": {
"social": {},
"semantic": {}
"semantic": {"table":"terms"}
}
}
},
......
......@@ -27,7 +27,7 @@ This will still evolve but the main steps for any graph initialization messily u
- `somenode.attributes`: the `attributes` property is always an object
- any attribute listed in the sourcenode.attributes will be indexed if the TW.scanClusters flag is true
- the mapping from attribute values to matching nodes is in TW.Clusters.aType.anAttr.aValue.map !
- the mapping from attribute values to matching nodes is in TW.Clusters.aType.anAttr.aValue.map
- coloration: "`age`" "`growth_rate`" + any attribute of type float or int
- clustering: "`cluster_index`" ou nom figurant dans `TW.nodeClusAtt`
- vocabulary: (en cours) any attribute of type string and where the amount of distinct values is < TW.somesettings
......
......@@ -21,16 +21,16 @@
Widely available font (possibly available without this cdn request, actually).
Also condensed is good for labels in crowded graphs
-->
<link href="https://fonts.googleapis.com/css?family=Ubuntu+Condensed" rel="stylesheet" type='text/css'>
<!-- <link href="https://fonts.googleapis.com/css?family=Ubuntu+Condensed" rel="stylesheet" type='text/css'> -->
<!-- Roboto
Good for tweets if Helvetica is not present
-->
<link href="https://fonts.googleapis.com/css?family=Roboto" rel="stylesheet" type='text/css'>
<!-- <link href="https://fonts.googleapis.com/css?family=Roboto" rel="stylesheet" type='text/css'> -->
<!-- Crete Round
Original *and* informative :) -->
<link href='https://fonts.googleapis.com/css?family=Crete+Round:400,400italic&subset=latin-ext' rel='stylesheet' type='text/css'>
<!-- <link href='https://fonts.googleapis.com/css?family=Crete+Round:400,400italic&subset=latin-ext' rel='stylesheet' type='text/css'> -->
<!-- Lora
"book" (roman style) + nice heading -->
......@@ -42,7 +42,7 @@
<!-- Sahitya & Gurajada
"book" (beautiful quality roman) + devanagari support + telugu -->
<link href="https://fonts.googleapis.com/css?family=Gurajada" rel="stylesheet">
<!-- <link href="https://fonts.googleapis.com/css?family=Gurajada" rel="stylesheet"> -->
<!-- <link href="https://fonts.googleapis.com/css?family=Sahitya" rel="stylesheet"> -->
<!-- Itim
......@@ -411,7 +411,7 @@
<!-- class="my-legend" (absolute position bottom left) -->
<div id="legend_for_clusters" class="over-panels"></div>
<div id="legend-for-clusters" class="over-panels"></div>
</div>
</div>
......
......@@ -255,8 +255,8 @@ function set_ClustersLegend ( daclass, groupedByTicks ) {
//TW.partialGraph.states.slice(-1)[0].LouvainFait = true
$("#legend_for_clusters").removeClass( "my-legend" )
$("#legend_for_clusters").html("")
$("#legend-for-clusters").removeClass( "my-legend" )
$("#legend-for-clusters").html("")
if(daclass==null) return;
if (daclass=="clust_louvain")
......@@ -281,7 +281,7 @@ function set_ClustersLegend ( daclass, groupedByTicks ) {
if (!groupedByTicks && (!TW.Clusters[curType] || !TW.Clusters[curType][daclass])) {
console.warn(`no class bins for ${daclass}, displaying no legend`)
$("#legend_for_clusters").hide()
$("#legend-for-clusters").hide()
}
else {
var LegendDiv = ""
......@@ -318,9 +318,9 @@ function set_ClustersLegend ( daclass, groupedByTicks ) {
LegendDiv += ' </ul>'
LegendDiv += ' </div>'
$("#legend_for_clusters").addClass( "my-legend" );
$("#legend_for_clusters").html( LegendDiv )
$("#legend_for_clusters").show()
$("#legend-for-clusters").addClass( "my-legend" );
$("#legend-for-clusters").html( LegendDiv )
$("#legend-for-clusters").show()
}
}
......
/* any block over the graph (legend, histogram...) */
.over-panels {
z-index: 5 ; /* over the graph */
padding: 5px;
-moz-border-radius: 3px;
-webkit-border-radius: 3px;
-border-radius: 3px;
......@@ -25,38 +24,25 @@
font-size: 70%;
float:right;
margin-top:.5em;
margin-right:1em
margin-right:1em;
}
""
/* LEGEND PANEL */
.legend_for_clusters {
position:absolute;
bottom:1px;
left:1px;
border:solid 1px red;
font-size:xx-small;
}
/*
.my-legend * {
position: relative;
z-index: 5;
}*/
.my-legend {
position:fixed;
z-index: 5;
/* width: we set it equal or smaller than #lefttopbox width */
width:18%;
max-width:18%;
max-height: 25%;
padding: 0 5px;
overflow-y:scroll;
bottom:5px;
left:5px;
bottom:0;
left:0;
border:solid 1px black;
background-color:white;
opacity: 0.8;
color:#250587;
margin: 7px;
font-size:small;
}
.my-legend .legend-title {
......@@ -286,6 +272,8 @@
overflow: hidden;
text-align:center;
padding: .3em 1em 1em 1em;
display: none; /* because no are nodes selected at page init time */
}
#current-selection {
......
......@@ -2,13 +2,15 @@
var TW = {}
// POSSIBLE: group like TW.settings ?
// £TODO separate files for TW.vars and TW.settings (configfile)
TW.geomap = false;
TW.colorByAtt = false;
TW.twittertimeline = false;
TW.minimap=false;
TW.getAdditionalInfo = true;// True: Activate TopPapers feature.
TW.filemenu = 'db.json'
// TW.mainfile = "data/mysuperproject/my.gexf"
TW.mainfile = "data/politoscope/ProgrammeDesCandidats.enrichi.gexf"
TW.APINAME = "http://127.0.0.1:5000/twitter_search";
......@@ -16,15 +18,7 @@ var TW = {}
TW.bridge={};
TW.bridge["forFilteredQuery"] = "services/api/graph";
TW.bridge["forNormalQuery"] = "services/api/graph";
TW.gexfDict={};
TW.gexfDictReverse={}
for (var i in TW.gexfDict){
TW.gexfDictReverse[TW.gexfDict[i]]=i;
}
TW.field = {}
// field["data/20141128_GPs_03_bi.gexf"] = "ISItermsfirstindexing";
// field["data/20141215_GPs_04.gexf"] = "ISItermsfirstindexing";
TW.gexfPaths={};
TW.Relations = {}
// module_names to load
......@@ -40,7 +34,7 @@ var TW = {}
TW.SystemState = {}
TW.SystemState.level = true;
TW.SystemState.type = [ true, false ] // usually overridden by makeSystemStates
TW.SystemState.type = [ true, false ] // usually overridden by initialActivetypes
TW.SystemState.selections = [];
TW.SystemState.opposites = [];
TW.catSoc = "Document";
......@@ -53,7 +47,8 @@ var SigmaUtils = function () {};
var TinaWebJS = function () {};
// node sizes
// ----------
var sizeMult = [];
sizeMult[TW.catSoc] = 0.0;
sizeMult[TW.catSem] = 0.0;
......@@ -70,24 +65,20 @@ var desirableTagCloudFont_MAX=20;
var desirableNodeSizeMIN=1;
var desirableNodeSizeMAX=12;
// apparently not used ?
var desirableScholarSize=6; //Remember that all scholars have the same size!
/*
*Three states:
* - true: fa2 auto-running at start
* - false: fa2 stopped at start, button exists
* - "off": button doesn't exist, fa2 stopped forever
**/ TW.fa2enabled=true;//"off";
TW.minNodesForAutoFA2 = 5
// layouts
// -------
// just a simple flag on this one
TW.disperseAvailable=false; // disperseButton hidden at start, disperse stopped forever
TW.fa2Available=false; // fa2Button hidden at start, fa2 stopped forever
// if fa2Button:
TW.fa2enabled=0; // fa2 auto-running at start ?
TW.minNodesForAutoFA2 = 5 // fa2 not run if graph has less nodes than this threshold
// ============ < / DEVELOPER OPTIONS > ============
TW.branding = 'test bipart'
TW.libspath = 'libs'
TW.libspath = 'libs' // NB path vars should not be used after page load
TW.nodeClusAtt = "modularity_class"
......@@ -126,7 +117,7 @@ TW.debugFlags = {
logParsers: false, // ...about parsing said data
logFacets: false, // ...about parsing node attribute:value facets
logSettings: false, // ...about settings at Tina and Sigma init time
logSelections: false
logSelections: true
}
// triggers overriding sigma.canvas renderers: nodes.def, labels.def, edges.def
......
......@@ -736,36 +736,46 @@ TinaWebJS = function ( sigmacanvas ) {
// button CENTER
$("#lensButton").click(function () {
// new sigma.js
partialGraph.camera.goTo({x:0, y:0, ratio:1.2})
TW.partialGraph.camera.goTo({x:0, y:0, ratio:1.2})
});
$("#layoutButton").click(function () {
sigma_utils.smartForceAtlas()
});
if (TW.fa2Available) {
$("#layoutButton").click(function () {
sigma_utils.smartForceAtlas()
});
}
else {
$("#layoutButton").hide()
}
$("#noverlapButton").click(function () {
if(! TW.partialGraph.isNoverlapRunning()) {
// show waiting cursor on page and button
theHtml.classList.add('waiting');
this.style.cursor = 'wait'
// and waiting icon
this.insertBefore(createWaitIcon('noverlapwait'), this.children[0])
var listener = TW.partialGraph.startNoverlap();
var noverButton = this
listener.bind('stop', function(event) {
var stillRunning = document.getElementById('noverlapwait')
if (stillRunning) {
theHtml.classList.remove('waiting');
noverButton.style.cursor = 'auto'
stillRunning.remove()
}
});
if (TW.disperseAvailable) {
$("#noverlapButton").click(function () {
if(! TW.partialGraph.isNoverlapRunning()) {
// show waiting cursor on page and button
theHtml.classList.add('waiting');
this.style.cursor = 'wait'
// and waiting icon
this.insertBefore(createWaitIcon('noverlapwait'), this.children[0])
var listener = TW.partialGraph.startNoverlap();
var noverButton = this
listener.bind('stop', function(event) {
var stillRunning = document.getElementById('noverlapwait')
if (stillRunning) {
theHtml.classList.remove('waiting');
noverButton.style.cursor = 'auto'
stillRunning.remove()
}
});
return;
}
});
}
else {
$("#noverlapButton").hide()
}
return;
}
});
$("#edges-switch").click(function () {
sigma_utils.toggleEdges(this.checked)
......@@ -1091,7 +1101,7 @@ TinaWebJS = function ( sigmacanvas ) {
var possibleActivetypes = {}
var N=Math.pow(2 , cats.length);
for (i = 0; i < N; i++) {
for (var i = 0; i < N; i++) {
let bin = (i).toString(2)
let bin_splitted = []
......
......@@ -74,18 +74,16 @@ var AjaxSync = (function(TYPE, URL, DATA, DT) {
return Result;
}).index();
function getGexfPath(v){
var gexfpath=(TW.gexfDictReverse[v])?TW.gexfDictReverse[v]:v;
return gexfpath;
}
function jsActionOnGexfSelector(gexfBasename , db_json){
db_json = (db_json)?"&mode=db.json":""
let gexfLegend = gexfBasename+".gexf"
if(getGexfPath[gexfLegend])
window.location=window.location.origin+window.location.pathname+"?file="+encodeURIComponent(getGexfPath(gexfLegend))+db_json;
else
window.location=window.location.origin+window.location.pathname+"?file="+encodeURIComponent( gexfLegend )+db_json;
function jsActionOnGexfSelector(gexfBasename){
let gexfPath = TW.gexfPaths[gexfBasename] || gexfBasename+".gexf"
let serverPrefix = ''
var pathcomponents = window.location.pathname.split('/')
for (var i in pathcomponents) {
if (pathcomponents[i] != 'explorerjs.html')
serverPrefix += '/'+pathcomponents[i]
}
var newDataRes = AjaxSync({ URL: window.location.origin+serverPrefix+'/'+gexfPath });
mainStartGraph(newDataRes["format"], newDataRes["data"], TW.instance)
}
// === [ what to do at start ] === //
......@@ -265,29 +263,17 @@ function syncRemoteGraphData () {
var the_file = "";
// ===================
// direct urlparam file case
if( !isUndef(getUrlParam.file) ) {
the_file = getUrlParam.file
}
// direct file fallback case: specified file in settings_explorer
else if (TW.mainfile && linkCheck(TW.mainfile)) {
console.log("no @file arg: trying TW.mainfile from settings")
the_file = TW.mainfile;
}
// overall fallback case: try to open a listing of files (by default: db.json)
// TODO rename 'mode=' argkey into something more descriptive like 'menufile='
// here and in caller apps like tweetoscope etc.
else {
// we'll first retrieve the menu of available files in db.json, then get the real data in a second ajax
var infofile = ''
if ( !isUndef(getUrlParam.mode) ) {
infofile = getUrlParam.mode
}
// default
else {
infofile = "db.json"
}
// TODO check if legacy apps use db.json name otherwise use mode == 'menufile'
// and 'db.json' should be hardcoded (safer)
// if (!isUndef(getUrlParam.mode) && getUrlParam.mode == 'menufile') {
// menufile case comes before single file because it uses urlparam file too
if (!isUndef(getUrlParam.mode) && getUrlParam.mode == 'db.json') {
console.log("no @file arg nor TW.mainfile: trying FILEMENU db.json")
// we'll first retrieve the menu of available files in db.json, then get the real data in a second ajax
var infofile = "db.json"
if (TW.debugFlags.logFetchers) console.info(`attempting to load infofile ${infofile}`)
var preRES = AjaxSync({ URL: infofile, DT:"json" });
......@@ -296,7 +282,6 @@ function syncRemoteGraphData () {
console.log('initial AjaxSync result preRES', preRES)
}
var first_file = "" , first_path = ""
for( var path in preRES.data ) {
......@@ -315,22 +300,29 @@ function syncRemoteGraphData () {
the_file = first_path+"/"+getUrlParam.file
}
var files_selector = '<select onchange="jsActionOnGexfSelector(this.value , true);">'
var files_selector = '<select onchange="jsActionOnGexfSelector(this.value);">'
for( var path in preRES.data ) {
var the_gexfs = preRES.data[path]["gexfs"]
for(var aGexf in the_gexfs) {
var gexfBasename = aGexf.replace(/\.gexf$/, "") // more human-readable in the menu
TW.gexfPaths[gexfBasename] = path+"/"+aGexf
// ex : "RiskV2PageRank1000.gexf":data/AXA/RiskV2PageRank1000.gexf
// (we assume there's no duplicate basenames)
if (TW.debugFlags.logFetchers)
console.log("\t\t\t"+gexfBasename+ " -> table:" +the_gexfs[aGexf]["semantic"]["table"] )
TW.field[path+"/"+aGexf] = the_gexfs[aGexf]["semantic"]["table"]
// -------------------------->8------------------------------------------
// £TODO this part is underspecified
// if used in some usecases, port it to nodetypes
// otherwise remove
// TW.field[path+"/"+aGexf] = the_gexfs[aGexf]["semantic"]["table"]
// ex : data/AXA/RiskV2PageRank5000.gexf:"ISItermsAxa_2015"
// -------------------------->8------------------------------------------
TW.gexfDict[path+"/"+aGexf] = aGexf
// ex : data/AXA/RiskV2PageRank1000.gexf:"RiskV2PageRank1000.gexf"
let cssFileSelected = (the_file==(path+"/"+aGexf))?"selected":""
files_selector += '<option '+cssFileSelected+'>'+gexfBasename+'</option>'
......@@ -339,8 +331,21 @@ function syncRemoteGraphData () {
break;
}
files_selector += "</select>"
console.log("files_selector HTML", files_selector)
$("#network").html(files_selector)
}
// direct urlparam file case
else if( !isUndef(getUrlParam.file) ) {
the_file = getUrlParam.file
}
// direct file fallback case: specified file in settings_explorer
else if (TW.mainfile && linkCheck(TW.mainfile)) {
console.log("no @file arg: trying TW.mainfile from settings")
the_file = TW.mainfile;
}
else {
console.warn("No specified input!")
}
var finalRes = AjaxSync({ URL: the_file });
inData = finalRes["data"]
......
......@@ -749,31 +749,32 @@ function updateRelations(typedRelations, edgeCateg, srcId, tgtId){
// To fill the reverse map: values => nodeids of a given type
function updateValueFacets(facetIdx, Atts_2_Exclude, aNode){
function updateValueFacets(facetIdx, Atts_2_Exclude, aNode) {
if (!facetIdx[aNode.type]) facetIdx[aNode.type]={}
for (var at in aNode.attributes) {
if (!facetIdx[aNode.type][at]) facetIdx[aNode.type][at]={'vals':[],'map':{}}
let castVal = Number(aNode.attributes[at])
// Identifying the attribute datatype: exclude strings and objects
if ( isNaN(castVal) ) {
if (!Atts_2_Exclude[at]) Atts_2_Exclude[at]=true;
// TODO: this old Atts_2_Exclude strategy could be replaced,
// not to exclude but to store the datatype somewhere like facetIdx[aNode.type][at].dtype
// => the datatype would be a condition (no bins if not numeric, etc.)
// => it would also allow to index text values (eg country, affiliation, etc.)
// with the strategy "most frequent distinct values" + "others"
// which would be useful (eg country, affiliation, etc.) !!!
}
// if ( isNaN(castVal) ) {
// if (!Atts_2_Exclude[at]) Atts_2_Exclude[at]=true;
//
// // TODO: this old Atts_2_Exclude strategy could be replaced,
// // not to exclude but to store the datatype somewhere like facetIdx[aNode.type][at].dtype
// // => the datatype would be a condition (no bins if not numeric, etc.)
// // => it would also allow to index text values (eg country, affiliation, etc.)
// // with the strategy "most frequent distinct values" + "others"
// // which would be useful (eg country, affiliation, etc.) !!!
//
// }
// numeric attr => build facets
else {
// else {
if (!facetIdx[aNode.type][at].map[castVal]) facetIdx[aNode.type][at].map[castVal] = []
facetIdx[aNode.type][at].vals.push(castVal) // for ordered scale
facetIdx[aNode.type][at].map[castVal].push(aNode.id) // inverted index
}
// }
}
return [facetIdx, Atts_2_Exclude]
}
......@@ -940,14 +941,15 @@ function dictfyJSON( data , categories ) {
nodes[node.id] = node;
console.log(n.attributes)
// creating a faceted index from node.attributes
if (TW.scanClusters) {
[tmpVals, Atts_2_Exclude] = updateValueFacets(tmpVals, Atts_2_Exclude, node)
}
}
// test: json with string facet (eg lab affiliation in comex)
console.log(tmpVals['Document'])
TW.Clusters = facetsBinning (tmpVals, Atts_2_Exclude)
colorList.sort(function(){ return Math.random()-0.5; });
......
......@@ -502,40 +502,43 @@ SigmaUtils = function () {
// - edges management (turns them off and restores them after finished)
this.smartForceAtlas = function (fa2duration) {
if (!fa2duration) {
fa2duration = parseInt(TW.fa2milliseconds) || 4000
}
if (TW.fa2Available) {
if (!fa2duration) {
fa2duration = parseInt(TW.fa2milliseconds) || 4000
}
// togglability case
if(TW.partialGraph.isForceAtlas2Running()) {
this.ourStopFA2()
return;
}
// normal case
else {
if ( TW.fa2enabled && TW.partialGraph.graph.nNodes() >= TW.minNodesForAutoFA2) {
// hide edges during work for smaller cpu load
if (TW.partialGraph.settings('drawEdges')) {
this.toggleEdges(false)
}
// togglability case
if(TW.partialGraph.isForceAtlas2Running()) {
this.ourStopFA2()
return;
}
// normal case
else {
if ( TW.fa2enabled && TW.partialGraph.graph.nNodes() >= TW.minNodesForAutoFA2) {
// hide edges during work for smaller cpu load
if (TW.partialGraph.settings('drawEdges')) {
this.toggleEdges(false)
}
TW.partialGraph.startForceAtlas2();
TW.partialGraph.startForceAtlas2();
var icon = createWaitIcon('layoutwait')
var btn = document.querySelector('#layoutButton')
btn.insertBefore(icon, btn.children[0])
var icon = createWaitIcon('layoutwait')
var btn = document.querySelector('#layoutButton')
btn.insertBefore(icon, btn.children[0])
setTimeout(function(){
// NB in here scope: 'this' is the window
if (TW.partialGraph.isForceAtlas2Running())
sigma_utils.ourStopFA2()
},
fa2duration)
setTimeout(function(){
// NB in here scope: 'this' is the window
if (TW.partialGraph.isForceAtlas2Running())
sigma_utils.ourStopFA2()
},
fa2duration)
return;
}
return;
}
}
}
}
} // /SigmaUtils object
......@@ -1203,7 +1206,7 @@ function colorsBy(daclass) {
for(var j in TW.nodeIds) {
var the_node = TW.Nodes[ TW.nodeIds[j] ]
// ££TODO put louvain in graph.nodes() like other attrs ??
// then possible to use TW.partialGraph.graph.nodes(TW.nodeIds[j])
......
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