Commit 4f2ad358 authored by Romain Loth's avatar Romain Loth

Merge branch 'dev' (bipartite colors)

parents 3bdfe991 4c1a7550
// KEPT FOR REFERENCE, BINNING NOW PRECOMPUTED in parseCustom
// rewrite of gradientColors with binning and for attributes that can have negative float values
// /!\ age and growth_rate attributes referred to by name
function colorsRelByBins_old(daclass) {
cancelSelection(false);
var binColors
var doModifyLabel = false
TW.handpickedcolor = true
// for debug of heatmapColoring
var totalsPerBinMin = {}
// should be = binColors.length
var nTicksParam = (daclass == 'age') ? 8 : 12
// do first loop entirely to get percentiles => bins, then modify alt_color
// estimating ticks
let tickThresholds = []
let valArray = []
for (var j=0 ; j < TW.nNodes ; j++) {
let n = TW.partialGraph.graph.nodes(TW.nodeIds[j])
if (
!n.hidden
&& n.attributes
&& n.attributes.category == 'terms'
&& n.attributes[daclass] != undefined
) {
valArray.push(Number(n.attributes[daclass]))
}
}
var len = valArray.length
valArray.sort(function(a, b) {return a - b;}) // important :)
for (var l=0 ; l < nTicksParam ; l++) {
let nthVal = Math.floor(len * l / nTicksParam)
tickThresholds.push(valArray[nthVal])
}
// also always add the max+1 as last tick (excluded upper bound of last bin)
tickThresholds.push((valArray[len-1])+1)
console.info(`[|===|=== ${nTicksParam} color ticks ===|===|]\n`, tickThresholds)
cancelSelection(false);
if (daclass == 'age') {
// 9 colors
binColors = TW.gui.getHeatmapColors(9)
}
else if (daclass == 'growth_rate') {
doModifyLabel = true
// 12 colors
binColors = TW.gui.getHeatmapColors(12)
}
// verification
if (nTicksParam != binColors.length) {
console.warn (`heatmapColoring setup mismatch: nTicksParam ${nTicksParam} should == nColors ${binColors.length}`)
}
// get the nodes
for (var j=0 ; j < TW.nNodes ; j++) {
let n = TW.partialGraph.graph.nodes(TW.nodeIds[j])
if (! n.hidden
&& n.attributes
&& n.attributes.category == 'terms'
&& ! isUndef(n.attributes[daclass])
) {
var valSt = n.attributes[daclass]
var originalLabel = TW.Nodes[n.id].label
if (doModifyLabel) {
n.label = `(${valSt}) ${originalLabel}`
}
else {
n.label = originalLabel
}
var theVal = parseFloat(valSt)
var foundBin = false
// console.log('theVal:',theVal)
if( !isNaN(theVal) ) { //is float
// iterate over bins
for(var k=0 ; k < tickThresholds.length-1; k++) {
var binMin = tickThresholds[k]
var binMax = tickThresholds[(k+1)]
if((theVal >= binMin) && (theVal < binMax)) {
// TW.partialGraph._core.graph.nodesIndex[n.id].binMin = binMin
// TW.partialGraph._core.graph.nodesIndex[n.id].color = binColors[j]
n.binMin = binMin
n.color = binColors[k]
n.customAttrs.alt_color = binColors[k]
n.customAttrs.altgrey_color = false
foundBin = true
// console.log(`theVal ${theVal} => found its bin ${binMin} ... ${binColors[k]}`)
if (!totalsPerBinMin[binMin]) {
totalsPerBinMin[binMin] = 1
}
else {
totalsPerBinMin[binMin]++
}
break
}
}
// case no bin after loop (perhaps more ticks than colors-1 ??)
if (!foundBin) {
console.warn('no bin for theVal', theVal, n.id)
n.binMin = null
n.color = '#000'
n.customAttrs.alt_color = '#000'
}
}
else {
// case no val
// console.log('no val for', n.id)
n.binMin = null
n.color = '#555'
n.customAttrs.alt_color = '#555'
}
}
}
// console.debug(valArray)
console.info('coloring distribution per tick thresholds' , totalsPerBinMin)
// Edge precompute alt_rgb by new source-target nodes-colours combination
repaintEdges()
// set_ClustersLegend ( daclass )
TW.partialGraph.render();
}
......@@ -159,8 +159,8 @@
<li>
<a>
<select id="aselector" onchange="console.log('salut monde')" class="selectpicker" data-style="btn btn-success btn-sm" data-width="auto">
<option value="Document" selected>Scholars</option>
<option value="NGram">Keywords</option>
<option value="Scholars" selected>Scholars</option>
<option value="Keywords">Keywords</option>
</select>
</a>
</li>
......@@ -430,9 +430,9 @@
</div><!-- /row -->
</div>
<!-- class="my-legend" (absolute position bottom left) -->
<div id="legend-for-clusters" class="over-panels"></div>
<!-- attributes' legends (absolute position bottom left)
(contains one div of class "my-legend" per colored nodetype) -->
<div id="legend-for-facets" class="over-panels"></div>
<!-- to reopen the panel -->
<div id="sideunfold">
......
......@@ -21,7 +21,7 @@ TW.conf = (function(TW){
// ...or remote bridge to default source api ajax queries
TWConf.sourceAPI={}
TWConf.sourceAPI["nodetypes"] = {"node0": "NGram", "node1": "Document" }
TWConf.sourceAPI["nodetypes"] = {"node0": "Keywords", "node1": "Scholars" }
TWConf.sourceAPI["forNormalQuery"] = "services/api/graph"
TWConf.sourceAPI["forFilteredQuery"] = "services/api/graph"
......@@ -114,8 +114,8 @@ TW.conf = (function(TW){
// =============
// Node typology: categories (resp. 0 and 1) will get these default labels
TWConf.catSem = "NGram";
TWConf.catSoc = "Document";
TWConf.catSem = "Keywords";
TWConf.catSoc = "Scholars";
// NB: these labels may be superseded by:
// - the input data's node types values cf. sortNodeTypes()
// - in servermenu mode, by the node0 & node1 properties
......
......@@ -49,8 +49,8 @@
}
/* legend re-positioned */
.my-legend {
bottom: calc(40% - 105px);
#legend-for-facets {
bottom: calc(40% - 100px);
/* bot just above #sidebar which has top at calc(105px + 60%); */
}
......@@ -97,17 +97,20 @@
}
/* legend reduction */
#legend-for-facets {
max-width: 25%;
}
.my-legend {
max-width: 27%;
max-height: 25%;
padding: 0 2px;
font-size: 85%;
margin: 0 0 5px 0;
margin: 0;
padding: 0 1px 3px 1px;
}
.my-legend .legend-title {
margin-bottom: 0;
font-size: 90%;
font-size: 85%;
}
.my-legend .legend-scale ul {
margin-bottom: 0;
......
......@@ -33,44 +33,55 @@
}
/* LEGEND PANEL */
#legend-for-facets {
bottom: 10px;
left:0;
position:fixed;
/* width: we set it a bit more than #lefttopbox width */
max-width: 20%;
max-height: 40%;
font-size:120%;
cursor: default;
overflow-y:auto;
overflow-x: hidden;
}
.my-legend {
position:fixed;
/* width: we set it equal or smaller than #lefttopbox width */
max-width:20%;
max-height: 30%;
padding: 0 5px;
overflow-y:scroll;
bottom:18px;
left:0;
border:solid 1px black;
background-color:white;
opacity: 0.8;
position: relative;
height: 100%;
width: 100%;
margin: 0;
padding: 0 5px 5px 5px;
opacity:1;
background-color: white;
color:#250587;
margin: 7px;
font-size:120%;
cursor: default;
overflow: hidden;
z-index: 5;
}
.my-legend .legend-title {
text-align: left;
margin-bottom: 5px;
font-weight: bold;
font-size: 90%;
}
font-size: 75%;
}
.my-legend .legend-scale ul {
margin: 0;
margin-bottom: 5px;
padding: 0;
float: left;
list-style: none;
}
}
.my-legend .legend-scale ul li {
font-size: 80%;
list-style: none;
margin-left: 0;
line-height: 16px;
margin-bottom: 2px;
}
}
.my-legend ul.legend-labels li span.lgdcol {
display: block;
float: left;
......
......@@ -165,6 +165,13 @@ html.waiting {
display: none; /* initial display off but turned on if bipartite */
}
.pseudo-optgroup {
color: #555;
text-align: center;
font-size: 90%;
padding: 1.5em 0 .5em 0;
}
.tagcloud-item{
display:inline-block;
border:solid 1px;
......
......@@ -1161,7 +1161,7 @@ var TinaWebJS = function ( sigmacanvas ) {
}
// otherwise, set the default legend
if (! madeDefaultColor) {
set_ClustersLegend ( "clust_default" )
updateColorsLegend ( "clust_default" )
}
// select currently active sliders
......
......@@ -23,11 +23,20 @@ TW.gui.checkBox=false;
TW.gui.shiftKey=false;
TW.gui.foldedSide=false;
TW.gui.manuallyChecked = false;
TW.gui.handpickedcolor = false; // <= changes edge rendering strategy
TW.gui.lastFilters = {}
TW.gui.lastFilters = {} // <= last values, by slider id
TW.gui.reldocTabs = [{}, {}] // <= by nodetype and then dbtype
TW.gui.sizeRatios = [1,1] // sizeRatios per nodetype
TW.gui.handpickedcolors = {}; // <= changes rendering, by nodetype
TW.gui.handpickedcolorsReset = function (forTypes = TW.categories) {
TW.gui.handpickedcolors = {}
for (var k in forTypes) {
TW.gui.handpickedcolors[forTypes[k]] = {
'alton': false,
'altattr': null
}
}
}
TW.gui.noverlapConf = {
nodeMargin: .4,
......@@ -517,15 +526,37 @@ function changeType(optionaltypeFlag) {
updateSearchLabels(nid,allNodes[nid].label,allNodes[nid].type);
}
// update the gui (POSS could be handled by TW.pushGUIState)
TW.gui.handpickedcolor = false
// update the gui (TODO handle by TW.pushGUIState) =========================
updateDynamicFacets()
// console.log("outgoing.activetypes", outgoing.activetypes)
// console.log("newActivetypes", newActivetypes)
changeGraphAppearanceByFacets( getActivetypesNames() )
if (typeFlag != 'all') {
graphResetLabelsAndSizes()
// turn off the altcolors for outgoing types
for (var tyId in TW.categories) {
let ty = TW.categories[tyId]
if (outgoing.activetypes[tyId] && ! newActivetypes[tyId]) {
if (TW.gui.handpickedcolors[ty].alton) {
clearColorLegend([ty])
TW.gui.handpickedcolors[ty].alton = false
}
}
else if (!outgoing.activetypes[tyId] && newActivetypes[tyId]) {
if (TW.gui.handpickedcolors[ty].altattr) {
TW.gui.handpickedcolors[ty].alton = true
// this re-coloring can be avoided if "hidden" was used in changeLevel and sliders
let recolorMethod = getColorFunction(TW.gui.handpickedcolors[ty].altattr)
window[recolorMethod](TW.gui.handpickedcolors[ty].altattr)
// without re-coloring step, we would only need to recreate legend box
// updateColorsLegend(TW.gui.handpickedcolors[ty].altattr, [ty])
}
}
}
TW.partialGraph.settings('labelThreshold', getSizeFactor())
fillAttrsInForm('choose-attr')
fillAttrsInForm('attr-titling-metric', 'num')
......@@ -537,6 +568,8 @@ function changeType(optionaltypeFlag) {
sigma_utils.smartForceAtlas()
}
})
// end update the gui ======================================================
}
......@@ -623,6 +656,13 @@ function changeLevel(optionalTgtState) {
activereltypes = present.activereltypes;
}
let activetypesDict = {}
for (var i in activetypes) {
if (activetypes[i]) {
activetypesDict[TW.categories[i]] = true
}
}
TW.partialGraph.graph.clear();
var voisinage = {}
......@@ -683,9 +723,10 @@ function changeLevel(optionalTgtState) {
// var t0 = performance.now()
for(var nid in TW.Nodes) {
if(activetypes[TW.catDict[TW.Nodes[nid].type]])
// we add 1 by 1
add1Elem(nid)
if (activetypesDict[TW.Nodes[nid].type]) {
// we add 1 by 1 (POSS: use hidden instead)
add1Elem(nid)
}
}
for(var eid in TW.Edges) {
for (var k in activereltypes) {
......@@ -715,6 +756,16 @@ function changeLevel(optionalTgtState) {
updateDynamicFacets()
changeGraphAppearanceByFacets( getActivetypesNames() )
// going back to global: recolor nodes that were out of scope
if(futurelevel) {
for (var ty in activetypesDict) {
if (TW.gui.handpickedcolors[ty].alton) {
let recolorMethod = getColorFunction(TW.gui.handpickedcolors[ty].altattr)
window[recolorMethod](TW.gui.handpickedcolors[ty].altattr)
}
}
}
// recreate FA2 nodes array after you change the nodes
reInitFa2({
useSoftMethod: false,
......
This diff is collapsed.
......@@ -96,8 +96,10 @@ function isUndef(variable){
function stringToSomeInt (anyString) {
let charCodeSum = 0
for (let i = 0 ; i < anyString.length ; i++) {
charCodeSum += anyString.charCodeAt(i)
if (anyString && anyString.length) {
for (let i = 0 ; i < anyString.length ; i++) {
charCodeSum += anyString.charCodeAt(i)
}
}
return charCodeSum
}
......
......@@ -413,6 +413,9 @@ function mainStartGraph(inFormat, inData, twInstance) {
TW.ByType = dicts.byType // useful for loops
// init the rendering flags (after we got types, before we first render)
TW.gui.handpickedcolorsReset(TW.categories)
// in-place: pre-compute all color/unselected color/size properties
prepareNodesRenderingProperties(TW.Nodes)
prepareEdgesRenderingProperties(TW.Edges, TW.Nodes)
......
......@@ -97,7 +97,6 @@ TW.resetGraph = function() {
// reset rendering gui flags
TW.gui.selectionActive = false
TW.gui.handpickedcolor = false
// reset circle size and cursor
TW.gui.circleSize = 0
......@@ -107,6 +106,9 @@ TW.resetGraph = function() {
TW.gui.checkBox=false
TW.gui.lastFilters = {}
// reset colors legends
updateColorsLegend()
// forget the states
TW.states = [TW.initialSystemState]
......@@ -734,7 +736,7 @@ function prepareNodesRenderingProperties(nodesDict) {
// default unselected color
defgrey_color : "rgba("+rgbStr+","+TW.conf.sigmaJsDrawingProperties.twNodesGreyOpacity+")",
// will be used for repainting (read when TW.gui.handpickedcolor flag)
// will be used for repainting (read when TW.gui.handpickedcolors flags)
alt_color: null,
altgrey_color: null,
}
......
......@@ -211,8 +211,8 @@ function scanGexf(gexfContent) {
// ex: terms
// ex: ISItermsriskV2_140 & ISItermsriskV2_140
// optional arg optionalNodeConf should contain keys of the form:
// "node0": "NGram",
// "node1": "Document"
// "node0": "Keywords",
// "node1": "Scholars"
// etc.
// (it's read from project_conf.json)
function sortNodeTypes(observedTypesDict, optionalNodeConf) {
......@@ -220,6 +220,14 @@ function sortNodeTypes(observedTypesDict, optionalNodeConf) {
observedTypes.sort(function(a,b) {return observedTypesDict[b] - observedTypesDict[a]})
let nbNodeTypes = 2
if (observedTypes.length > nbNodeTypes) {
console.warn(`The graph source data has more different node types than
supported. Less frequent node types will be ignored.
Max allowed types: ${nbNodeTypes},
Found: ${observedTypes.length} (namely: ${observedTypes})`)
}
var declaredTypes = []
for (var i = 0 ; i < nbNodeTypes ; i++ ) {
if (optionalNodeConf && optionalNodeConf["node"+i]) {
......@@ -234,10 +242,14 @@ function sortNodeTypes(observedTypesDict, optionalNodeConf) {
}
}
var newcats = []
var catDict = {}
// console.log("observedTypes", observedTypes)
// console.log("declaredTypes", declaredTypes)
var newcats = [] // will become TW.categories
var catDict = {} // will become TW.catDict
var nTypes = observedTypes.length
if(nTypes==0) {
newcats[0]="Terms";
catDict["Terms"] = 0;
......@@ -251,43 +263,72 @@ function sortNodeTypes(observedTypesDict, optionalNodeConf) {
console.log(`cat unique (${observedTypes[0]}) =>0`)
}
if(nTypes>1) {
// allows multiple node types, with an "all the rest" node1
// try stipulated cats, then fallbacks
// possible: loop
if (observedTypesDict[declaredTypes[0]]) {
newcats[0] = declaredTypes[0];
catDict[declaredTypes[0]] = 0;
}
if (observedTypesDict[declaredTypes[1]]) {
newcats[1] = declaredTypes[1];
catDict[declaredTypes[1]] = 1;
// allows multiple node types even if not well declared
// ----------------------------------------------------
// POSSIBLE: an "all the rest" last nodeType ?
let alreadyUsed = {}
// try declared cats in declared position, independantly from each other
for (var i = 0 ; i < nbNodeTypes; i++) {
if (observedTypesDict[declaredTypes[i]]) {
let validatedType = declaredTypes[i]
newcats[i] = validatedType;
alreadyUsed[validatedType] = true
}
}
// NB: type for nodes0 will be the majoritary by default, unless taken
if (!newcats[0]) {
if (observedTypes[0] != newcats[1]) {
newcats[0] = observedTypes[0] // 0 is the most frequent here
catDict[observedTypes[0]] = 0;
}
else {
newcats[0] = observedTypes[1] // 1 is second most frequent
catDict[observedTypes[1]] = 0;
// console.log("found stipulated cats", newcats, catDict)
// fallbacks: if some or all stipulated cats are not found
// ---------
// heuristic A: fill missing ones, by frequence
// (eg if nodes0 was not found, then type for nodes0 will be the
// majoritary observed one, unless taken where we move one up)
for (var i = 0 ; i < nbNodeTypes; i++) {
if (typeof newcats[i] == "undefined") {
for (var j = 0 ; j < nTypes ; j++) {
if (!alreadyUsed[observedTypes[j]]) {
newcats[i] = observedTypes[j]
alreadyUsed[observedTypes[j]] = true
break
}
}
}
}
// all the rest
for(var i in observedTypes) {
let c = observedTypes[i]
// or c is in "all the rest" group
// (POSS extend to multitypes)
if (c != newcats[0] && c != newcats[1]) {
if (!newcats[1]) newcats[1] = c;
else newcats[1] += '/'+c
catDict[c] = 1;
// console.log("after filling majority cats", newcats, catDict)
// all the rest (heuristic B)
if (!newcats[nbNodeTypes-1]) {
for(var i in observedTypes) {
// without a group others: if there is more than two cats altogether,
// only the last cat counts as node1 cat
let c = observedTypes[i]
// -------------------------------------------- for a group "others"
// with a group "others": if there is more than two cats altogether,
// all the non majoritary or non-stipulated
// are grouped here as node1 cat
// but problem: it break the symetry b/w TW.categories and TW.catDict
//
// // c is in "all the rest" group (POSS extend to multitypes)
// if (c != newcats[0] && c != newcats[1]) {
// if (!newcats[1]) newcats[1] = c;
// else newcats[1] += '/'+c
// catDict[c] = 1;
// }
// -------------------------------------------/ for a group "others"
}
}
}
// reverse lookup
for (var i in newcats) {
catDict[newcats[i]] = i
}
return {'categories': newcats, 'lookup_dict': catDict}
}
......@@ -650,10 +691,18 @@ function dictfyGexf( gexf , categories ){
for(var i in categories) {
nodesByType[i] = []
let subCats = categories[i].split(/\//g)
for (var j in subCats) {
catDict[subCats[j]] = i
}
// without a group "others" -------------------
catDict[categories[i]] = i
// POSS subCats for cat "others" if open types mapped to n types
//
// ----------------------- with a group "others"
// let subCats = categories[i].split(/\//g)
// for (var j in subCats) {
// catDict[subCats[j]] = i
// }
// ---------------------- /with a group "others"
}
......@@ -1077,11 +1126,17 @@ function dictfyJSON( data , categories ) {
for(var i in categories) {
nodesByType[i] = []
let subCats = categories[i].split(/\//g)
for (var j in subCats) {
catDict[subCats[j]] = i
}
// without a group "others" -------------------
catDict[categories[i]] = i
// POSS subCats for cat "others" if open types mapped to n types
//
// ----------------------- with a group "others"
// let subCats = categories[i].split(/\//g)
// for (var j in subCats) {
// catDict[subCats[j]] = i
// }
// ---------------------- /with a group "others"
}
// normalization, same as parseGexf
......
This diff is collapsed.
......@@ -21,7 +21,7 @@ TW.conf = (function(TW){
// ...or remote bridge to default source api ajax queries
TWConf.sourceAPI={}
TWConf.sourceAPI["nodetypes"] = {"node0": "NGram", "node1": "Document" }
TWConf.sourceAPI["nodetypes"] = {"node0": "Keywords", "node1": "Scholars" }
TWConf.sourceAPI["forNormalQuery"] = "services/api/graph"
TWConf.sourceAPI["forFilteredQuery"] = "services/api/graph"
......@@ -251,7 +251,7 @@ TW.conf = (function(TW){
// "default" for white background
// not selected <=> (1-greyness)
twNodesGreyOpacity: .4, // smaller value: more grey
twNodesGreyOpacity: .5, // smaller value: more grey
twBorderGreyColor: "rgba(100, 100, 100, 0.5)",
twEdgeGreyColor: "rgba(100, 100, 100, 0.25)",
};
......
......@@ -21,7 +21,7 @@ TW.conf = (function(TW){
// ...or remote bridge to default source api ajax queries
TWConf.sourceAPI={}
TWConf.sourceAPI["nodetypes"] = {"node0": "NGram", "node1": "Document" }
TWConf.sourceAPI["nodetypes"] = {"node0": "Keywords", "node1": "Scholars" }
TWConf.sourceAPI["forNormalQuery"] = "services/api/graph"
TWConf.sourceAPI["forFilteredQuery"] = "services/api/graph"
......
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