Commit 5a095c4c authored by Romain Loth's avatar Romain Loth

fix Louvain legend and menu + partly optimize coloring <=> selection interactions

parent 2cf9e030
......@@ -20,8 +20,8 @@ function changeGraphAppearanceByFacets( manualflag ) {
let currentNbNodes = TW.partialGraph.graph.nNodes()
// create colormenu
var color_menu_info = '<li><a href="#" onclick="graphResetColor()">By Default</a></li>';
// create colormenu and 1st default entry
var color_menu_info = '<li><a href="#" onclick="TW.gui.handpickedcolor = false ; graphResetLabelsAndSizes()">By Default</a></li>';
if( $( "#colorgraph-menu" ).length>0 ) {
......@@ -86,7 +86,7 @@ function changeGraphAppearanceByFacets( manualflag ) {
}
// we also add clust_louvain in all cases
color_menu_info += `<li><a href="#" onclick='clusterColoring("clust_louvain")'>By Louvain clustering ( ? | ${currentNbNodes})</a></li>`
color_menu_info += `<li><a href="#" onclick='clusterColoring("clust_louvain")'>By Louvain clustering ( <span id="louvainN">?</span> | ${currentNbNodes})</a></li>`
// for debug
// console.warn('color_menu_info', color_menu_info)
......@@ -119,13 +119,45 @@ function RunLouvain() {
var community = jLouvain().nodes(node_realdata).edges(edge_realdata);
var results = community();
var louvainValNids = {}
for(var i in results) {
let n = TW.partialGraph.graph.nodes(i)
let n = TW.partialGraph.graph.nodes(i) // <= new way: like all other colors
if (n) {
n.attributes["clust_louvain"] = results[i]
// TW.Nodes[i].attributes["clust_louvain"]=results[i]
}
// also create legend's facets
louvainValNids = updateValueFacets(louvainValNids, n, "clust_louvain")
}
let nClasses = 0
for (let typ in louvainValNids) {
let revIdx = louvainValNids[typ]["clust_louvain"]['map']
// init a new legend in TW.Clusters
TW.Clusters[typ]['clust_louvain'] = []
for (let entry in revIdx) {
let len = revIdx[entry].length
if (len) {
TW.Clusters[typ]['clust_louvain'].push({
'labl': `${entry} (${len})`,
'fullLabl': `${typ}||Louvain||${entry} (${len})`,
'nids': revIdx[entry],
'val': entry
})
nClasses ++
}
}
}
// finally update the html menu
let menu = document.getElementById('louvainN')
if (menu) {
menu.innerHTML = nClasses
}
// NB the LouvainFait flag is updated by caller fun
}
......@@ -137,6 +169,8 @@ function SomeEffect( ValueclassCode ) {
greyEverything();
TW.gui.selectionActive = true
var nodes_2_colour = {};
var edges_2_colour = {};
......@@ -201,37 +235,11 @@ function SomeEffect( ValueclassCode ) {
TW.partialGraph.refresh()
}
function graphResetColor(){
// some colorings cases also modify size and label
function graphResetLabelsAndSizes(){
// reset global var
TW.gui.handpickedcolor = false
// reset each node's color and label
for (var j in TW.nodeIds) {
let n = TW.partialGraph.graph.nodes(TW.nodeIds[j])
// as usual, n can be absent if not in current subset !
if (n) {
n.color = n.customAttrs["true_color"];
n.customAttrs.alt_color = false
n.customAttrs.altgrey_color = false
n.label = TW.Nodes[n.id].label
// some colorings also modified size
n.size = TW.Nodes[n.id].size
}
}
// if (TW.partialGraph.settings('drawEdges')) {
// for(var x in eds){
// e=eds[x];
// e.customAttrs["grey"] = 0;
// e.color = e.customAttrs["true_color"];
// }
// }
TW.partialGraph.render()
// NB could be also merged with greyEverything and highlightSelectedNodes(false)
cancelSelection(false, {'render':true, 'resetSizes':true, 'resetLabels': true})
}
......@@ -245,9 +253,6 @@ function set_ClustersLegend ( daclass, groupedByTicks ) {
$("#legend-for-clusters").html("")
if(daclass==null) return;
if (daclass=="clust_louvain")
daclass = "louvain"
var actypes = getActivetypes()
// we have no specifications yet for colors (and legends) on multiple types
......@@ -285,7 +290,14 @@ function set_ClustersLegend ( daclass, groupedByTicks ) {
if (nMatchedNodes) {
var midNid = legendInfo[l]['nids'][Math.floor(3*nMatchedNodes/4)]
var exampleColor = TW.partialGraph.graph.nodes(midNid).color
var exampleColor
if (TW.gui.handpickedcolor) {
exampleColor = TW.partialGraph.graph.nodes(midNid).customAttrs.alt_color
}
else {
exampleColor = TW.partialGraph.graph.nodes(midNid).color
}
// create the legend item
var preparedLabel = legendInfo[l]['labl']
......
......@@ -248,7 +248,7 @@ function SelectionEngine() {
if(nid) {
n = TW.partialGraph.graph.nodes(nid)
if(n) {
n.color = n.customAttrs['true_color'];
// our deselected flag
n.customAttrs['grey'] = 0;
// it's a selected node
......@@ -877,7 +877,7 @@ var TinaWebJS = function ( sigmacanvas ) {
// console.log("clickStage event e", e)
if (! e.data.captor.isDragging
&& TW.SystemState.selectionNids.length
&& TW.gui.selectionActive
&& ! TW.gui.circleSize) {
// we clear selections and all its effects
......
......@@ -142,10 +142,17 @@ function cancelSelection (fromTagCloud, settings) {
// console.log("cancelSelection: node", n)
if (n) {
n.active = false;
n.color = TW.gui.handpickedcolor ? n.customAttrs['alt_color'] : n.customAttrs['true_color'];
n.customAttrs.grey = 0
n.customAttrs.forceLabel = 0
n.customAttrs.highlight = 0
// some colorings cases also modify size and label
if (settings.resetLabels) {
n.label = TW.Nodes[n.id].label
}
if (settings.resetSizes) {
n.size = TW.Nodes[n.id].size
}
}
}
......@@ -441,8 +448,8 @@ function LevelButtonDisable( TF ){
// renderer will see the flags and handle the case accordingly
function greyEverything(){
for(var j in TW.nodeIds){
let n = TW.partialGraph.graph.nodes(TW.nodeIds[j])
for(var nid in TW.Nodes){
let n = TW.partialGraph.graph.nodes(nid)
if (n && !n.hidden) {
// normal case handled by node renderers
......@@ -520,15 +527,22 @@ function prepareNodesRenderingProperties(nodesDict) {
n.color = `rgb(${rgbStr})`
}
else {
// will not be modified
n.color = TW.conf.sigmaJsDrawingProperties.defaultNodeColor
rgbStr = n.color.split(',').splice(0, 3).join(',');
}
n.customAttrs = {
grey: false,
highlight: false,
true_color : n.color,
defgrey_color : "rgba("+rgbStr+","+TW.conf.sigmaJsDrawingProperties.twNodesGreyOpacity+")"
// status flags
grey: false, // deselected
highlight: false, // neighbors or legend's click
// default unselected color
defgrey_color : "rgba("+rgbStr+","+TW.conf.sigmaJsDrawingProperties.twNodesGreyOpacity+")",
// will be used for repainting (read when TW.gui.handpickedcolor flag)
alt_color: null,
altgrey_color: null,
}
// POSS n.type: distinguish rendtype and twtype
......
......@@ -204,15 +204,9 @@ function scanGexf(gexfContent) {
return sortNodeTypes(categoriesDict)
}
// sorting observed node types into Sem/Soc (factorized 11/05/2017)
// --------------------
// FIXME this factorizes what we had twice (json & gexf scanFile workflows),
// and we just added missing TW.conf.catSoc/Sem comparisons
// *but it doesn't fix the underlying logic*
// (current expected structure in 'categories' can only accomodate 2 types
// and the way it and catDict are used is not entirely coherent throughout
// the project, cf. among others: - the effect on 'typestring'
// - the way default cat is handled as 0...)
// sorting observed node types into Sem/Soc
// NB: tina structure in 'categories' can only accomodate 2 types
// - default cat is handled as cat 0...
// -------------------
// expected content: usually a just a few cats over all nodes
// ex: terms
......
......@@ -280,15 +280,27 @@ var SigmaUtils = function () {
var nodeSize = node[prefix + 'size']
var nodeColor = node.color || settings('defaultNodeColor')
// our shapes are dependant on type AND categories
// so unless we rename types at parsing and use sigma's
// "def vs someType" syntax we need the bool and conditions
// our shapes are dependant on flags, type AND categories
// so we need the bool and conditions
// other POSS: we rename n.type at parsing
// and each action (recoloring/selection)
// to use sigma's "def vs someType" syntax
// NB cost of this condition seems small:
// - without: [11 - 30] ms for 23 nodes
// - with : [11 - 33] ms for 23 nodes
var catSemFlag = (TW.categories.length > 1 && node.type == TW.categories[0])
// mode variants
// mode variants 1: if a coloringFunction is active
if (! TW.gui.handpickedcolor) {
nodeColor = node.color
}
else {
nodeColor = node.customAttrs.alt_color
}
// mode variants 2: if node is selected, highlighted or unselected
if (TW.gui.selectionActive) {
// passive nodes should blend in the grey of twEdgeGreyColor
// cf settings_explorerjs, defgrey_color and greyEverything()
......@@ -297,28 +309,19 @@ var SigmaUtils = function () {
nodeColor = node.customAttrs.defgrey_color
}
else {
// #C01O3 += alpha 55
// => #C01O355
if (!node.customAttrs.altgrey_color) {
node.customAttrs.altgrey_color = "rgba("+(hex2rgba(node.customAttrs.alt_color).slice(0,3).join(','))+",0.4)"
}
nodeColor = node.customAttrs.altgrey_color
}
// nice looking uniform grey
borderColor = TW.conf.sigmaJsDrawingProperties.twBorderGreyColor
}
// neighbor nodes <=> (highlight flag AND selectionActive)
else if(node.customAttrs.highlight) {
else {
nodeSize *= 1.4
borderSize *= 1.4
if (TW.gui.handpickedcolor) {
nodeColor = node.customAttrs.alt_color
}
}
else if(node.active) {
if(node.active) {
// called by label+background overlay cf. "subcall"
nodeColor = "#222" // metro ticket
}
}
}
// highlight AND (NOT selectionActive) => highlight just this one time
......@@ -642,7 +645,9 @@ function edgeInfos(anEdge) {
function gradientColoring(daclass) {
// £TODO group as an option of cancelSelection to avoid 2 loops
cancelSelection(false);
graphResetLabelsAndSizes()
TW.gui.handpickedcolor = true
......@@ -709,8 +714,8 @@ function gradientColoring(daclass) {
n.color = hex_color
n.customAttrs.alt_color = hex_color
// FIXME not used ?
n.customAttrs.altgrey_color = false
// also recalculating the "unselected" color for next renders
n.customAttrs.altgrey_color = "rgba("+(hex2rgba(hex_color).slice(0,3).join(','))+",0.4)"
// £TODO SETTING SIZE HERE SHOULD BE OPTIONAL
var newval_size = Math.round( ( Min_size+(NodeID_Val[nid]["round"]-real_min)*((Max_size-Min_size)/(real_max-real_min)) ) );
......@@ -808,11 +813,10 @@ function heatmapColoring(daclass) {
binColors = getHeatmapColors(nColors)
// for debug of heatmapColoring
var totalsPerBinMin = {}
// let's go
// £TODO group as an option of cancelSelection to avoid 2 loops
cancelSelection(false);
graphResetLabelsAndSizes()
// global flag
TW.gui.handpickedcolor = true
......@@ -829,15 +833,12 @@ function heatmapColoring(daclass) {
// ex: {labl: "terms||growth_rate||[0 ; 0.583]", nids: Array(99), range: [0 ; 0.583210]}
totalsPerBinMin[tickThresholds[k].range[0]] = tickThresholds[k].nids.length
// color the referred nodes
for (var j in tickThresholds[k].nids) {
let n = TW.partialGraph.graph.nodes(tickThresholds[k].nids[j])
n.color = binColors[k]
n.customAttrs.alt_color = binColors[k]
n.customAttrs.altgrey_color = false
n.customAttrs.altgrey_color = "rgba("+(hex2rgba(binColors[k]).slice(0,3).join(','))+",0.4)"
var originalLabel = TW.Nodes[n.id].label
if (doModifyLabel) {
......@@ -851,8 +852,6 @@ function heatmapColoring(daclass) {
}
}
// console.info('coloring distribution per tick thresholds' , totalsPerBinMin)
// Edge precompute alt_rgb by new source-target nodes-colours combination
repaintEdges()
......@@ -872,11 +871,22 @@ function clusterColoring(daclass) {
console.log(" = = = = = = = = = = = = = = = = = ")
console.log("")
// £TODO group as an option of cancelSelection to avoid 2 loops
cancelSelection(false);
graphResetLabelsAndSizes()
// louvain needs preparation
if(daclass=="clust_louvain") {
if(!TW.states.slice(-1)[0].LouvainFait) {
RunLouvain()
TW.states.slice(-1)[0].LouvainFait = true
try {
RunLouvain()
TW.states.slice(-1)[0].LouvainFait = true
}
catch(e) {
TW.states.slice(-1)[0].LouvainFait = false
console.warn("skipped error on louvain, falling back to default colors")
daclass == 'clust_default'
}
}
}
......@@ -906,33 +916,32 @@ function clusterColoring(daclass) {
let nColors = TW.gui.colorList.length
for(var j in TW.nodeIds) {
var the_node = TW.Nodes[ TW.nodeIds[j] ]
var the_node = TW.partialGraph.graph.nodes(TW.nodeIds[j])
// ££TODO put louvain in graph.nodes() like other attrs ??
// then possible to use TW.partialGraph.graph.nodes(TW.nodeIds[j])
if (the_node) {
// if (j< 15)
// console.log(` ${TW.nodeIds[j]} : hidden?${the_node.hidden} the_node.attributes[daclass] = ${the_node.attributes[daclass]}`) // final val=${attval}
// POSS: use "hidden" in filters instead of remove/readd
// then this condition would be more useful here
if (! the_node.hidden) {
var attval = ( !isUndef(the_node.attributes) && !isUndef(the_node.attributes[daclass]) )? the_node.attributes[daclass] : TW.partialGraph.graph.nodes(TW.nodeIds[j])[daclass];
if (! the_node.hidden) {
var attval = ( !isUndef(the_node.attributes) && !isUndef(the_node.attributes[daclass]) )? the_node.attributes[daclass] : TW.partialGraph.graph.nodes(TW.nodeIds[j])[daclass];
let theColor
let theColor
if (attval == '_non_numeric_') {
theColor = '#bbb'
}
else if (! isNaN(parseInt(attval))) {
theColor = colList[ attval ]
}
else {
let someRepresentativeInt = stringToSomeInt(attval) % nColors
theColor = colList[ someRepresentativeInt ]
}
if (attval == '_non_numeric_') {
theColor = '#bbb'
}
else if (! isNaN(parseInt(attval))) {
theColor = colList[ attval ]
// TW.partialGraph.graph.nodes(TW.nodeIds[j]).color = theColor
the_node.customAttrs.alt_color = theColor
the_node.customAttrs.altgrey_color = "rgba("+(hex2rgba(theColor).slice(0,3).join(','))+",0.4)"
}
else {
let someRepresentativeInt = stringToSomeInt(attval) % nColors
theColor = colList[ someRepresentativeInt ]
}
TW.partialGraph.graph.nodes(TW.nodeIds[j]).color = theColor
TW.partialGraph.graph.nodes(TW.nodeIds[j]).customAttrs.alt_color = theColor
TW.partialGraph.graph.nodes(TW.nodeIds[j]).customAttrs.altgrey_color = false
}
}
// set the global state
......
......@@ -30,7 +30,6 @@ sigmaTools = (function(stools) {
customAttrs: {
grey: false,
highlight: false,
true_color: rawNode.viz.color,
defgrey_color : "rgba("+rgbStr+",.4)"
},
// for metrics like centrality
......
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