Commit 71507331 authored by Romain Loth's avatar Romain Loth

much better bins and clusters (in testing)

factor in binning we had for legends to a much earlier stage in parsing, thus creating value facets with reverse index accessed via TW.Clusters.type.facet[facetvalue].nids
parent 4fb19e2f
......@@ -9,18 +9,14 @@ function newPopup(url) {
// = = = = = = = = = = = [ Clusters Plugin ] = = = = = = = = = = = //
// Execution: ChangeGraphAppearanceByAtt( true )
// It scans the existing node-attributes and t keeps only those which are Numeric.
// then, add the button in the html with the sigmaUtils.clustersBy(x) listener.
// [TODO: fonction un peu lourde dans le profilage]
function ChangeGraphAppearanceByAtt( manualflag ) {
// Execution: changeGraphAppearanceByFacets( true )
// It reads scanned node-attributes and prepared legends in TW.Clusters
// to add the button in the html with the sigmaUtils.clustersBy(x) listener.
function changeGraphAppearanceByFacets( manualflag ) {
if ( !isUndef(manualflag) && !TW.colorByAtt ) TW.colorByAtt = manualflag;
if(!TW.colorByAtt) return;
// Seeing all the possible attributes!
var AttsDict = {}
// for GUI html: if present, rename raw attribute key by a proper label
var AttsTranslations = {
'clust_louvain': 'Groupes de voisins, méthode de Louvain',
......@@ -30,73 +26,74 @@ function ChangeGraphAppearanceByAtt( manualflag ) {
'modularity_class': 'Groupes de voisins, méthode des classes de modularité'
}
var Atts_2_Exclude = {}
for (var j in TW.nodeIds) {
let nid = TW.nodeIds[j]
let n = TW.partialGraph.graph.nodes(nid)
if(!n.hidden) {
for(var a in TW.Nodes[nid].attributes) {
var someatt = TW.Nodes[nid].attributes[a]
// Identifying the attribute datatype: exclude strings and objects
if ( ( typeof(someatt)=="string" && isNaN(Number(someatt)) ) || typeof(someatt)=="object" ) {
if (!Atts_2_Exclude[a]) Atts_2_Exclude[a]=0;
Atts_2_Exclude[a]++;
}
}
var possible_atts = [];
if (!isUndef(TW.Nodes[nid].attributes))
possible_atts = Object.keys(TW.Nodes[nid].attributes)
if(!isUndef(n.degree))
possible_atts.push("degree")
possible_atts.push("clust_louvain")
for(var a in possible_atts){
if ( !AttsDict[ possible_atts[a] ] )
AttsDict[ possible_atts[a] ] = 0
AttsDict[ possible_atts[a] ] ++;
}
}
}
for(var i in Atts_2_Exclude)
delete AttsDict[i];
var AttsDict_sorted = ArraySortByValue(AttsDict, function(a,b){
return b-a
});
// console.log( "I AM IN ChangeGraphAppearanceByAtt( true )" )
// console.log( AttsDict_sorted )
// create colormenu
var ty = getCurrentType()
var color_menu_info = '<li><a href="#" onclick="graphResetColor()">By Default</a></li>';
if( $( "#colorgraph-menu" ).length>0 ){
for (var i in AttsDict_sorted) {
var att_s = AttsDict_sorted[i].key;
var att_c = AttsDict_sorted[i].value;
var the_method = "clustersBy"
// variants
if(att_s.indexOf("clust")>-1) the_method = "colorsBy"
if(att_s == "growth_rate") the_method = "colorsRelByBins"
if(att_s == "age") the_method = "colorsRelByBins"
// each facet family or clustering type was already prepared
for (var att_s in TW.Clusters[ty]) {
// POSS here distinguish [ty][att_s].classes.length and ranges.length
var att_c = TW.Clusters[ty][att_s].length
var the_method = "clustersBy"
// labels :)
var lab_att_s ;
if (AttsTranslations[att_s]) lab_att_s = AttsTranslations[att_s]
else lab_att_s = att_s
// variants
if(att_s.indexOf("clust")>-1||att_s.indexOf("class")>-1) {
// for classes and clusters
the_method = "colorsBy"
}
if(att_s == "growth_rate") the_method = "colorsRelByBins"
if(att_s == "age") the_method = "colorsRelByBins"
// family label :)
var lab_att_s ;
if (AttsTranslations[att_s]) lab_att_s = AttsTranslations[att_s]
else lab_att_s = att_s
color_menu_info += '<li><a href="#" onclick=\''+the_method+'("'+att_s+'")\'>By '+lab_att_s+'('+att_c+')'+'</a></li>'
color_menu_info += '<li><a href="#" onclick=\''+the_method+'("'+att_s+'")\'>By '+lab_att_s+'('+att_c+')'+'</a></li>'
// console.log('<li><a href="#" onclick=\''+the_method+'("'+att_s+'")\'>By '+att_s+'('+att_c+')'+'</a></li>')
}
$("#colorgraph-menu").html(color_menu_info)
}
// // 2) prepare legend slots
// console.warn ("classes_per_Att:", classes_per_Att)
// let nodeType = getCurrentType()
// for (var attr in classes_per_Att) {
// let distinctVals = Object.keys(classes_per_Att[attr])
//
// // ------------------------------------------------
// if (distinctVals.length > TW.maxDiscreteValues) {
// TW.Clusters[nodeType][attr] = {'ranges': {}}
// // will be computed at changeColor FIXME could be now...
// }
// else {
// TW.Clusters[nodeType][attr] = {'classes': {}}
// for (var k_cls in distinctVals) {
// TW.Clusters[nodeType][attr].classes[distinctVals[k_cls]] = []
// // will become array of ids per subclass
// }
// }
// }
}
// creates TW.legendsBins bins
// @sortedValues array, mandatory
function intervalsInventory(sortedValues) {
var binmins = []
var len = sortedValues.length
for (var l=0 ; l < TW.legendsBins ; l++) {
let nthVal = Math.floor(len * l / TW.legendsBins)
binmins.push(sortedValues[nthVal])
}
// console.info("legendRefTicks", binmins)
return binmins
}
......@@ -125,60 +122,40 @@ function RunLouvain() {
}
function SomeEffect( ClusterCode ) {
console.log( "SomeEffect" )
console.log( ClusterCode )
// ex: ISItermsriskV2_140 & ISItermsriskV2_140||clust_default||7
// vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv vvvvv v
// type Cluster key clstID
var raw = ClusterCode.split("||")
var Type=raw[0], Cluster=raw[1], clstID=Number(raw[2]);
var present = TW.partialGraph.states.slice(-1)[0]; // Last
var type_t0 = present.type;
var str_type_t0 = type_t0.map(Number).join("|")
console.log( "\t"+str_type_t0)
// Highlights nodes with given value using id map
// previously: highlighted nodes with given value using loop on node values
function SomeEffect( ValueclassCode ) {
console.debug("highlighting:", ValueclassCode )
greyEverything();
var nodes_2_colour = {};
var edges_2_colour = {};
var nodesV = getVisibleNodes()
for(var j=0;j<TW.nNodes;j++) {
let n = TW.partialGraph.graph.nodes(TW.nodeIds[j])
if (n && !n.hidden) {
n.customAttrs.forceLabel = false
// we look also at the original gexf node to get access to gexf attributes like cluster
var gNode = TW.Nodes[n.id]
if (gNode.type = Type && !isUndef(gNode.attributes[Cluster]) && gNode.attributes[Cluster]==clstID) {
// console.log( n.id + " | " + Cluster + " : " + node.attributes[Cluster] )
// nodes_2_colour[n.id] = n.degree;
nodes_2_colour[n.id] = 1;
}
}
}
// ex: ISItermsriskV2_140 & ISItermsriskV2_140::clust_default::7
// vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv vvvvv v
// type Cluster key class iClu
// (family) (array index)
var raw = ValueclassCode.split("::")
var nodeType=raw[0],
cluType=raw[1],
iClu=Number(raw[2]);
for(var s in nodes_2_colour) {
if(TW.Relations[str_type_t0] && TW.Relations[str_type_t0][s] ) {
neigh = TW.Relations[str_type_t0][s]
if(neigh) {
for(j in neigh) {
t = neigh[j]
if( !isUndef(nodes_2_colour[t]) ) {
edges_2_colour[s+";"+t]=true;
edges_2_colour[t+";"+s]=true;
}
}
}
}
}
// also get node type code from env (ie "1", "1|1", etc for TW.Relations lookup)
var present = TW.partialGraph.states.slice(-1)[0]; // Last
var type_t0 = present.type;
var str_type_t0 = type_t0.map(Number).join("|")
// console.log( "\t"+str_type_t0)
// we have our precomputed idmaps for nodes_2_colour
// -------------------------------------------------
for (var k in TW.Clusters[nodeType][cluType][iClu].nids) {
var nid = TW.Clusters[nodeType][cluType][iClu].nids[k]
nodes_2_colour[nid] = true
}
for(var nid in nodes_2_colour) {
n = TW.partialGraph.graph.nodes(nid)
......@@ -189,6 +166,19 @@ function SomeEffect( ClusterCode ) {
// highlight (like neighbors but with no selection)
n.customAttrs['highlight'] = true;
}
if(TW.Relations[str_type_t0] && TW.Relations[str_type_t0][nid] ) {
neigh = TW.Relations[str_type_t0][nid]
if(neigh) {
for(j in neigh) {
tgt_nid = neigh[j]
if( !isUndef(nodes_2_colour[tgt_nid]) ) {
edges_2_colour[nid+";"+tgt_nid]=true;
edges_2_colour[tgt_nid+";"+nid]=true;
}
}
}
}
}
......@@ -202,23 +192,16 @@ function SomeEffect( ClusterCode ) {
}
var nodes_2_label = ArraySortByValue(nodes_2_colour, function(a,b){
return b-a
});
// force 4 first labels
for(var j in nodes_2_label) {
if(j==4)
break
var ID = nodes_2_label[j].key
TW.partialGraph.graph.nodes(ID).customAttrs.forceLabel = true;
}
// // force 3 first labels
// for(var j in nodes_2_label) {
// if(j==3)
// break
// var ID = nodes_2_label[j].key
// TW.partialGraph.graph.nodes(ID).customAttrs.forceLabel = true;
// }
TW.selectionActive=true;
// TW.partialGraph.render()
TW.partialGraph.refresh()
}
......@@ -253,82 +236,67 @@ function graphResetColor(){
}
function set_ClustersLegend ( daclass ) {
// @daclass: the name of a numeric/categorical attribute from node.attributes
// @groupingTicks: an optional threshold's array expressing ranges with their low/up bounds label and ref to matchin nodeIds
function set_ClustersLegend ( daclass, groupedByTicks ) {
//TW.partialGraph.states.slice(-1)[0].LouvainFait = true
$("#legend_for_clusters").removeClass( "my-legend" )
$("#legend_for_clusters").html("")
if(daclass==null) return;
var ClustNB_CurrentColor = {}
// TODO avoid this new loop by writing the census at coloring time in some var eg TW.Clusters.legends
var nodesV = getVisibleNodes()
for(var i in nodesV) {
n = nodesV[i]
color = n.color
type = TW.Nodes[n.id].type
clstNB = TW.Nodes[n.id].attributes[daclass]
// joining properties in a "ClusterCode"
ClustNB_CurrentColor[type+"||"+daclass+"||"+clstNB] = color
}
LegendDiv = ""
LegendDiv += ' <div class="legend-title">Map Legend</div>'
var LegendDiv = ""
LegendDiv += ` <div class="legend-title">Map Legend <small>(${daclass})</small></div>`
LegendDiv += ' <div class="legend-scale">'
LegendDiv += ' <ul class="legend-labels">'
if (daclass=="clust_louvain")
daclass = "louvain"
OrderedClustDicts = Object.keys(ClustNB_CurrentColor).sort(function (a,b) {
// mostly joined properties of the form "terms||pageranks||9.494E-4"
var aInfos = a.split('||')
var bInfos = b.split('||')
var compared
if (aInfos.length == 3 && bInfos.length == 3)
compared = (Number(aInfos[2]-Number(bInfos[2])))
else
compared = a-b
return compared
})
// console.log("set_ClustersLegend: OrderedClustDicts", OrderedClustDicts)
// TODO allow external cluster legends dict
if( daclass.indexOf("clust")>-1 ) {
for(var i in OrderedClustDicts) {
var IDx = OrderedClustDicts[i]
var raw = IDx.split("||")
var Type = raw[0]
var ClustType = raw[1]
var ClustID = raw[2]
var legTxt = "N/A"
if (TW.Clusters && TW.Clusters[Type] && TW.Clusters[Type][ClustType] && TW.Clusters[Type][ClustType][ClustID]) {
legTxt = TW.Clusters[Type][ClustType][ClustID]
}
var Color = ClustNB_CurrentColor[IDx]
// console.log ( Color+" : ", Type, ClustType, ClustID )
console.log ( Color+" : "+ legTxt )
var ColorDiv = '<span style="background:'+Color+';"></span>'
LegendDiv += '<li onclick=\'SomeEffect("'+IDx+'")\'>'+ColorDiv+ legTxt+"</li>"+"\n"
}
} else {
for(var i in OrderedClustDicts) {
var IDx = OrderedClustDicts[i]
var Color = ClustNB_CurrentColor[IDx]
// pr ( Color+" : "+ TW.Clusters[Type][ClustType][ClustID] )
var ColorDiv = '<span style="background:'+Color+';"></span>'
LegendDiv += '<li onclick=\'SomeEffect("'+IDx+'")\'>'+ColorDiv+ IDx+"</li>"+"\n"
}
// usually 'terms' or anything in categories[0]
var curType = getCurrentType()
// all infos in a bin array
var legendInfo = []
// sample node color
var ClustNB_CurrentColor = {}
// passed as arg or prepared in parseCustom
if (!groupedByTicks && !TW.Clusters[curType][daclass]) {
console.error('class not prepared ??', daclass)
}
else {
var legendInfo = groupedByTicks || TW.Clusters[curType][daclass]
// valueclasses (values or intervals or classes) are already sorted in TW.Clusters
for (var l in legendInfo) {
// get a sample node color for each bin/class
var nMatchedNodes = legendInfo[l]['nids'].length
var midNid = legendInfo[l]['nids'][Math.floor(3*nMatchedNodes/4)]
var exampleColor = TW.partialGraph.graph.nodes(midNid).color
// create the legend item
var preparedLabel = legendInfo[l]['labl']
// console.log("preparedLabel", preparedLabel)
// all-in-one argument for SomeEffect
var valueclassId = `${curType}::${daclass}::${l}`
var colorBg = `<span style="background:${exampleColor};"></span>`
LegendDiv += `<li onclick='SomeEffect("${valueclassId}")'>`
LegendDiv += colorBg + preparedLabel
LegendDiv += "</li>\n"
}
}
LegendDiv += ' </ul>'
LegendDiv += ' </div>'
$("#legend_for_clusters").addClass( "my-legend" );
$("#legend_for_clusters").html( LegendDiv )
}
......
......@@ -49,7 +49,7 @@
z-index: 5;
/* width: we set it equal or smaller than #lefttopbox width */
width:18%;
max-height: 40%;
max-height: 25%;
overflow-y:scroll;
bottom:5px;
left:5px;
......
......@@ -67,8 +67,8 @@ html, body {
#twbrand {
font-family: "Gurajada" ;
font-size: 28px;
font-family: "Gurajada", "Droid Sans", sans-serif ;
font-size: 24px;
width: 50px !important;
}
......
......@@ -118,6 +118,14 @@ TW.overSampling = true // costly hi-def rendering (true => pixelRatio x 2)
TW.deselectOnclickStage = true // will a click on the background remove selection ? (except when dragging)
var showLabelsIfZoom=1.0;
// for continuous attrvalues/colors (cf. clustersBy), how many levels in legend?
TW.legendsBins = 7 ;
// some specific attributes may have other number of levels
TW.customLegendsBins = {
'age': 8,
'growth_rate': 12
}
// ============ < / DEVELOPER OPTIONS > ============
......@@ -147,7 +155,8 @@ var sigmaJsDrawingProperties = {
twNodeRendBorderColor: "#222",
// twNodeRendBorderColor: "#eee",
font: "Crete Round",
font: "Droid Sans",
// font: "Crete Round",
// font: "Ubuntu Condensed",
fontStyle: "bold",
};
......@@ -210,17 +219,22 @@ TW.Nodes = [];
TW.Edges = [];
TW.Clusters = [];
// new dev properties
TW.scanClusters = true // build TW.Clusters in an (attr+val => nodes) reverse index (aka facets)
TW.maxDiscreteValues = 40 // max discrete levels in facet legend (aka bins)
// new TW.Clusters structure
// --------------------------
// was: built in separate loop from read of all attr values
// TW.Clusters[nodeType][clusterType][possibleValue] = clst_idx_of_possible_value
// from now on (WIP):
// built in parseCustom (when reading all nodes attributes anyway)
// if discrete attrvalues with < 15 classes (colorsBy, clustersBy)
// => TW.Clusters[nodeType][clusterType][possibleValue] = number of nodes with this value
// if continuous or many possible values (clustersBy, colorsRelByBins)
// => TW.Clusters[nodeType][clusterType][interval] = number of nodes in interval
// from now on:
// still built in ChangeGraphAppearanceByAtt
// POSS: build in parseCustom (when reading all nodes attributes anyway)
// if discrete attrvalues with <= 30 classes (colorsBy, clustersBy)
// => TW.Clusters[nodeType][clusterType].classes.[possibleValue] = list of ids with the value
// if continuous or many possible values (>30) (clustersBy, colorsRelByBins)
// => TW.Clusters[nodeType][clusterType].ranges.[interval] = list of ids in the interval
var nodeslength=0;
......
......@@ -34,6 +34,7 @@ function changeType() {
// types eg [true] <=> '1'
// [true, true] <=> '1|1'
// [true, false] <=> '1|0'
// Complement of the received state ~[X\Y] )
var type_t1 = []
......
......@@ -554,12 +554,9 @@ if(RES["OK"]) {
// }
}
try {
ChangeGraphAppearanceByAtt(true)
}
catch (e) {
console.error(e)
}
// TEST new strategy: TW.Clusters were prepared in parseCustom
changeGraphAppearanceByFacets(true)
// set the default legend
set_ClustersLegend ( "clust_default" )
......
......@@ -83,6 +83,27 @@ function cancelSelection (fromTagCloud, settings) {
}
}
function getCurrentType() {
// type grammar overly complicated: it's absurd to have to do 10 lines
// to retrieve the tina type when other times
// there's so many window-scoped vars !!!
// TODO expose current type more accessibly
let currentTypeName
let currentTypeIdx
let typeIdxs = Object.keys(TW.partialGraph.states.slice(-1)[0].type)
for (var m in typeIdxs) {
if (TW.partialGraph.states.slice(-1)[0].type[m]) {
currentTypeIdx = m
break
}
}
currentTypeName = window.categories[currentTypeIdx]
return currentTypeName
}
function highlightSelectedNodes(flag){
console.log("\t***methods.js:highlightSelectedNodes(flag)"+flag+" selEmpty:"+is_empty(selections))
if(!is_empty(selections)){
......
......@@ -151,8 +151,8 @@ function gexfCheckAttributesMap (someXMLContent) {
}
} //out: nodesAttributes Array
console.debug('>>> tr: nodesAttributes', nodesAttributes)
console.debug('>>> tr: edgesAttributes', edgesAttributes)
// console.debug('>>> tr: nodesAttributes', nodesAttributes)
// console.debug('>>> tr: edgesAttributes', edgesAttributes)
return {nAttrs: nodesAttributes, eAttrs: edgesAttributes}
}
......@@ -254,15 +254,9 @@ function dictfyGexf( gexf , categories ){
nodes2={}, bipartiteD2N={}, bipartiteN2D={}
}
// --------------------8<-----------------------
// HERE REMOVED XML <attributes> parsing
// b/c a priori useless at this point ?
// (moved earlier to scanGexf)
//
// var atts = gexfCheckAttributesMap(gexf)
// var nodesAttributes = atts.nAttrs
// var edgesAttributes = atts.eAttrs
// --------------------8<-----------------------
var declaredAtts = gexfCheckAttributesMap(gexf)
var nodesAttributes = declaredAtts.nAttrs
// var edgesAttributes = declaredAtts.eAttrs
var elsNodes = gexf.getElementsByTagName('nodes') // The list of xml nodes 'nodes' (plural)
labels = [];
......@@ -276,6 +270,13 @@ function dictfyGexf( gexf , categories ){
// let sumSizes = 0
// let sizeStats = {'mean':null, 'median':null, 'max':0, 'min':1000000000}
// if scanClusters, we'll also use:
var tmpVals = {} // to build reverse index attval => nodes
// (to inventory subclasses for a given attr)
// if < maxDiscreteValues: keep all in legend
// else: show intervals in legend
var Atts_2_Exclude = {} // to exclude strings that don't convert to number
// usually there is only 1 <nodes> element...
for(i=0; i<elsNodes.length; i++) {
var elNodes = elsNodes[i]; // Each xml element 'nodes' (plural)
......@@ -287,9 +288,9 @@ function dictfyGexf( gexf , categories ){
// window.NODE = elNode;
if (j == 0) {
console.debug('>>> tr: XML nodes/node (1 of'+elsNode.length+')', elNodes)
}
// if (j == 0) {
// console.debug('>>> tr: XML nodes/node (1 of'+elsNode.length+')', elNodes)
// }
// [ get ID ]
var id = elNode.getAttribute('id');
......@@ -359,18 +360,18 @@ function dictfyGexf( gexf , categories ){
var attr = attvalueNode.getAttribute('for');
var val = attvalueNode.getAttribute('value');
// POSS here check nodesAttributes from scanGexf
if(catDict[val]) atts["category"] = val;
if(nodesAttributes[attr]) attr = atts[nodesAttributes[attr]]=val
else atts[attr]=val;
attributes = atts;
}
node.attributes = atts;
// nodew=parseInt(attributes["weight"]);
if ( attributes["category"] ) {
node_cat = attributes["category"];
if ( atts["category"] ) {
node_cat = atts["category"];
}
else {
node_cat = 0 // basic TW node type is 0 (~ terms)
// basic TW type idx is 0 (~ terms if one type, doc if both types)
node_cat = categories[0]
}
node.type = node_cat;
......@@ -380,7 +381,10 @@ function dictfyGexf( gexf , categories ){
// node.id = (node_cat==categories[0])? ("D:"+node.id) : ("N:"+node.id);
if(!node.size) console.log("node without size: "+node.id+" : "+node.label);
node.attributes = attributes;
// user-indicated default => copy for old default accessors
if (node.attributes[TW.nodeClusAtt]) {
node.attributes['clust_default'] = node.attributes[TW.nodeClusAtt]
}
// save record
nodes[node.id] = node
......@@ -391,10 +395,33 @@ function dictfyGexf( gexf , categories ){
if(parseInt(node.size) > parseInt(maxNodeSize))
maxNodeSize= node.size;
}
// console.debug("node.attributes", node.attributes)
if (TW.scanClusters) {
if (!tmpVals[node_cat]) tmpVals[node_cat]={}
for (var at in node.attributes) {
if (!tmpVals[node_cat][at]) tmpVals[node_cat][at]={'vals':[],'map':{}}
let someval = Number(node.attributes[at])
// Identifying the attribute datatype: exclude strings and objects
if ( isNaN(someval) ) {
if (!Atts_2_Exclude[at]) Atts_2_Exclude[at]=true;
}
// numeric attr => build facets
else {
if (!tmpVals[node_cat][at].map[someval]) tmpVals[node_cat][at].map[someval] = []
tmpVals[node_cat][at].vals.push(someval) // for ordered scale
tmpVals[node_cat][at].map[someval].push(node.id) // reverse index
}
}
}
} // finish nodes loop
}
// console.warn ('parseCustom output nodes', nodes)
// console.warn ('parseCustom reverse index: vals to ids', tmpVals)
// -------------- debug: for local stats ----------------
// allSizes.sort();
......@@ -406,51 +433,198 @@ function dictfyGexf( gexf , categories ){
// ------------- /debug: for local stats ----------------
console.warn('---> dictfyGexf <---\n, begin TW.Clusters :', TW.Clusters )
var classvalues_deb = performance.now()
// console.log('dictfyGexf: begin TW.Clusters')
var gotClusters = false
if( TW.Clusters.length == 0 ) {
for( var i in nodes ) {
if( nodes[i].attributes["cluster_index"] || nodes[i].attributes[TW.nodeClusAtt] ) {
gotClusters = true;
for (var nodecat in tmpVals) {
gotClusters = gotClusters || (tmpVals[nodecat]['cluster_index'] || tmpVals[nodecat][TW.nodeClusAtt])
}
// clusters and other facets => type => name => [{label,val/range,nodeids}]
TW.Clusters = {}
// sorting out properties in n.attributes
// --------------------------------------
if(gotClusters) {
// 1) default cluster properties "cluster_index" [, "cluster_label"]
for (var t_type in tmpVals) {
var t_clusname
// all distinct values to create labels
var t_cnumbers = []
var allTicks = []
if (TW.nodeClusAtt != undefined && tmpVals[t_type][TW.nodeClusAtt]) {
t_clusname = TW.nodeClusAtt
}
else if (tmpVals[t_type]["cluster_index"]) {
t_clusname = "cluster_index"
}
if (t_clusname) {
// values (we assume they are cluster numbers)
t_cnumbers = Object.keys(tmpVals[t_type][t_clusname].map)
// add label names (TODO use cluster_label if present
// £POSS, use maxsize node label if absent)
for (var l in t_cnumbers) {
var t_cnumber = t_cnumbers[l]
var newTick = {
'labl': `${t_type}||${t_clusname}||${t_cnumber}`,
'val': t_cnumber,
// val2ids: [nid5,nid27..]
'nids': tmpVals[t_type][TW.nodeClusAtt].map[t_cnumber]
}
break
allTicks.push(newTick)
}
TW.Clusters[t_type] = {}
TW.Clusters[t_type]["clust_default"] = allTicks
}
}
}
TW.Clusters = {}
//New scale for node size: now, between 2 and 5 instead [1,70]
for(var it in nodes){
console.log("dictfyGexf node", it)
nodes[it].size = desirableNodeSizeMIN+ (parseInt(nodes[it].size)-1)*((desirableNodeSizeMAX-desirableNodeSizeMIN) / (maxNodeSize-minNodeSize));
if(gotClusters) {
console.warn('---> writing cluster labels <---')
var t_type = nodes[it].type
var t_cnumber
if (TW.nodeClusAtt != undefined) {
t_cnumber = nodes[it].attributes[TW.nodeClusAtt]
// 2) all scanned
for (var cat in tmpVals) {
if (!TW.Clusters[cat]) TW.Clusters[cat] = {}
for (var at in tmpVals[cat]) {
// console.log(`======= ${cat}::${at} =======`)
var allTicks = []
// skip non-numeric or already done
if (Atts_2_Exclude[at] || at == "clust_default") {
continue
}
// array of valueclass/interval/bin objects
TW.Clusters[cat][at] = []
// if n possible values doesn't need binify
if (Object.keys(tmpVals[cat][at].map).length <= TW.maxDiscreteValues) {
for (var pval in tmpVals[cat][at].map) {
TW.Clusters[cat][at].push({
'labl': `${cat}||${at}||${pval}`,
'val': pval,
// val2ids
'nids': tmpVals[cat][at].map[pval]
})
}
}
// if binify
else {
var len = tmpVals[cat][at].vals.length
// sort out vals
tmpVals[cat][at].vals.sort(function (a,b) {
return Number(a)-Number(b)
})
// (enhanced intervalsInventory)
// => creates bin, binlabels, reverse index per bins
var legendRefTicks = []
// how many bins for this attribute ?
var nBins = 3
if (TW.customLegendsBins && TW.customLegendsBins[at]) {
nBins = TW.customLegendsBins[at]
}
else if (TW.legendsBins) {
nBins = TW.legendsBins
}
// create tick thresholds
for (var l=0 ; l < nBins ; l++) {
let nthVal = Math.floor(len * l / nBins)
legendRefTicks.push(tmpVals[cat][at].vals[nthVal])
}
console.debug("intervals for", at, legendRefTicks)
var nTicks = legendRefTicks.length
var sortedDistinctVals = Object.keys(tmpVals[cat][at].map).sort(function(a,b){return Number(a)-Number(b)})
var nDistinctVals = sortedDistinctVals.length
var lastCursor = 0
// create ticks objects with retrieved full info
for (var l in legendRefTicks) {
l = Number(l)
let lowThres = Number(legendRefTicks[l])
let hiThres = null
if (l < nTicks-1) {
hiThres = Number(legendRefTicks[l+1])
}
else {
t_cnumber = nodes[it].attributes["cluster_index"]
hiThres = Infinity
}
nodes[it].attributes["clust_default"] = t_cnumber;
var t_label = (nodes[it].attributes["cluster_label"])?nodes[it].attributes["cluster_label"]:"cluster_"+t_cnumber
if(!TW.Clusters[t_type]) {
TW.Clusters[t_type] = {}
TW.Clusters[t_type]["clust_default"] = {}
var newTick = {
'labl':'',
'nids':[],
'range':[lowThres, hiThres]
}
TW.Clusters[t_type]["clust_default"][t_cnumber] = t_label
// 1) union of idmaps
for (var k = lastCursor ; k <= nDistinctVals ; k++) {
var val = Number(sortedDistinctVals[k])
if (val < lowThres) {
console.error("mixup !!", val, lowThres, at)
}
else if ((val >= lowThres) && (val < hiThres)) {
if (!tmpVals[cat][at].map[val]) {
console.error("unscanned val2ids mapping", val, at)
}
else {
// eg bin2ids map for 2 <= val < 3
// will be U(val2ids maps for 2, 2.1, 2.2,...,2.9)
for (var j in tmpVals[cat][at].map[val]) {
newTick.nids.push(tmpVals[cat][at].map[val][j])
}
}
}
// we're over the interval upper bound
// we just need to remember where we were for next interval
else if (val >= hiThres) {
lastCursor = k
break
}
}
// create label
// round %.6f for display
var labLowThres = Math.round(lowThres*1000000)/1000000
var labHiThres = (l==nTicks-1)? '+ ∞' : Math.round(hiThres*1000000)/1000000
newTick.labl = `${cat}||${at}||[${labLowThres} ; ${labHiThres}]`
// save these bins as the cluster index (aka faceting)
TW.Clusters[cat][at].push(newTick)
}
}
// TW.partialGraph._core.graph.nodesIndex[it].size=Nodes[it].size;
}
}
console.warn('---> dictfyGexf <---\n, after TW.Clusters :', TW.Clusters )
var classvalues_fin = performance.now()
console.log('dictfyGexf: end TW.Clusters, own time:', classvalues_fin-classvalues_deb)
//New scale for node size: now, between 2 and 5 instead [1,70]
for(var nid in nodes){
// console.log("dictfyGexf node", nid)
nodes[nid].size = desirableNodeSizeMIN+ (parseInt(nodes[nid].size)-1)*((desirableNodeSizeMAX-desirableNodeSizeMIN) / (maxNodeSize-minNodeSize));
}
var edgeId = 0;
var edgesNodes = gexf.getElementsByTagName('edges');
for(i=0; i<edgesNodes.length; i++) {
var edgesNode = edgesNodes[i];
var edgeNodes = edgesNode.getElementsByTagName('edge');
console.log("edgeNodes.length", edgeNodes.length)
for(j=0; j<edgeNodes.length; j++) {
var edgeNode = edgeNodes[j];
var source = parseInt( edgeNode.getAttribute('source') );
......
......@@ -691,6 +691,7 @@ function getArrSubkeys(arr,id) {
return result;
}
function clustersBy(daclass) {
cancelSelection(false);
......@@ -716,7 +717,6 @@ function clustersBy(daclass) {
var real_min = 1000000;
var real_max = -1;
var themult = Math.pow(10,min_pow);
// console.log('themult', themult)
for(var j in TW.nodeIds) {
......@@ -731,26 +731,28 @@ function clustersBy(daclass) {
if (round_number>real_max) real_max = round_number;
}
console.log("NodeID_Val", NodeID_Val)
// console.log("NodeID_Val", NodeID_Val)
console.log(" - - - - - - - - -- - - ")
console.log(real_min)
console.log(real_max)
console.log("10^"+min_pow)
console.log("the mult: "+themult)
console.log(" - - - - - - - - -- - - ")
// console.log(" - - - - - - - - -- - - ")
// console.log(real_min)
// console.log(real_max)
// console.log("10^"+min_pow)
// console.log("the mult: "+themult)
// console.log(" - - - - - - - - -- - - ")
// [ Scaling node colours(0-255) and sizes(3-5) ]
// [ Scaling node colours(0-255) and sizes(2-7) ]
var Min_color = 0;
var Max_color = 255;
var Min_size = 2;
var Max_size= 6;
var Min_size = 1;
var Max_size= 8;
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 hex_color = rgbToHex(255, (255-newval_color) , 0)
TW.partialGraph.graph.nodes(nid).color = hex_color
TW.partialGraph.graph.nodes(nid).customAttrs.alt_color = hex_color
// FIXME not used ?
TW.partialGraph.graph.nodes(nid).customAttrs.altgrey_color = false
var newval_size = Math.round( ( Min_size+(NodeID_Val[nid]["round"]-real_min)*((Max_size-Min_size)/(real_max-real_min)) ) );
......@@ -759,19 +761,19 @@ function clustersBy(daclass) {
TW.partialGraph.graph.nodes(nid).label = "("+NodeID_Val[nid]["real"].toFixed(min_pow)+") "+TW.Nodes[nid].label
}
// [ / Scaling node colours(0-255) and sizes(3-5) ]
// [ / Scaling node colours(0-255) and sizes(2-7) ]
// Edge precompute alt_rgb by new source-target nodes-colours combination
repaintEdges()
set_ClustersLegend ( daclass )
// NB legend will group different possible values using
// precomputed ticks from TW.Clusters.terms[daclass]
set_ClustersLegend ( daclass)
TW.partialGraph.render();
}
// for debug of colorsRelByBins
var totalsPerBinMin = {}
// Edge-colour: precompute alt_rgb by source-target node.alt_color combination
function repaintEdges() {
......@@ -799,8 +801,113 @@ function repaintEdges() {
}
// rewrite of clustersBy with binning and for attributes that can have negative float values
// /!\ age and growth_rate attributes referred to by name
// NB - binning is done at parseCustom
// - number of bins can be specified by attribute name in TW.customLegendsBins
function colorsRelByBins(daclass) {
var binColors
var doModifyLabel = false
var ty = getCurrentType()
// our binning
var tickThresholds = TW.Clusters[ty][daclass]
// for debug of colorsRelByBins
var totalsPerBinMin = {}
// let's go
cancelSelection(false);
// global flag
TW.handpickedcolor = true
if (daclass == 'age') {
// 9 colors
binColors = [
"#F9F588",//epsilon
"#f9f008", //yel
"#f9da08",
"#fab207",
"#fa9607",
"#fa6e07",
"#fa4607",
"#ff0000" // red binMin 125
];
}
else if (daclass == 'growth_rate') {
doModifyLabel = true
// 12 colors
binColors = [
"#005197", //blue binMin -∞
"#3c76fb", // binMin -75
"#5c8af2", // binMin -50
"#64c5f2", // binMin -25
"#F9F7ED",//epsilon binMin -15
"#bae64f", // binMin 15
"#f9f008", // binMin 25
"#fab207", // binMin 50
"#fa9607", // binMin 75
"#fa6e07", // binMin 100
"#fa4607", // red binMin 125
"#991B1E" // binMin 150
];
}
// verification
if (tickThresholds.length != binColors.length) {
console.warn (`colorsRelByBins setup mismatch: TW.Clusters ticks ${tickThresholds} should == nColors ${binColors.length}`)
}
// use our valueclass => ids mapping
for (var k in tickThresholds) {
// console.debug('tick infos', tickThresholds[k])
// 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
var originalLabel = TW.Nodes[n.id].label
if (doModifyLabel) {
var valSt = n.attributes[daclass]
n.label = `(${valSt}) ${originalLabel}`
}
else {
n.label = originalLabel
}
}
}
// 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();
}
// KEPT FOR REFERENCE, BINNING NOW PRECOMPUTED in parseCustom
// rewrite of clustersBy 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
......@@ -808,6 +915,10 @@ function colorsRelByBins(daclass) {
TW.handpickedcolor = true
// for debug of colorsRelByBins
var totalsPerBinMin = {}
// should be = binColors.length
var nTicksParam = (daclass == 'age') ? 8 : 12
// do first loop entirely to get percentiles => bins, then modify alt_color
......
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