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 @@ ...@@ -159,8 +159,8 @@
<li> <li>
<a> <a>
<select id="aselector" onchange="console.log('salut monde')" class="selectpicker" data-style="btn btn-success btn-sm" data-width="auto"> <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="Scholars" selected>Scholars</option>
<option value="NGram">Keywords</option> <option value="Keywords">Keywords</option>
</select> </select>
</a> </a>
</li> </li>
...@@ -430,9 +430,9 @@ ...@@ -430,9 +430,9 @@
</div><!-- /row --> </div><!-- /row -->
</div> </div>
<!-- attributes' legends (absolute position bottom left)
<!-- class="my-legend" (absolute position bottom left) --> (contains one div of class "my-legend" per colored nodetype) -->
<div id="legend-for-clusters" class="over-panels"></div> <div id="legend-for-facets" class="over-panels"></div>
<!-- to reopen the panel --> <!-- to reopen the panel -->
<div id="sideunfold"> <div id="sideunfold">
......
...@@ -21,7 +21,7 @@ TW.conf = (function(TW){ ...@@ -21,7 +21,7 @@ TW.conf = (function(TW){
// ...or remote bridge to default source api ajax queries // ...or remote bridge to default source api ajax queries
TWConf.sourceAPI={} TWConf.sourceAPI={}
TWConf.sourceAPI["nodetypes"] = {"node0": "NGram", "node1": "Document" } TWConf.sourceAPI["nodetypes"] = {"node0": "Keywords", "node1": "Scholars" }
TWConf.sourceAPI["forNormalQuery"] = "services/api/graph" TWConf.sourceAPI["forNormalQuery"] = "services/api/graph"
TWConf.sourceAPI["forFilteredQuery"] = "services/api/graph" TWConf.sourceAPI["forFilteredQuery"] = "services/api/graph"
...@@ -114,8 +114,8 @@ TW.conf = (function(TW){ ...@@ -114,8 +114,8 @@ TW.conf = (function(TW){
// ============= // =============
// Node typology: categories (resp. 0 and 1) will get these default labels // Node typology: categories (resp. 0 and 1) will get these default labels
TWConf.catSem = "NGram"; TWConf.catSem = "Keywords";
TWConf.catSoc = "Document"; TWConf.catSoc = "Scholars";
// NB: these labels may be superseded by: // NB: these labels may be superseded by:
// - the input data's node types values cf. sortNodeTypes() // - the input data's node types values cf. sortNodeTypes()
// - in servermenu mode, by the node0 & node1 properties // - in servermenu mode, by the node0 & node1 properties
......
...@@ -49,8 +49,8 @@ ...@@ -49,8 +49,8 @@
} }
/* legend re-positioned */ /* legend re-positioned */
.my-legend { #legend-for-facets {
bottom: calc(40% - 105px); bottom: calc(40% - 100px);
/* bot just above #sidebar which has top at calc(105px + 60%); */ /* bot just above #sidebar which has top at calc(105px + 60%); */
} }
...@@ -97,17 +97,20 @@ ...@@ -97,17 +97,20 @@
} }
/* legend reduction */ /* legend reduction */
#legend-for-facets {
max-width: 25%;
}
.my-legend { .my-legend {
max-width: 27%;
max-height: 25%; max-height: 25%;
padding: 0 2px;
font-size: 85%; font-size: 85%;
margin: 0 0 5px 0; margin: 0;
padding: 0 1px 3px 1px;
} }
.my-legend .legend-title { .my-legend .legend-title {
margin-bottom: 0; margin-bottom: 0;
font-size: 90%; font-size: 85%;
} }
.my-legend .legend-scale ul { .my-legend .legend-scale ul {
margin-bottom: 0; margin-bottom: 0;
......
...@@ -33,44 +33,55 @@ ...@@ -33,44 +33,55 @@
} }
/* LEGEND PANEL */ /* LEGEND PANEL */
.my-legend { #legend-for-facets {
position:fixed; bottom: 10px;
/* 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; left:0;
border:solid 1px black; position:fixed;
background-color:white; /* width: we set it a bit more than #lefttopbox width */
opacity: 0.8; max-width: 20%;
color:#250587; max-height: 40%;
margin: 7px;
font-size:120%; font-size:120%;
cursor: default; cursor: default;
overflow-y:auto;
overflow-x: hidden;
}
.my-legend {
position: relative;
height: 100%;
width: 100%;
margin: 0;
padding: 0 5px 5px 5px;
opacity:1;
background-color: white;
color:#250587;
overflow: hidden;
z-index: 5;
} }
.my-legend .legend-title { .my-legend .legend-title {
text-align: left; text-align: left;
margin-bottom: 5px; margin-bottom: 5px;
font-weight: bold; font-weight: bold;
font-size: 90%; font-size: 75%;
} }
.my-legend .legend-scale ul { .my-legend .legend-scale ul {
margin: 0; margin: 0;
margin-bottom: 5px; margin-bottom: 5px;
padding: 0; padding: 0;
float: left; float: left;
list-style: none; list-style: none;
} }
.my-legend .legend-scale ul li { .my-legend .legend-scale ul li {
font-size: 80%; font-size: 80%;
list-style: none; list-style: none;
margin-left: 0; margin-left: 0;
line-height: 16px; line-height: 16px;
margin-bottom: 2px; margin-bottom: 2px;
} }
.my-legend ul.legend-labels li span.lgdcol { .my-legend ul.legend-labels li span.lgdcol {
display: block; display: block;
float: left; float: left;
......
...@@ -165,6 +165,13 @@ html.waiting { ...@@ -165,6 +165,13 @@ html.waiting {
display: none; /* initial display off but turned on if bipartite */ 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{ .tagcloud-item{
display:inline-block; display:inline-block;
border:solid 1px; border:solid 1px;
......
...@@ -1161,7 +1161,7 @@ var TinaWebJS = function ( sigmacanvas ) { ...@@ -1161,7 +1161,7 @@ var TinaWebJS = function ( sigmacanvas ) {
} }
// otherwise, set the default legend // otherwise, set the default legend
if (! madeDefaultColor) { if (! madeDefaultColor) {
set_ClustersLegend ( "clust_default" ) updateColorsLegend ( "clust_default" )
} }
// select currently active sliders // select currently active sliders
......
...@@ -23,11 +23,20 @@ TW.gui.checkBox=false; ...@@ -23,11 +23,20 @@ TW.gui.checkBox=false;
TW.gui.shiftKey=false; TW.gui.shiftKey=false;
TW.gui.foldedSide=false; TW.gui.foldedSide=false;
TW.gui.manuallyChecked = false; TW.gui.manuallyChecked = false;
TW.gui.handpickedcolor = false; // <= changes edge rendering strategy TW.gui.lastFilters = {} // <= last values, by slider id
TW.gui.lastFilters = {}
TW.gui.reldocTabs = [{}, {}] // <= by nodetype and then dbtype TW.gui.reldocTabs = [{}, {}] // <= by nodetype and then dbtype
TW.gui.sizeRatios = [1,1] // sizeRatios per nodetype 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 = { TW.gui.noverlapConf = {
nodeMargin: .4, nodeMargin: .4,
...@@ -517,15 +526,37 @@ function changeType(optionaltypeFlag) { ...@@ -517,15 +526,37 @@ function changeType(optionaltypeFlag) {
updateSearchLabels(nid,allNodes[nid].label,allNodes[nid].type); updateSearchLabels(nid,allNodes[nid].label,allNodes[nid].type);
} }
// update the gui (TODO handle by TW.pushGUIState) =========================
// update the gui (POSS could be handled by TW.pushGUIState)
TW.gui.handpickedcolor = false
updateDynamicFacets() updateDynamicFacets()
// console.log("outgoing.activetypes", outgoing.activetypes)
// console.log("newActivetypes", newActivetypes)
changeGraphAppearanceByFacets( getActivetypesNames() ) changeGraphAppearanceByFacets( getActivetypesNames() )
if (typeFlag != 'all') { // turn off the altcolors for outgoing types
graphResetLabelsAndSizes() 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()) TW.partialGraph.settings('labelThreshold', getSizeFactor())
fillAttrsInForm('choose-attr') fillAttrsInForm('choose-attr')
fillAttrsInForm('attr-titling-metric', 'num') fillAttrsInForm('attr-titling-metric', 'num')
...@@ -537,6 +568,8 @@ function changeType(optionaltypeFlag) { ...@@ -537,6 +568,8 @@ function changeType(optionaltypeFlag) {
sigma_utils.smartForceAtlas() sigma_utils.smartForceAtlas()
} }
}) })
// end update the gui ======================================================
} }
...@@ -623,6 +656,13 @@ function changeLevel(optionalTgtState) { ...@@ -623,6 +656,13 @@ function changeLevel(optionalTgtState) {
activereltypes = present.activereltypes; activereltypes = present.activereltypes;
} }
let activetypesDict = {}
for (var i in activetypes) {
if (activetypes[i]) {
activetypesDict[TW.categories[i]] = true
}
}
TW.partialGraph.graph.clear(); TW.partialGraph.graph.clear();
var voisinage = {} var voisinage = {}
...@@ -683,10 +723,11 @@ function changeLevel(optionalTgtState) { ...@@ -683,10 +723,11 @@ function changeLevel(optionalTgtState) {
// var t0 = performance.now() // var t0 = performance.now()
for(var nid in TW.Nodes) { for(var nid in TW.Nodes) {
if(activetypes[TW.catDict[TW.Nodes[nid].type]]) if (activetypesDict[TW.Nodes[nid].type]) {
// we add 1 by 1 // we add 1 by 1 (POSS: use hidden instead)
add1Elem(nid) add1Elem(nid)
} }
}
for(var eid in TW.Edges) { for(var eid in TW.Edges) {
for (var k in activereltypes) { for (var k in activereltypes) {
let activereltype = activereltypes[k] let activereltype = activereltypes[k]
...@@ -715,6 +756,16 @@ function changeLevel(optionalTgtState) { ...@@ -715,6 +756,16 @@ function changeLevel(optionalTgtState) {
updateDynamicFacets() updateDynamicFacets()
changeGraphAppearanceByFacets( getActivetypesNames() ) 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 // recreate FA2 nodes array after you change the nodes
reInitFa2({ reInitFa2({
useSoftMethod: false, useSoftMethod: false,
......
...@@ -68,29 +68,29 @@ function changeGraphAppearanceByFacets(actypes) { ...@@ -68,29 +68,29 @@ function changeGraphAppearanceByFacets(actypes) {
if (!actypes) actypes = getActivetypesNames() if (!actypes) actypes = getActivetypesNames()
let currentNbNodes = TW.partialGraph.graph.nNodes() let colorsMeta = {}
let allNbNodes = TW.partialGraph.graph.nNodes()
// create colormenu and 1st default entry
var color_menu_info = '<li><a href="#" onclick="TW.gui.handpickedcolor = false ; graphResetLabelsAndSizes() ; TW.partialGraph.refresh()">By Default</a></li>';
let gotPreviousLouvain = false let gotPreviousLouvain = false
if( $( "#colorgraph-menu" ).length>0 ) {
for (var tid in actypes) {
let ty = actypes[tid]
let currentNbNodes = {}
for (var k in actypes) {
currentNbNodes[actypes[k]] = TW.partialGraph.graph.getNodesByType(TW.catDict[actypes[k]]).length
}
// 1st loop: census on [colors <=> types] and involved nb Nodes
for (var k in actypes) {
let ty = actypes[k]
let attNbNodes = currentNbNodes[ty]
// each facet family or clustering type was already prepared // each facet family or clustering type was already prepared
for (var attTitle in TW.Facets[ty]) { for (var attTitle in TW.Facets[ty]) {
if (! colorsMeta[attTitle]) {
// console.warn('changeGraphAppearanceByFacets:', ty, attTitle) colorsMeta[attTitle] = []
}
// note any previous louvains
if (attTitle == 'clust_louvain') gotPreviousLouvain = true
// attribute counts: nb of classes // attribute counts: nb of classes
// POSS here distinguish [ty][attTitle].classes.length and ranges.length // POSS here distinguish [ty][attTitle].classes.length and ranges.length
var attNbClasses = TW.Facets[ty][attTitle].invIdx.length let attNbClasses = TW.Facets[ty][attTitle].invIdx.length
var attNbNodes = currentNbNodes
if (attNbClasses) { if (attNbClasses) {
let lastClass = TW.Facets[ty][attTitle].invIdx[attNbClasses-1] let lastClass = TW.Facets[ty][attTitle].invIdx[attNbClasses-1]
...@@ -104,24 +104,57 @@ function changeGraphAppearanceByFacets(actypes) { ...@@ -104,24 +104,57 @@ function changeGraphAppearanceByFacets(actypes) {
} }
} }
// coloringFunction // note any previous louvains
var colMethod if (attTitle == 'clust_louvain') gotPreviousLouvain = true
// read from user settings // save relevant info
if (TW.facetOptions[attTitle] && TW.facetOptions[attTitle]['col']) { colorsMeta[attTitle].push({
colMethod = TW.gui.colorFuns[TW.facetOptions[attTitle]['col']] 'type': ty,
'nbDomain': attNbNodes,
'nbOutput': attNbClasses
})
} }
// fallback guess-values
if (! colMethod) {
if(attTitle.indexOf("clust")>-1||attTitle.indexOf("class")>-1) {
// for classes and clusters
colMethod = "clusterColoring"
} }
console.log(colorsMeta)
// sorted by type and how many types (one type x by alpha, one type y by alpha, two types xy by alpha)
let colorsList = Object.keys(colorsMeta)
colorsList.sort(function (a, b) {
let cmp = colorsMeta[a].length - colorsMeta[b].length
if (cmp == 0) {
cmp = (a in TW.sigmaAttributes) - (b in TW.sigmaAttributes)
if (cmp == 0) {
if (colorsMeta[a][0].type < colorsMeta[b][0].type) cmp = -1
else if (colorsMeta[a][0].type > colorsMeta[b][0].type) cmp = 1
else { else {
colMethod = "gradientColoring" if (a < b) cmp = -1
else if (a > b) cmp = 1
}
}
} }
return cmp
})
// 2nd loop: create colormenu and 1st default entry
var color_menu_info = '<li><a href="#" onclick="graphResetAllColors() ; TW.partialGraph.refresh()">By Default</a></li>';
let lastGroup = null
if( $( "#colorgraph-menu" ).length>0 ) {
for (var l in colorsList) {
var attTitle = colorsList[l]
// which concerned types and how many concerned nodes and output classes
let attNbNodes = 0
let forTypes = []
for (var i in colorsMeta[attTitle]) {
forTypes.push(colorsMeta[attTitle][i].type)
attNbNodes += colorsMeta[attTitle][i].nbDomain
} }
let attNbClasses = colorsMeta[attTitle][0].nbOutput
// coloring function
let colMethod = getColorFunction(attTitle)
// family label :) // family label :)
var attLabel ; var attLabel ;
...@@ -130,15 +163,30 @@ function changeGraphAppearanceByFacets(actypes) { ...@@ -130,15 +163,30 @@ function changeGraphAppearanceByFacets(actypes) {
} }
else attLabel = attTitle else attLabel = attTitle
color_menu_info += `<li><a href="#" onclick='${colMethod}("${attTitle}")'>By ${attLabel} (${attNbClasses} | ${attNbNodes})</a></li>` if (actypes.length == 1) {
color_menu_info += `
<li><a href="#" onclick='${colMethod}("${attTitle}")'>
By ${attLabel} (${attNbClasses} | ${attNbNodes})
</a></li>
`
}
else {
groupName = `${forTypes}`
if (groupName != lastGroup) {
color_menu_info += `<li class="pseudo-optgroup">${groupName}</li>`
}
color_menu_info += `
<li><a href="#" onclick='${colMethod}("${attTitle}",${JSON.stringify(forTypes)})'>
By ${attLabel} (${attNbClasses} | ${attNbNodes})
</a></li>
`
lastGroup = groupName
} }
// POSS add cumulated degree via TW.partialGraph.graph.degree(nid)
} }
// we also add clust_louvain if not already there // we also add clust_louvain if not already there
if (!gotPreviousLouvain) { if (!gotPreviousLouvain) {
color_menu_info += `<li><a href="#" onclick='clusterColoring("clust_louvain")'>By Louvain clustering ( <span id="louvainN">?</span> | ${currentNbNodes})</a></li>` color_menu_info += `<li><a href="#" onclick='clusterColoring("clust_louvain")'>By Louvain clustering ( <span id="louvainN">?</span> | ${allNbNodes})</a></li>`
} }
// for debug // for debug
...@@ -148,11 +196,33 @@ function changeGraphAppearanceByFacets(actypes) { ...@@ -148,11 +196,33 @@ function changeGraphAppearanceByFacets(actypes) {
} }
// Legend slots were prepared in TW.Facets // Legend slots were prepared in TW.Facets
} }
function getColorFunction(attTitle) {
// coloringFunction name as str
var colMethod
// read from user settings
if (TW.facetOptions[attTitle] && TW.facetOptions[attTitle]['col']) {
colMethod = TW.gui.colorFuns[TW.facetOptions[attTitle]['col']]
}
// fallback guess-values
if (! colMethod) {
if(attTitle.indexOf("clust")>-1||attTitle.indexOf("class")>-1) {
// for classes and clusters
colMethod = "clusterColoring"
}
else {
colMethod = "gradientColoring"
}
}
function RunLouvain() { return colMethod
}
// @cb: optional callback
function RunLouvain(cb) {
var node_realdata = [] var node_realdata = []
var nodesV = getVisibleNodes() var nodesV = getVisibleNodes()
...@@ -216,6 +286,11 @@ function RunLouvain() { ...@@ -216,6 +286,11 @@ function RunLouvain() {
TW.facetOptions['clust_louvain'] = {'col': 'cluster'} TW.facetOptions['clust_louvain'] = {'col': 'cluster'}
} }
// NB the LouvainFait flag is updated by caller fun // NB the LouvainFait flag is updated by caller fun
// callback
if (cb && typeof cb == 'function') {
cb()
}
} }
...@@ -269,25 +344,47 @@ function graphResetLabelsAndSizes(){ ...@@ -269,25 +344,47 @@ function graphResetLabelsAndSizes(){
n.size = TW.Nodes[n.id].size n.size = TW.Nodes[n.id].size
} }
} }
set_ClustersLegend()
} }
// @daclass: the name of a numeric/categorical attribute from node.attributes function graphResetAllColors() {
// @groupingTicks: an optional threshold's array expressing ranges with their low/up bounds label and ref to matchin nodeIds graphResetLabelsAndSizes()
function set_ClustersLegend ( daclass, groupedByTicks ) { TW.gui.handpickedcolorsReset()
$("#legend-for-clusters").removeClass( "my-legend" ) updateColorsLegend()
$("#legend-for-clusters").html("") }
if(daclass==null) return;
// removes selectively for an array of nodetypes
function clearColorLegend (forTypes) {
// console.log('clearColorLegend', forTypes)
for (var ty of forTypes) {
let legTy = document.getElementById("legend-for-"+ty)
if (legTy)
legTy.remove()
}
}
var actypes = getActivetypesNames()
// TODO test more for multiple types // @daclass: the name of a numeric/categorical attribute from node.attributes
// we have no specifications yet for colors (and legends) on multiple types // @forTypes: array of which typenames are concerned
if (actypes.length > 1) { // @groupingTicks: an optional threshold's array expressing ranges with their low/up bounds label and ref to matchin nodeIds
console.warn("colors by bins will only color nodes of type 0") function updateColorsLegend ( daclass, forTypes, groupedByTicks ) {
// shortcut to erase legends for all types
if(daclass == null) {
clearColorLegend(TW.categories)
$("#legend-for-facets").html("")
};
// current display among TW.categories (ex: ['terms'])
if (typeof forTypes != 'array' || ! forTypes.length) {
forTypes = getActivetypesNames().filter(function(ty){
return daclass in TW.Facets[ty]
})
} }
// current display among TW.categories (ex: 'terms') // (we ignore other types: their color legends remain the same by default)
var curType = actypes[0]
for (var k in forTypes) {
let curType = forTypes[k]
var LegendDiv = "<div id=legend-for-"+curType+" class=\"over-panels my-legend\">"
// all infos in a bin array // all infos in a bin array
var legendInfo = [] var legendInfo = []
...@@ -297,9 +394,7 @@ function set_ClustersLegend ( daclass, groupedByTicks ) { ...@@ -297,9 +394,7 @@ function set_ClustersLegend ( daclass, groupedByTicks ) {
// passed as arg or prepared in parseCustom // passed as arg or prepared in parseCustom
if (!groupedByTicks && (!TW.Facets[curType] || !TW.Facets[curType][daclass])) { if (!groupedByTicks && (!TW.Facets[curType] || !TW.Facets[curType][daclass])) {
console.warn(`no class bins for ${daclass}, displaying no legend`) console.warn(`no class bins for ${curType} ${daclass}`)
$("#legend-for-clusters").hide()
} }
else { else {
let daclassLabel = daclass let daclassLabel = daclass
...@@ -308,8 +403,8 @@ function set_ClustersLegend ( daclass, groupedByTicks ) { ...@@ -308,8 +403,8 @@ function set_ClustersLegend ( daclass, groupedByTicks ) {
&& TW.facetOptions[daclass].legend) { && TW.facetOptions[daclass].legend) {
daclassLabel = TW.facetOptions[daclass].legend daclassLabel = TW.facetOptions[daclass].legend
} }
var LegendDiv = ""
LegendDiv += ` <div class="legend-title">${daclassLabel}</div>` LegendDiv += ` <div class="legend-title"><small>${curType}:</small> ${daclassLabel}</div>`
LegendDiv += ' <div class="legend-scale">' LegendDiv += ' <div class="legend-scale">'
LegendDiv += ' <ul class="legend-labels">' LegendDiv += ' <ul class="legend-labels">'
...@@ -397,11 +492,20 @@ function set_ClustersLegend ( daclass, groupedByTicks ) { ...@@ -397,11 +492,20 @@ function set_ClustersLegend ( daclass, groupedByTicks ) {
} }
LegendDiv += ' </ul>' LegendDiv += ' </ul>'
LegendDiv += ' </div>' LegendDiv += ' </div>'
LegendDiv += ' </div>'
$("#legend-for-clusters").addClass( "my-legend" ); let perhapsPreviousLegend = document.getElementById("legend-for-"+curType)
$("#legend-for-clusters").html( LegendDiv ) if (perhapsPreviousLegend) {
$("#legend-for-clusters").show() perhapsPreviousLegend.outerHTML = LegendDiv
}
else {
let newLegend = document.createElement('div')
$("#legend-for-facets").prepend(newLegend)
newLegend.outerHTML = LegendDiv
}
}
} }
$("#legend-for-facets").show()
} }
// = = = = = = = = = = = [ / Clusters Plugin ] = = = = = = = = = = = // // = = = = = = = = = = = [ / Clusters Plugin ] = = = = = = = = = = = //
......
...@@ -96,9 +96,11 @@ function isUndef(variable){ ...@@ -96,9 +96,11 @@ function isUndef(variable){
function stringToSomeInt (anyString) { function stringToSomeInt (anyString) {
let charCodeSum = 0 let charCodeSum = 0
if (anyString && anyString.length) {
for (let i = 0 ; i < anyString.length ; i++) { for (let i = 0 ; i < anyString.length ; i++) {
charCodeSum += anyString.charCodeAt(i) charCodeSum += anyString.charCodeAt(i)
} }
}
return charCodeSum return charCodeSum
} }
......
...@@ -413,6 +413,9 @@ function mainStartGraph(inFormat, inData, twInstance) { ...@@ -413,6 +413,9 @@ function mainStartGraph(inFormat, inData, twInstance) {
TW.ByType = dicts.byType // useful for loops 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 // in-place: pre-compute all color/unselected color/size properties
prepareNodesRenderingProperties(TW.Nodes) prepareNodesRenderingProperties(TW.Nodes)
prepareEdgesRenderingProperties(TW.Edges, TW.Nodes) prepareEdgesRenderingProperties(TW.Edges, TW.Nodes)
......
...@@ -97,7 +97,6 @@ TW.resetGraph = function() { ...@@ -97,7 +97,6 @@ TW.resetGraph = function() {
// reset rendering gui flags // reset rendering gui flags
TW.gui.selectionActive = false TW.gui.selectionActive = false
TW.gui.handpickedcolor = false
// reset circle size and cursor // reset circle size and cursor
TW.gui.circleSize = 0 TW.gui.circleSize = 0
...@@ -107,6 +106,9 @@ TW.resetGraph = function() { ...@@ -107,6 +106,9 @@ TW.resetGraph = function() {
TW.gui.checkBox=false TW.gui.checkBox=false
TW.gui.lastFilters = {} TW.gui.lastFilters = {}
// reset colors legends
updateColorsLegend()
// forget the states // forget the states
TW.states = [TW.initialSystemState] TW.states = [TW.initialSystemState]
...@@ -734,7 +736,7 @@ function prepareNodesRenderingProperties(nodesDict) { ...@@ -734,7 +736,7 @@ function prepareNodesRenderingProperties(nodesDict) {
// default unselected color // default unselected color
defgrey_color : "rgba("+rgbStr+","+TW.conf.sigmaJsDrawingProperties.twNodesGreyOpacity+")", 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, alt_color: null,
altgrey_color: null, altgrey_color: null,
} }
......
...@@ -211,8 +211,8 @@ function scanGexf(gexfContent) { ...@@ -211,8 +211,8 @@ function scanGexf(gexfContent) {
// ex: terms // ex: terms
// ex: ISItermsriskV2_140 & ISItermsriskV2_140 // ex: ISItermsriskV2_140 & ISItermsriskV2_140
// optional arg optionalNodeConf should contain keys of the form: // optional arg optionalNodeConf should contain keys of the form:
// "node0": "NGram", // "node0": "Keywords",
// "node1": "Document" // "node1": "Scholars"
// etc. // etc.
// (it's read from project_conf.json) // (it's read from project_conf.json)
function sortNodeTypes(observedTypesDict, optionalNodeConf) { function sortNodeTypes(observedTypesDict, optionalNodeConf) {
...@@ -220,6 +220,14 @@ function sortNodeTypes(observedTypesDict, optionalNodeConf) { ...@@ -220,6 +220,14 @@ function sortNodeTypes(observedTypesDict, optionalNodeConf) {
observedTypes.sort(function(a,b) {return observedTypesDict[b] - observedTypesDict[a]}) observedTypes.sort(function(a,b) {return observedTypesDict[b] - observedTypesDict[a]})
let nbNodeTypes = 2 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 = [] var declaredTypes = []
for (var i = 0 ; i < nbNodeTypes ; i++ ) { for (var i = 0 ; i < nbNodeTypes ; i++ ) {
if (optionalNodeConf && optionalNodeConf["node"+i]) { if (optionalNodeConf && optionalNodeConf["node"+i]) {
...@@ -234,10 +242,14 @@ function sortNodeTypes(observedTypesDict, optionalNodeConf) { ...@@ -234,10 +242,14 @@ function sortNodeTypes(observedTypesDict, optionalNodeConf) {
} }
} }
var newcats = [] // console.log("observedTypes", observedTypes)
var catDict = {} // console.log("declaredTypes", declaredTypes)
var newcats = [] // will become TW.categories
var catDict = {} // will become TW.catDict
var nTypes = observedTypes.length var nTypes = observedTypes.length
if(nTypes==0) { if(nTypes==0) {
newcats[0]="Terms"; newcats[0]="Terms";
catDict["Terms"] = 0; catDict["Terms"] = 0;
...@@ -251,43 +263,72 @@ function sortNodeTypes(observedTypesDict, optionalNodeConf) { ...@@ -251,43 +263,72 @@ function sortNodeTypes(observedTypesDict, optionalNodeConf) {
console.log(`cat unique (${observedTypes[0]}) =>0`) console.log(`cat unique (${observedTypes[0]}) =>0`)
} }
if(nTypes>1) { if(nTypes>1) {
// allows multiple node types, with an "all the rest" node1 // allows multiple node types even if not well declared
// ----------------------------------------------------
// POSSIBLE: an "all the rest" last nodeType ?
let alreadyUsed = {}
// try stipulated cats, then fallbacks // try declared cats in declared position, independantly from each other
// possible: loop for (var i = 0 ; i < nbNodeTypes; i++) {
if (observedTypesDict[declaredTypes[0]]) { if (observedTypesDict[declaredTypes[i]]) {
newcats[0] = declaredTypes[0]; let validatedType = declaredTypes[i]
catDict[declaredTypes[0]] = 0; newcats[i] = validatedType;
alreadyUsed[validatedType] = true
} }
if (observedTypesDict[declaredTypes[1]]) {
newcats[1] = declaredTypes[1];
catDict[declaredTypes[1]] = 1;
} }
// NB: type for nodes0 will be the majoritary by default, unless taken // console.log("found stipulated cats", newcats, catDict)
if (!newcats[0]) {
if (observedTypes[0] != newcats[1]) { // fallbacks: if some or all stipulated cats are not found
newcats[0] = observedTypes[0] // 0 is the most frequent here // ---------
catDict[observedTypes[0]] = 0;
// 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
}
} }
else {
newcats[0] = observedTypes[1] // 1 is second most frequent
catDict[observedTypes[1]] = 0;
} }
} }
// console.log("after filling majority cats", newcats, catDict)
// all the rest // all the rest (heuristic B)
if (!newcats[nbNodeTypes-1]) {
for(var i in observedTypes) { 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] let c = observedTypes[i]
// or c is in "all the rest" group
// (POSS extend to multitypes)
if (c != newcats[0] && c != newcats[1]) { // -------------------------------------------- for a group "others"
if (!newcats[1]) newcats[1] = c; // with a group "others": if there is more than two cats altogether,
else newcats[1] += '/'+c // all the non majoritary or non-stipulated
catDict[c] = 1; // 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} return {'categories': newcats, 'lookup_dict': catDict}
} }
...@@ -650,10 +691,18 @@ function dictfyGexf( gexf , categories ){ ...@@ -650,10 +691,18 @@ function dictfyGexf( gexf , categories ){
for(var i in categories) { for(var i in categories) {
nodesByType[i] = [] nodesByType[i] = []
let subCats = categories[i].split(/\//g)
for (var j in subCats) { // without a group "others" -------------------
catDict[subCats[j]] = i 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 ) { ...@@ -1077,11 +1126,17 @@ function dictfyJSON( data , categories ) {
for(var i in categories) { for(var i in categories) {
nodesByType[i] = [] nodesByType[i] = []
let subCats = categories[i].split(/\//g) // without a group "others" -------------------
for (var j in subCats) { catDict[categories[i]] = i
catDict[subCats[j]] = 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 // normalization, same as parseGexf
......
...@@ -118,7 +118,7 @@ var SigmaUtils = function () { ...@@ -118,7 +118,7 @@ var SigmaUtils = function () {
context.beginPath(); context.beginPath();
if (settings('twSelectedColor') == "node") if (settings('twSelectedColor') == "node")
context.fillStyle = TW.gui.handpickedcolor? node.customAttrs.alt_color : node.color; // node's context.fillStyle = TW.gui.handpickedcolors[node.type].alton ? node.customAttrs.alt_color : node.color; // node's
else else
context.fillStyle = "#fff"; // default context.fillStyle = "#fff"; // default
...@@ -180,7 +180,6 @@ var SigmaUtils = function () { ...@@ -180,7 +180,6 @@ var SigmaUtils = function () {
var color, size, var color, size,
prefix = settings('prefix') || '' prefix = settings('prefix') || ''
//debug //debug
// console.warn("rendering edge", edge) // console.warn("rendering edge", edge)
...@@ -188,7 +187,7 @@ var SigmaUtils = function () { ...@@ -188,7 +187,7 @@ var SigmaUtils = function () {
// precomputed color with no opacity // precomputed color with no opacity
// cf. sigmaTools.edgeRGB // cf. sigmaTools.edgeRGB
var baseRGB = TW.gui.handpickedcolor ? edge.customAttrs.alt_rgb : edge.customAttrs.rgb var baseRGB = edge.customAttrs.useAltColor ? edge.customAttrs.alt_rgb : edge.customAttrs.rgb
if (edge.customAttrs.activeEdge) { if (edge.customAttrs.activeEdge) {
size = (defSize * 2) + 1 size = (defSize * 2) + 1
...@@ -291,7 +290,7 @@ var SigmaUtils = function () { ...@@ -291,7 +290,7 @@ var SigmaUtils = function () {
// mode variants 1: if a coloringFunction is active // mode variants 1: if a coloringFunction is active
if (! TW.gui.handpickedcolor) { if (! TW.gui.handpickedcolors[node.type].alton) {
nodeColor = node.color nodeColor = node.color
} }
else { else {
...@@ -301,7 +300,6 @@ var SigmaUtils = function () { ...@@ -301,7 +300,6 @@ var SigmaUtils = function () {
// mode variants 2: if node is selected, highlighted or unselected // mode variants 2: if node is selected, highlighted or unselected
if (TW.gui.selectionActive) { if (TW.gui.selectionActive) {
// the selected node(s) // the selected node(s)
if (node.customAttrs.active) { if (node.customAttrs.active) {
// called by label+background overlay cf. "subcall" // called by label+background overlay cf. "subcall"
...@@ -317,7 +315,7 @@ var SigmaUtils = function () { ...@@ -317,7 +315,7 @@ var SigmaUtils = function () {
// passive nodes should blend in the grey of twEdgeGreyColor // passive nodes should blend in the grey of twEdgeGreyColor
// cf settings_explorerjs, defgrey_color and deselectNodes() // cf settings_explorerjs, defgrey_color and deselectNodes()
else { else {
if (! TW.gui.handpickedcolor) { if (! TW.gui.handpickedcolors[node.type].alton) {
nodeColor = node.customAttrs.defgrey_color nodeColor = node.customAttrs.defgrey_color
} }
else { else {
...@@ -442,7 +440,7 @@ var SigmaUtils = function () { ...@@ -442,7 +440,7 @@ var SigmaUtils = function () {
x = Math.round(node[prefix + 'x'] - fontSize / 2 - 2); x = Math.round(node[prefix + 'x'] - fontSize / 2 - 2);
y = Math.round(node[prefix + 'y'] - fontSize / 2 - 2); y = Math.round(node[prefix + 'y'] - fontSize / 2 - 2);
w = Math.round( w = Math.round(
context.measureText(node.label).width + fontSize / 2 + size + 7 context.measureText(node.label).width + fontSize / 2 + size + 12
); );
h = Math.round(fontSize + 4); h = Math.round(fontSize + 4);
e = Math.round(fontSize / 2 + 4); e = Math.round(fontSize / 2 + 4);
...@@ -656,11 +654,17 @@ function edgeInfos(anEdge) { ...@@ -656,11 +654,17 @@ function edgeInfos(anEdge) {
} }
function gradientColoring(daclass) { // FIXME this function could be optimized
function gradientColoring(daclass, forTypes) {
graphResetLabelsAndSizes() // full loop graphResetLabelsAndSizes() // full loop
TW.gui.handpickedcolor = true if (typeof forTypes != 'array' || ! forTypes.length) {
// default strategy on multiple types: color all types that have the attr
forTypes = getActivetypesNames().filter(function(ty){
return daclass in TW.Facets[ty]
})
}
// value getter // value getter
let getVal let getVal
...@@ -672,9 +676,10 @@ function gradientColoring(daclass) { ...@@ -672,9 +676,10 @@ function gradientColoring(daclass) {
} }
var min_pow = 0; var min_pow = 0;
for(var nid in TW.Nodes) { for (var k in forTypes) {
var the_node = TW.Nodes[ nid ] let nids = TW.ByType[TW.catDict[forTypes[k]]]
var attval = getVal(the_node); for (var j in nids) {
let attval = getVal(TW.Nodes[ nids[j] ]);
if( !isNaN(parseFloat(attval)) ) { //is float if( !isNaN(parseFloat(attval)) ) { //is float
while(true) { while(true) {
var themult = Math.pow(10,min_pow); var themult = Math.pow(10,min_pow);
...@@ -685,6 +690,7 @@ function gradientColoring(daclass) { ...@@ -685,6 +690,7 @@ function gradientColoring(daclass) {
} }
} }
} }
}
var NodeID_Val = {} var NodeID_Val = {}
var real_min = 1000000; var real_min = 1000000;
...@@ -692,9 +698,13 @@ function gradientColoring(daclass) { ...@@ -692,9 +698,13 @@ function gradientColoring(daclass) {
var themult = Math.pow(10,min_pow); var themult = Math.pow(10,min_pow);
// console.log('themult', themult) // console.log('themult', themult)
for(var nid in TW.Nodes) { for (var k in forTypes) {
var the_node = TW.Nodes[ nid ] let nids = TW.partialGraph.graph.getNodesByType(
var attval = getVal(the_node) TW.catDict[forTypes[k]] // unordered type names to type id
)
for (var j in nids) {
let nid = nids[j]
var attval = getVal(TW.Nodes[nid])
var attnumber = Number(attval); var attnumber = Number(attval);
if (isNaN(attnumber)) { if (isNaN(attnumber)) {
...@@ -708,6 +718,7 @@ function gradientColoring(daclass) { ...@@ -708,6 +718,7 @@ function gradientColoring(daclass) {
if (round_number<real_min) real_min = round_number; if (round_number<real_min) real_min = round_number;
if (round_number>real_max) real_max = round_number; if (round_number>real_max) real_max = round_number;
} }
}
// console.log("NodeID_Val", NodeID_Val) // console.log("NodeID_Val", NodeID_Val)
...@@ -718,14 +729,21 @@ function gradientColoring(daclass) { ...@@ -718,14 +729,21 @@ function gradientColoring(daclass) {
// console.log("the mult: "+themult) // console.log("the mult: "+themult)
// console.log(" - - - - - - - - -- - - ") // console.log(" - - - - - - - - -- - - ")
// [ Scaling node colours(0-255) and sizes(2-7) ] // [ Scaling node colours(0-255) and sizes(2-7) ]
var Min_color = 0; var Min_color = 0;
var Max_color = 255; var Max_color = 255;
var Min_size = 1; var Min_size = 1;
var Max_size= 8; var Max_size= 8;
var setSize = (TW.facetOptions[daclass] && TW.facetOptions[daclass].setsize)
for(var nid in NodeID_Val) { for(var nid in NodeID_Val) {
var newval_color = Math.round( ( Min_color+(NodeID_Val[nid]["round"]-real_min)*((Max_color-Min_color)/(real_max-real_min)) ) ); var newval_color
// special case: all nodes have the same size
if (real_min == real_max) {
newval_color = 0
}
else {
newval_color = Math.round( ( Min_color+(NodeID_Val[nid]["round"]-real_min)*((Max_color-Min_color)/(real_max-real_min)) ) );
}
var hex_color = rgbToHex(255, (255-newval_color) , 0) var hex_color = rgbToHex(255, (255-newval_color) , 0)
let n = TW.partialGraph.graph.nodes(nid) let n = TW.partialGraph.graph.nodes(nid)
...@@ -736,7 +754,7 @@ function gradientColoring(daclass) { ...@@ -736,7 +754,7 @@ function gradientColoring(daclass) {
n.customAttrs.altgrey_color = "rgba("+(hex2rgba(hex_color).slice(0,3).join(','))+",0.4)" n.customAttrs.altgrey_color = "rgba("+(hex2rgba(hex_color).slice(0,3).join(','))+",0.4)"
// optionally changing size // optionally changing size
if (TW.facetOptions[daclass] && TW.facetOptions[daclass].setsize) { if (setSize) {
var newval_size = Math.round( ( Min_size+(NodeID_Val[nid]["round"]-real_min)*((Max_size-Min_size)/(real_max-real_min)) ) ); var newval_size = Math.round( ( Min_size+(NodeID_Val[nid]["round"]-real_min)*((Max_size-Min_size)/(real_max-real_min)) ) );
n.size = newval_size; n.size = newval_size;
} }
...@@ -752,8 +770,14 @@ function gradientColoring(daclass) { ...@@ -752,8 +770,14 @@ function gradientColoring(daclass) {
// Edge precompute alt_rgb by new source-target nodes-colours combination // Edge precompute alt_rgb by new source-target nodes-colours combination
repaintEdges() repaintEdges()
// remember in clusters // remember in clusters of each requested type
let bins = TW.Facets[getActivetypesNames()[0]][daclass] for (var k in forTypes) {
let ty = forTypes[k]
TW.gui.handpickedcolors[ty] = {
'alton': true,
'altattr': daclass,
}
let bins = TW.Facets[ty][daclass]
if (bins && bins.invIdx) { if (bins && bins.invIdx) {
for (var i in bins.invIdx) { for (var i in bins.invIdx) {
if (bins.invIdx[i].labl != '_non_numeric_') { if (bins.invIdx[i].labl != '_non_numeric_') {
...@@ -778,10 +802,11 @@ function gradientColoring(daclass) { ...@@ -778,10 +802,11 @@ function gradientColoring(daclass) {
} }
} }
} }
}
// NB legend will group different possible values using // NB legend will group different possible values using
// precomputed ticks from TW.Facets.terms[daclass] // precomputed ticks from TW.Facets[type][daclass]
set_ClustersLegend ( daclass) updateColorsLegend (daclass, forTypes)
TW.partialGraph.render(); TW.partialGraph.render();
} }
...@@ -808,15 +833,27 @@ function repaintEdges() { ...@@ -808,15 +833,27 @@ function repaintEdges() {
if (src && tgt) { if (src && tgt) {
let src_color let src_color
let tgt_color let tgt_color
if (TW.gui.handpickedcolor) {
// handpickedcolors on multiple types may or may not affect
// a given edge so we need the useAltColor individual flag
let useAlt = false
if (TW.gui.handpickedcolors[src.type].alton) {
src_color = src.customAttrs.alt_color || '#555' src_color = src.customAttrs.alt_color || '#555'
tgt_color = tgt.customAttrs.alt_color || '#555' useAlt = true
} }
else { else
src_color = src.color || '#555' src_color = src.color || '#555'
tgt_color = tgt.color || '#555'
if (TW.gui.handpickedcolors[tgt.type].alton) {
tgt_color = tgt.customAttrs.alt_color || '#555'
useAlt = true
} }
else
tgt_color = tgt.color || '#555'
e.customAttrs.alt_rgb = sigmaTools.edgeRGB(src_color,tgt_color) e.customAttrs.alt_rgb = sigmaTools.edgeRGB(src_color,tgt_color)
e.customAttrs.useAltColor = useAlt
// we don't set e.color because opacity may vary if selected or not // we don't set e.color because opacity may vary if selected or not
} }
} }
...@@ -830,10 +867,12 @@ function repaintEdges() { ...@@ -830,10 +867,12 @@ function repaintEdges() {
// (good for values centered around a neutral zone) // (good for values centered around a neutral zone)
// NB - binning is done at parseCustom (cf. TW.Facets) // NB - binning is done at parseCustom (cf. TW.Facets)
// - number of bins can be specified by attribute name in TW.facetOptions[daclass]["n"] // - number of bins can be specified by attribute name in TW.facetOptions[daclass]["n"]
function heatmapColoring(daclass) { function heatmapColoring(daclass, forTypes) {
var binColors var binColors
var doModifyLabel = false var doModifyLabel = false
var actypes = getActivetypesNames()
// let's go
graphResetLabelsAndSizes() // full loop
// default value // default value
let nColors = TW.conf.legendsBins || 5 let nColors = TW.conf.legendsBins || 5
...@@ -848,30 +887,34 @@ function heatmapColoring(daclass) { ...@@ -848,30 +887,34 @@ function heatmapColoring(daclass) {
} }
} }
// we have no specifications yet for colors and legends on multiple types if (typeof forTypes != 'array' || ! forTypes.length) {
if (actypes.length > 1) { // default strategy on multiple types: color all types that have the attr
console.warn("colors by bins will only color nodes of type 0") forTypes = getActivetypesNames().filter(function(ty){
return daclass in TW.Facets[ty]
})
} }
var ty = actypes[0] for (var k in forTypes) {
var ty = forTypes[k]
// global flag
TW.gui.handpickedcolors[ty] = {
'alton': true,
'altattr': daclass,
}
// our binning // our binning
var tickThresholds = TW.Facets[ty][daclass].invIdx var tickThresholds = TW.Facets[ty][daclass].invIdx
// verifications // verifications
if (tickThresholds.length - 1 != nColors) { if (tickThresholds.length - 1 != nColors) {
console.warn (`heatmapColoring setup mismatch: TW.Facets ticks ${tickThresholds.length} - 1 non_numeric from scanAttributes should == nColors ${nColors}`) console.log (`heatmapColoring setup mismatch: TW.Facets ticks ${tickThresholds.length} - 1 non_numeric from scanAttributes should == nColors ${nColors}`)
nColors = tickThresholds.length - 1 nColors = tickThresholds.length - 1
} }
binColors = getHeatmapColors(nColors) binColors = getHeatmapColors(nColors)
// let's go
graphResetLabelsAndSizes() // full loop
// global flag
TW.gui.handpickedcolor = true
// use our valueclass => ids mapping // use our valueclass => ids mapping
for (var k in tickThresholds) { for (var k in tickThresholds) {
...@@ -910,32 +953,43 @@ function heatmapColoring(daclass) { ...@@ -910,32 +953,43 @@ function heatmapColoring(daclass) {
// remember // remember
tickThresholds[k].col = theColor tickThresholds[k].col = theColor
} }
}
// Edge precompute alt_rgb by new source-target nodes-colours combination // Edge precompute alt_rgb by new source-target nodes-colours combination
repaintEdges() repaintEdges()
set_ClustersLegend ( daclass ) updateColorsLegend ( daclass, forTypes )
TW.partialGraph.render(); TW.partialGraph.render();
} }
function clusterColoring(daclass) { function clusterColoring(daclass, forTypes) {
graphResetLabelsAndSizes() // full loop graphResetLabelsAndSizes() // full loop (could be avoided most times if flag in sstate)
if (typeof forTypes != 'array' || ! forTypes.length) {
// default strategy on multiple types: color all types that have the attr
forTypes = getActivetypesNames().filter(function(ty){
return daclass in TW.Facets[ty]
})
}
// louvain needs preparation // louvain needs preparation
if(daclass=="clust_louvain") { if(daclass=="clust_louvain") {
if(!TW.SystemState().LouvainFait) { if(!TW.SystemState().LouvainFait) {
try { try {
RunLouvain() RunLouvain(function() {
TW.SystemState().LouvainFait = true TW.SystemState().LouvainFait = true
clusterColoring("clust_louvain")
})
} }
catch(e) { catch(e) {
TW.SystemState().LouvainFait = false TW.SystemState().LouvainFait = false
console.warn("skipped error on louvain, falling back to default colors") console.warn("skipped error on louvain, falling back to default colors")
daclass == 'clust_default' daclass == 'clust_default'
} }
return
} }
} }
...@@ -949,10 +1003,10 @@ function clusterColoring(daclass) { ...@@ -949,10 +1003,10 @@ function clusterColoring(daclass) {
} }
// reset the global state // reset the global state
TW.gui.handpickedcolor = false TW.gui.handpickedcolorsReset()
} }
else { else {
let colList = [] let colList = []
if (TW.conf.randomizeClusterColors) { if (TW.conf.randomizeClusterColors) {
// shuffle on entire array is better than random sorting function on each element // shuffle on entire array is better than random sorting function on each element
...@@ -964,8 +1018,10 @@ function clusterColoring(daclass) { ...@@ -964,8 +1018,10 @@ function clusterColoring(daclass) {
let nColors = TW.gui.colorList.length let nColors = TW.gui.colorList.length
for (var k in forTypes) {
let typeName = forTypes[k]
let facets = TW.Facets[typeName][daclass]
let facets = TW.Facets[getActivetypesNames()[0]][daclass]
if (facets && facets.invIdx) { if (facets && facets.invIdx) {
for (var i in facets.invIdx) { for (var i in facets.invIdx) {
let valGroup = facets.invIdx[i] let valGroup = facets.invIdx[i]
...@@ -992,7 +1048,7 @@ function clusterColoring(daclass) { ...@@ -992,7 +1048,7 @@ function clusterColoring(daclass) {
let theNode = TW.partialGraph.graph.nodes(valGroup.nids[j]) let theNode = TW.partialGraph.graph.nodes(valGroup.nids[j])
if (theNode) { if (theNode) {
theNode.customAttrs.alt_color = theColor theNode.customAttrs.alt_color = theColor
theNode.customAttrs.altgrey_color = "rgba("+rgbColStr+",0.4)" theNode.customAttrs.altgrey_color = "rgba("+rgbColStr+","+TW.conf.sigmaJsDrawingProperties.twNodesGreyOpacity+")"
} }
} }
} }
...@@ -1003,13 +1059,15 @@ function clusterColoring(daclass) { ...@@ -1003,13 +1059,15 @@ function clusterColoring(daclass) {
} }
// fallback on old, slower strategy if scanAttributes inactive // fallback on old, slower strategy if scanAttributes inactive
else { else {
for(var nid in TW.Nodes) { let nids = TW.ByType[TW.catDict[typeName]]
var the_node = TW.partialGraph.graph.nodes(nid) for (var j in nids) {
let nid = nids[j]
let the_node = TW.partialGraph.graph.nodes(nid)
if (the_node) { if (the_node) {
// POSS: use "hidden" in filters instead of remove/readd // POSS: use "hidden" in filters instead of remove/readd
// then this condition would be more useful here // typeName then this condition would be more useful here
if (! the_node.hidden) { if (! the_node.hidden) {
var attval = ( !isUndef(the_node.attributes) && !isUndef(the_node.attributes[daclass]) )? the_node.attributes[daclass] : TW.partialGraph.graph.nodes(nid)[daclass]; var attval = ( !isUndef(the_node.attributes) && !isUndef(the_node.attributes[daclass]) )? the_node.attributes[daclass] : TW.partialGraph.graph.nodes(nid)[daclass];
...@@ -1033,15 +1091,18 @@ function clusterColoring(daclass) { ...@@ -1033,15 +1091,18 @@ function clusterColoring(daclass) {
} }
} }
} }
// set the global state // set the global state
TW.gui.handpickedcolor = true TW.gui.handpickedcolors[typeName] = {
'alton': true,
'altattr': daclass,
}
}
} }
// Edge precompute alt_rgb by new source-target nodes-colours combination // Edge precompute alt_rgb by new source-target nodes-colours combination
repaintEdges() repaintEdges()
set_ClustersLegend ( daclass ) updateColorsLegend ( daclass, forTypes )
TW.partialGraph.render(); TW.partialGraph.render();
} }
......
...@@ -21,7 +21,7 @@ TW.conf = (function(TW){ ...@@ -21,7 +21,7 @@ TW.conf = (function(TW){
// ...or remote bridge to default source api ajax queries // ...or remote bridge to default source api ajax queries
TWConf.sourceAPI={} TWConf.sourceAPI={}
TWConf.sourceAPI["nodetypes"] = {"node0": "NGram", "node1": "Document" } TWConf.sourceAPI["nodetypes"] = {"node0": "Keywords", "node1": "Scholars" }
TWConf.sourceAPI["forNormalQuery"] = "services/api/graph" TWConf.sourceAPI["forNormalQuery"] = "services/api/graph"
TWConf.sourceAPI["forFilteredQuery"] = "services/api/graph" TWConf.sourceAPI["forFilteredQuery"] = "services/api/graph"
...@@ -251,7 +251,7 @@ TW.conf = (function(TW){ ...@@ -251,7 +251,7 @@ TW.conf = (function(TW){
// "default" for white background // "default" for white background
// not selected <=> (1-greyness) // not selected <=> (1-greyness)
twNodesGreyOpacity: .4, // smaller value: more grey twNodesGreyOpacity: .5, // smaller value: more grey
twBorderGreyColor: "rgba(100, 100, 100, 0.5)", twBorderGreyColor: "rgba(100, 100, 100, 0.5)",
twEdgeGreyColor: "rgba(100, 100, 100, 0.25)", twEdgeGreyColor: "rgba(100, 100, 100, 0.25)",
}; };
......
...@@ -21,7 +21,7 @@ TW.conf = (function(TW){ ...@@ -21,7 +21,7 @@ TW.conf = (function(TW){
// ...or remote bridge to default source api ajax queries // ...or remote bridge to default source api ajax queries
TWConf.sourceAPI={} TWConf.sourceAPI={}
TWConf.sourceAPI["nodetypes"] = {"node0": "NGram", "node1": "Document" } TWConf.sourceAPI["nodetypes"] = {"node0": "Keywords", "node1": "Scholars" }
TWConf.sourceAPI["forNormalQuery"] = "services/api/graph" TWConf.sourceAPI["forNormalQuery"] = "services/api/graph"
TWConf.sourceAPI["forFilteredQuery"] = "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