Commit 24648495 authored by Romain Loth's avatar Romain Loth

finished reldb entries and logic: gui tab mecanisms + doc

parent 79407bcd
......@@ -13,9 +13,9 @@ Après commits de la semaine 26-30 juin 2017, une structure plus facile pour les
│   └── (fusionné avec ./doc)
├── data
│   └── (graphes par sous-projets)
├── db.json
├── explorerjs.html <= point d'entrée lancement
├── settings_explorerjs.js <= point d'entrée config
├── settings_explorerjs.js <= config générale
├── db.json <= config additionnelle par sources gexf/json
├── favicon.ico
├── LICENSE
├── README.md
......@@ -34,7 +34,6 @@ Après commits de la semaine 26-30 juin 2017, une structure plus facile pour les
│   ├── jquery-3
│   ├── readmore.js
│   ├── sigma_v1.2
│   ├── sigma_v1.5
│   └── tweets
|
├── twmain <= ancien dossier tinawebJS
......
......@@ -64,55 +64,103 @@ Having a node0.name entry and optionally a node1.name is enough to display the g
The servermenu file also allows configuration of associated queries for selected node(s): **relatedDocs**
To enable it, you need to add to your node entry the `reldbfile` key:
To enable it, you need to add to your node entry the `reldbs` key with minimally a db type :
```json
"node0": {
"name": "$$blabla",
"reldbfile": "$$relpath/to/csv/or/sqlite"
"reldbs": {
"$$myType" : {}
}
}
```
The presence of this property `reldbfile` makes the API usable in db.json.
The presence of this property "reldbs" makes the API usable in db.json.
##### More relatedDocs settings
In addition, for full configuration, the following entries can be set under node0 or node1.
###### => for a CSV doc-by-doc table
Expected type is `"csv"` and you should fill the columns to search in.
Expected type is `"csv"` and you should fill the columns to search in and the template to use to render hits
```json
"reldbtype": "csv",
"reldbqcols": ["list", "of", "columns", "to", "search", "in", "for", "node0"]
"reldbs": {
"csv" : {
"file": "$$relpath/to/some.csv",
"qcols": ["list", "of", "cols", "to", "search", "in", "for", "node0"],
"template": "bib_details"
}
}
```
###### Real life example
###### => for a cortext sql base
Expected type is `"CortextDB"` and you should fill the tables to search in.
```json
"reldbs": {
"CortextDB": {
"file": "$$relpath/to/some.db",
"qtable": "$$tableNameToSearchIn",
"template": "cortext_with_link"
}
}
```
###### => for twitter queries
Expected type is `"twitter"` and no additional conf is needed (POSS for the future: add twitter query context, ex: "Présidentielles 2017 AND (query)").
```json
"reldbs": {
"twitter": {}
}
```
###### Real life examples
```json
"data/gargistex": {
"first": "shale_and_ice.gexf",
"graphs": {
"shale_and_ice.gexf": {
"node0": {
"name": "terms",
"reldbtype": "csv",
"reldbfile": "shale_and_ice.csv",
"reldbqcols": ["title", "abstract"]
"graphs":{
"model_calibration.gexf": {
"node0": {
"name": "terms",
"reldbs": {
"csv": {
"file": "model_calibration.csv",
"qcols": ["title"],
"template": "bib_details"
},
"twitter": {}
}
}
}
}
},
"data/test": {
"first" : "mini_for_csv.gexf",
"graphs": {
"mini_for_csv.gexf": {
"node0": {
"name": "terms",
"reldbs": {
"csv": {
"file": "mini_for_csv.csv",
"qcols": ["title","keywords","text"],
"template": "bib_details"
},
"twitter": {}
}
},
"model_calibration.gexf": {
"node0": {
"name": "terms",
"reldbtype": "csv",
"reldbfile": "model_calibration.csv",
"reldbqcols": ["title", "abstract"]
"node1": {
"name": "authors",
"reldbs": {
"csv": {
"file": "mini_for_csv.csv",
"qcols": ["author"],
"template": "bib_details"
}
}
}
}
}
}
```
###### => for CortextDB SQL tables
Expected type is `"CortextDB"` and you should fill the table to search in.
```json
"reldbtype": "CortextDB",
"reldbqtable": []
```
In the last exemple, we have two nodetypes:
- node0 allows both CSV and twitter relatedDocs tabs.
- node1 allows only the CSV relatedDocs tab.
This is a stub for a future documentation for developers.
#### About settings
- system-wide settings are in `settings_explorerjs.js`
- source-by-source settings (nodetypes, relatedDocs APIs) are in `db.json`
## Graph input choices
......@@ -13,7 +16,7 @@ Tina allows 3 main ways of input :
The `sourcemode` value is by default the one in settings_explorerjs.js (`TW.conf.sourcemode`), unless an url argument of the same name is present.
The `serverfile` option has an extended version called `servermenu`. It opens a list of files called `db.json` on the server, providing a menu to choose from it.
The `serverfile` option has an extended version called `servermenu`. It opens the list of files from `db.json` on the server, providing a menu to choose from it.
The detailed implementation of these choices can be found in the function `syncRemoteGraphData()` in main.js.
......@@ -32,7 +35,8 @@ This will still evolve but the main steps for any graph initialization messily u
4. [`main.js`] mainStartGraph() function runs all the rest
1. precomputes display properties (grey color, etc.)
2. calls [`sigmaUtils`] where the function `FillGraph()` was a central point for filtering and preparing properties but now with 2 and 3 it just creates a filtered copy of the nodes and edges of the current active types to a new structure that groups them together (POSSIBLE remove this extra step)
3. back in [`main.js`], finally all sigma settings (user + defaults) are merged and we initialize the sigma instance (`new sigma` etc.)
3. back in [`main.js`], finally all sigma settings (user + defaults) are merged and we initialize the sigma instance (`new sigma` etc.)
at this point, any additional conf located in db.json is used for nodeTypes and relatedDocsTypes
4. finally a call to [`TinawebJS`] initializes the action listeners and this phase should crucially initialize items that need the sigma instance (because they may depend the displayed categories, the number of displayed nodes, etc)
......
{
"data/ClimateChange": {
"graphs": {
"Maps_S_800.gexf": {
"node0": {
"name": "ISItermsWhitelistV2Oct_5 &amp; ISItermsWhitelistV2Oct_5",
"reldbs": {
"CortextDB": {
"file": "wos_climate-change_title_2014-2015.db",
"qtable": "ISItermsWhitelistV2Oct_5",
"template": "cortext_with_link"
},
"twitter": {}
}
}
}
}
},
"data/gargistex": {
"first" : "shale_and_ice.gexf",
"graphs":{
......@@ -81,23 +98,6 @@
}
}
},
"data/ClimateChange": {
"graphs": {
"Maps_S_800.gexf": {
"node0": {
"name": "ISItermsWhitelistV2Oct_5 &amp; ISItermsWhitelistV2Oct_5",
"reldbs": {
"CortextDB": {
"file": "wos_climate-change_title_2014-2015.db",
"qtable": "ISItermsWhitelistV2Oct_5",
"template": "cortext_with_link"
},
"twitter": {}
}
}
}
}
},
"graphapi": {
"__comment__": "special subproject for api sourcemode",
"__comment__": "allows setting nodetypes and reldocs for api graph sources",
......
......@@ -31,7 +31,7 @@ TW.conf = (function(TW){
TWConf.getRelatedDocs = true
TWConf.relatedDocsMax = 10
// fallback type (overriden by new replacement from db.json)
// fallback type (if no detailed source-by-source conf from db.json)
TWConf.relatedDocsType = "csv" // accepted: "twitter" | "csv" | "CortextDB"
// POSSible: "elastic"
......
......@@ -26,7 +26,7 @@
background-color: #BBB;
}
#topPapers {
.topPapers {
-moz-box-shadow: none ;
-webkit-box-shadow: none ;
box-shadow: none;
......
......@@ -325,12 +325,14 @@ ul.infoitems {
border-right: 1px solid #222;
}
#topPapers{
.topPapers{
display: none;
color:black;
}
.tab-pane {
transition: height 0.5s ease-out;
}
......
......@@ -1058,93 +1058,8 @@ var TinaWebJS = function ( sigmacanvas ) {
let moreConfKey = optionalConfEntry || TW.File
let ul = document.getElementById('reldocs-tabs')
let divs = document.getElementById('reldocs-boxes')
resetTabs(initialActivetypes, TW.gmenuInfos[moreConfKey])
// remove any previous tabs
ul.innerHTML = ""
divs.innerHTML = ""
TW.gui.reldocTabs = [{},{}]
// for all existing nodetypes
for (let nodetypeId in initialActivetypes) {
let additionalConf = TW.gmenuInfos[moreConfKey][nodetypeId]
if (TW.conf.debug.logSettings)
console.log ("additionalConf for this source", additionalConf)
let possibleAPIs = []
if (additionalConf.reldbs) {
possibleAPIs = additionalConf.reldbs
console.log("Tabs init from db.json")
}
else {
possibleAPIs = [TW.conf.relatedDocsType]
console.log("Tabs init from default")
}
if (additionalConf.reldbs) {
for (var possibleAPI in possibleAPIs){
// the tab's id
let tabref = `rd-${nodetypeId}-${possibleAPI}`
// create valid tabs
let newLi = document.createElement('li')
newLi.setAttribute("role", "presentation")
let newRDTab = document.createElement('a')
newRDTab.text = `${possibleAPI} (${nodetypeId==0?'sem':'soc'})`
newRDTab.href = "#"+tabref
newRDTab.setAttribute("role", "tab")
newRDTab.dataset.toggle = 'tab'
newRDTab.dataset.reldocstype = possibleAPI
newRDTab.dataset.nodetype = nodetypeId
newRDTab.setAttribute("class", `for-nodecategory-${nodetypeId}`)
// keep access
TW.gui.reldocTabs[nodetypeId][possibleAPI] = newRDTab
// create corresponding content box
let newContentDiv = document.createElement('div')
newContentDiv.setAttribute("role", "tabpanel")
newContentDiv.setAttribute("class", "topPapers tab-pane")
newContentDiv.id = tabref
// add to DOM
ul.append(newLi)
newLi.append(newRDTab)
divs.append(newContentDiv)
// select currently preferred reldoc tabs
if (possibleAPI == TW.conf.relatedDocsType) {
newLi.classList.add("active")
newContentDiv.classList.add("active")
}
}
// afterwards to already have all initialized rdtypes in DOM
for (let rdtype in TW.gui.reldocTabs[nodetypeId]) {
let tab = TW.gui.reldocTabs[nodetypeId][rdtype]
// init toggle mecanisms (bootstrap.native/#componentTab)
// (just used for the tabs active/inactive handling,
// content is *always* topPapers and we modify it ourselves)
new Tab(tab);
// add handler to switch relatedDocsType
tab.addEventListener('click', function(e){
let reldType = e.target.dataset.reldocstype
let nodeType = e.target.dataset.nodetype
let qWords = queryForType(nodeType)
console.log('tab click', `rd-${nodeType}-${reldType}`)
getTopPapers(qWords, nodeType, reldType, `rd-${nodeType}-${reldType}`)
})
}
}
}
}
// select currently active sliders
......
......@@ -1128,6 +1128,120 @@ function createWaitIcon(idname, width) {
return icon
}
activateRDTab = function(elTgt) {
let relDbType = elTgt.dataset.reldocstype
let ndTypeId = elTgt.dataset.nodetype
let tabs = document.querySelectorAll('ul#reldocs-tabs > li')
for (var tabLi of tabs) {
if (tabLi != elTgt.parentNode)
tabLi.classList.remove("active")
else
tabLi.classList.add("active")
}
let divs = document.querySelectorAll("div#reldocs-boxes > div.tab-pane")
let theId = `rd-${ndTypeId}-${relDbType}`
// POSS: animate with transitions here
for (var tabDiv of divs) {
if (tabDiv.id != theId)
tabDiv.classList.remove("active", "in")
else
tabDiv.classList.add("active", "in")
}
}
// set up tabs for a given activetypes state and db.json entry
function resetTabs(activetypes, dbconf) {
let ul = document.getElementById('reldocs-tabs')
let divs = document.getElementById('reldocs-boxes')
// remove any previous tabs
ul.innerHTML = ""
divs.innerHTML = ""
TW.gui.reldocTabs = [{},{}]
// used with no args for full reset
if (!activetypes || !dbconf) {
return
}
console.log("dbconf for this source", dbconf)
// for all active nodetypes
for (let nodetypeId in activetypes) {
if (activetypes[nodetypeId]) {
let additionalConf = dbconf[nodetypeId]
if (TW.conf.debug.logSettings)
console.log ("additionalConf for this source", additionalConf)
let possibleAPIs = []
if (additionalConf.reldbs) {
possibleAPIs = additionalConf.reldbs
// 3 vars to know which one to activate
let nAPIs = Object.keys(possibleAPIs).length
let iAPI = 0
let didActiveFlag = false
for (var possibleAPI in possibleAPIs){
// the tab's id
let tabref = `rd-${nodetypeId}-${possibleAPI}`
// create valid tabs
let newLi = document.createElement('li')
newLi.setAttribute("role", "presentation")
let newRDTab = document.createElement('a')
newRDTab.text = `${possibleAPI} (${nodetypeId==0?'sem':'soc'})`
newRDTab.setAttribute("role", "tab")
newRDTab.dataset.reldocstype = possibleAPI
newRDTab.dataset.nodetype = nodetypeId
newRDTab.setAttribute("class", `for-nodecategory-${nodetypeId}`)
// newRDTab.dataset.toggle = 'tab' // only needed if using bootstrap
// keep access
TW.gui.reldocTabs[nodetypeId][possibleAPI] = newRDTab
// create corresponding content box
let newContentDiv = document.createElement('div')
newContentDiv.setAttribute("role", "tabpanel")
newContentDiv.setAttribute("class", "topPapers tab-pane")
newContentDiv.id = tabref
// add to DOM
ul.append(newLi)
newLi.append(newRDTab)
divs.append(newContentDiv)
// select currently preferred reldoc tabs
// (we activate if favorite or if no matching favorite and last)
if (possibleAPI == TW.conf.relatedDocsType
|| (!didActiveFlag && iAPI == nAPIs - 1)) {
newLi.classList.add("active")
newContentDiv.classList.add("active", "in")
didActiveFlag = true
}
// add handler to switch relatedDocsType
newRDTab.addEventListener('click', function(e){
// tab mecanism
activateRDTab(e.target)
// no need to run associated query:
// (updateRelatedNodesPanel did it at selection time)
})
iAPI++
}
}
}
}
}
function jsActionOnGexfSelector(graphBasename){
let graphPath = TW.gmenuPaths[graphBasename] || graphBasename+".gexf"
......
......@@ -298,7 +298,6 @@ saferString = function(string) {
}
/**
* function to test if file exists
* via XHR, enhanced from http://stackoverflow.com/questions/5115141
......
......@@ -78,8 +78,9 @@ if (window.location.protocol == 'file:' || sourcemode == 'localfile') {
// traditional cases: remote read from API or prepared server-side file
else {
try {
// we'll first retrieve the menu of available sources in db.json, then get the real data in a second ajax via API or server file
[TW.gmenuPaths, TW.gmenuInfos] = readMenu(TW.conf.paths.sourceMenu)
// we'll first retrieve the menu of available sources in db.json,
// then get the real data in a second ajax via API or server file
[TW.gmenuPaths, TW.gmenuInfos, TW.File] = readMenu(TW.conf.paths.sourceMenu)
// NB: this menu used to be a file list for only one sourcemode
// but now also contains settings for nodetypes and for
......@@ -238,11 +239,12 @@ function syncRemoteGraphData () {
var files_selector = '<select onchange="jsActionOnGexfSelector(this.value);">'
for (let shortname in TW.gmenuPaths) {
let fullPath = TW.gmenuPaths[shortname]
let cssFileSelected = (TW.File==fullPath)?"selected":""
files_selector += '<option '+cssFileSelected+'>'+shortname+'</option>'
files_selector += '<option>'+shortname+'</option>'
}
files_selector += "</select>"
$("#network").html(files_selector)
// in this case we keep the TW.File that was already set from readMenu
}
// direct urlparam file case
......@@ -262,6 +264,7 @@ function syncRemoteGraphData () {
inData = finalRes["data"]
inFormat = finalRes["format"]
inConfKey = TW.File
mapLabel = TW.File
if (TW.conf.debug.logFetchers) {
console.warn('@TW.File', finalRes["OK"], TW.File)
......
......@@ -60,6 +60,16 @@ TW.pushGUIState = function( args ) {
}
}
// recreate tabs after type changes
// db.json conf entry (£TODO unify s/(?:TW.File|inConfKey)/TW.sourceId/g)
let inConfKey = (sourcemode != "api") ? TW.File : 'graphapi/default'
if (TW.conf.getRelatedDocs
&& !isUndef(args.activetypes)
&& TW.gmenuInfos[inConfKey]) {
resetTabs(newState.activetypes, TW.gmenuInfos[inConfKey])
}
// 4) store it in TW.states
TW.states.push(newState)
......@@ -78,6 +88,9 @@ TW.resetGraph = function() {
// remove the selection
cancelSelection(false, {norender: true})
// and set tabs to none
resetTabs()
// call the sigma graph clearing
TW.instance.clearSigma()
......@@ -104,6 +117,7 @@ TW.resetGraph = function() {
function readMenu(infofile) {
// exemple entry
// --------------
// "data/gargistex": {
// "first" : "shale_and_ice.gexf",
// "graphs":{
......@@ -123,8 +137,6 @@ function readMenu(infofile) {
// }
// }
let paths = {}
let details = {}
if (TW.conf.debug.logFetchers) console.info(`attempting to load filemenu ${infofile}`)
var preRES = AjaxSync({ url: infofile, datatype:"json" });
......@@ -133,25 +145,19 @@ function readMenu(infofile) {
if (TW.conf.debug.logFetchers) console.log('initial AjaxSync result preRES', preRES)
}
var first_file = "" , first_path = ""
// 1 - store the first one (b/c we'll loose order)
var first_file = "", first_dir = "" , first_path = ""
for( var path in preRES.data ) {
if (TW.conf.debug.logFetchers) console.log("db.json path", path)
first_file = preRES.data[path]["first"]
first_path = path
break;
if (TW.conf.debug.logFetchers) console.log("db.json path", path)
first_file = preRES.data[path]["first"] || Object.keys(preRES.data[path]["graphs"])[0]
first_dir = path
break;
}
first_path = first_dir+"/"+first_file
// the first or a specified one (ie both mode and file params are present)
if( isUndef(getUrlParam.file) ) {
TW.File = first_path+"/"+first_file
mapLabel = first_file
} else {
// £POSS; match on the full paths from db.json
TW.File = first_path+"/"+getUrlParam.file
mapLabel = getUrlParam.file
}
// 2 - process all the paths and associated confs
let paths = {}
let details = {}
for( var path in preRES.data ) {
var theGraphs = preRES.data[path]["graphs"]
......@@ -162,35 +168,27 @@ function readMenu(infofile) {
// ex : "RiskV2PageRank1000.gexf":data/AXA/RiskV2PageRank1000.gexf
// (we assume there's no duplicate basenames)
if (TW.conf.debug.logSettings)
console.log("db conf entry: "+graphBasename)
// for associated LocalDB php queries: CSV (or CortextDBs sql)
if (theGraphs[aGraph]) {
let gSrcEntry = theGraphs[aGraph]
details[path+"/"+aGraph] = new Array(2)
if (gSrcEntry.node0) {
details[path+"/"+aGraph][0] = gSrcEntry.node0
}
if (gSrcEntry.node1) {
details[path+"/"+aGraph][1] = gSrcEntry.node1
}
}
else {
details[path+"/"+aGraph] = null
}
}
// console.log( files_selector )
}
return [paths, details]
return [paths, details, first_path]
}
......@@ -520,20 +518,28 @@ function updateRelatedNodesPanel( sels , same, oppos ) {
$("#information").html(informationDIV);
if (TW.conf.getRelatedDocs) {
$("#reldocs-tabs-wrapper").show();
let rdTabCount = 0
// update all related docs tabs
for (let ntId in TW.SystemState().activetypes) {
if (TW.SystemState().activetypes[ntId]) {
let qWords = queryForType(ntId)
// console.log("available topPapers tabs:", TW.gui.reldocTabs[ntId])
for (let relDbType in TW.gui.reldocTabs[ntId]) {
let tabId = `rd-${ntId}-${relDbType}`
rdTabCount ++
console.log("available topPapers tabs:", TW.gui.reldocTabs[ntId])
// if not already done
if (! TW.lastRelDocQueries[tabId]
|| TW.lastRelDocQueries[tabId] != qWords) {
getTopPapers(qWords, ntId, relDbType, tabId)
for (let relDbType in TW.gui.reldocTabs[ntId]) {
getTopPapers(qWords, ntId, relDbType, `rd-${ntId}-${relDbType}`)
// memoize
TW.lastRelDocQueries[tabId] = qWords
}
}
}
}
if (rdTabCount > 0) $("#reldocs-tabs-wrapper").show();
}
else {
$("#reldocs-tabs-wrapper").hide();
......
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