Commit 028ce16c authored by Romain Loth's avatar Romain Loth

optimize coloring legends

display empty bins if samerange, show unrounded values on range thresh hover, normalize empty bin treatment, write exemplar colors directly in TW.Clusters etc
parent 1d4e221c
......@@ -264,74 +264,73 @@ function set_ClustersLegend ( daclass, groupedByTicks ) {
// get a sample node color for each bin/class
var nMatchedNodes = legendInfo[l]['nids'].length
if (nMatchedNodes) {
var midNid = legendInfo[l]['nids'][Math.floor(3*nMatchedNodes/4)]
var exampleColor
let theColor = legendInfo[l].col || "#111" // black if empty
if (TW.gui.handpickedcolor) {
exampleColor = TW.partialGraph.graph.nodes(midNid).customAttrs.alt_color
// create the legend item
var preparedLabel = legendInfo[l]['labl']
if (preparedLabel == '_non_numeric_') {
if (!nMatchedNodes) {
continue // we skip "trash" category if empty
}
else {
exampleColor = TW.partialGraph.graph.nodes(midNid).color
preparedLabel = "not numeric"
}
}
// create the legend item
var preparedLabel = legendInfo[l]['labl']
// we add a title to cluster classes by ranking their nodes and taking k best labels
if (TW.conf.facetOptions[daclass] && TW.conf.facetOptions[daclass].col == 'cluster') {
// we add a title to cluster classes by ranking their nodes and taking k best labels
if (TW.conf.facetOptions[daclass] && TW.conf.facetOptions[daclass].col == 'cluster') {
// let t0 = performance.now()
// let t0 = performance.now()
let titles = []
let theRankingAttr = TW.conf.facetOptions[daclass].titlingMetric
let maxLen = TW.conf.facetOptions[daclass].titlingNTerms || 2
let titles = []
let theRankingAttr = TW.conf.facetOptions[daclass].titlingMetric
let maxLen = TW.conf.facetOptions[daclass].titlingNTerms || 2
// custom accessor (user settings or by default)
let getVal
if(theRankingAttr) {
getVal = function(node) {return node.attributes[theRankingAttr]}
}
else {
// default ranking: by size
getVal = function(node) {return node.size}
}
// custom accessor (user settings or by default)
let getVal
if(theRankingAttr) {
getVal = function(node) {return node.attributes[theRankingAttr]}
}
else {
// default ranking: by size
getVal = function(node) {return node.size}
}
for (let j in legendInfo[l]['nids']) {
let n = TW.partialGraph.graph.nodes(legendInfo[l]['nids'][j])
for (let j in legendInfo[l]['nids']) {
let n = TW.partialGraph.graph.nodes(legendInfo[l]['nids'][j])
let theRankingVal = getVal(n)
let theRankingVal = getVal(n)
if (titles.length < maxLen) {
if (titles.length < maxLen) {
titles.push({'key':n.label, 'val':theRankingVal})
}
else {
// we keep titles sorted for this
let lastMax = titles.slice(-1)[0].val
if (theRankingVal > lastMax) {
titles.push({'key':n.label, 'val':theRankingVal})
}
else {
// we keep titles sorted for this
let lastMax = titles.slice(-1)[0].val
if (theRankingVal > lastMax) {
titles.push({'key':n.label, 'val':theRankingVal})
}
}
titles.sort(function(a,b) {return b.val - a.val})
titles = titles.slice(0,maxLen)
}
// adding those k best titles to the legend
preparedLabel += " ["+titles.map(function(x){return x.key}).join('/')+"...]"
// console.log("finding title perf", performance.now() - t0, titles)
titles.sort(function(a,b) {return b.val - a.val})
titles = titles.slice(0,maxLen)
}
// all-in-one argument for SomeEffect
var valueclassId = `${curType}::${daclass}::${l}`
var colorBg = `<span style="background:${exampleColor};"></span>`
// adding those k best titles to the legend
preparedLabel += " ["+titles.map(function(x){return x.key}).join('/')+"...]"
LegendDiv += `<li onclick='SomeEffect("${valueclassId}")'>`
LegendDiv += colorBg + preparedLabel
LegendDiv += "</li>\n"
// console.log("finding title perf", performance.now() - t0, titles)
}
// all-in-one argument for SomeEffect
var valueclassId = `${curType}::${daclass}::${l}`
var colorBg = `<span class="lgdcol" style="background:${theColor};"></span>`
LegendDiv += `<li onclick='SomeEffect("${valueclassId}")'>`
LegendDiv += colorBg + preparedLabel
LegendDiv += "</li>\n"
}
LegendDiv += ' </ul>'
LegendDiv += ' </div>'
......
......@@ -48,6 +48,7 @@
color:#250587;
margin: 7px;
font-size:120%;
cursor: default;
}
.my-legend .legend-title {
......@@ -70,7 +71,7 @@
line-height: 16px;
margin-bottom: 2px;
}
.my-legend ul.legend-labels li span {
.my-legend ul.legend-labels li span.lgdcol {
display: block;
float: left;
height: 16px;
......@@ -79,6 +80,9 @@
margin-left: 0;
border: 1px solid #999;
}
.my-legend span.thresh {
display: inline;
}
.my-legend .legend-source {
font-size: 70%;
color: #999;
......@@ -89,6 +93,7 @@
}
/* ZOOMBAR */
#ctlzoom {
......@@ -322,7 +327,7 @@
margin-bottom: 0;
}
.my-legend ul.legend-labels li span {
.my-legend ul.legend-labels li span.lgdcol {
height: 11px;
width: 11px;
border: 1px solid #999;
......
......@@ -416,6 +416,8 @@ function facetsBinning (valuesIdx) {
}
// NB these ticks are *minimums* so we stop one step *before* vMax
// and simply include it in last interval
// console.warn(`samerange nBins:${nBins}, n distinct:${workingVals.length} => got n ticks:${legendRefTicks.length}`)
}
else if (binningMode == 'samepop') {
......@@ -545,13 +547,11 @@ function facetsBinning (valuesIdx) {
bracket = ']'
}
newTick.labl = `[${labLowThres} ; ${labHiThres}${bracket} (${newTick.nids.length})`
newTick.fullLabl = `${cat}||${at}||[${labLowThres} ; ${labHiThres}${bracket} (${newTick.nids.length})`
newTick.labl = `[<span title="${lowThres}">${labLowThres}</span> ; <span title="${hiThres}">${labHiThres}</span>${bracket} (${newTick.nids.length})`
// newTick.fullLabl = `${cat}||${at}||[${lowThres} ; ${hiThres}${bracket} (${newTick.nids.length})`
// save these bins as the cluster index (aka faceting)
if (newTick.nids.length) {
facetIdx[cat][at].invIdx.push(newTick)
}
// faceting: save these bins as the cluster index (even if empty)
facetIdx[cat][at].invIdx.push(newTick)
}
// finally add the 'trash' category with any non_numeric vals
......
......@@ -645,9 +645,8 @@ function edgeInfos(anEdge) {
function gradientColoring(daclass) {
// £TODO group as an option of cancelSelection to avoid 2 loops
cancelSelection(false);
graphResetLabelsAndSizes()
cancelSelection(false); // loops only on selected
graphResetLabelsAndSizes() // full loop
TW.gui.handpickedcolor = true
......@@ -672,8 +671,6 @@ function gradientColoring(daclass) {
var themult = Math.pow(10,min_pow);
// console.log('themult', themult)
// £TODO should use TW.Clusters here
for(var j in TW.nodeIds) {
var the_node = TW.Nodes[ TW.nodeIds[j] ]
var attval = the_node.attributes[daclass];
......@@ -732,6 +729,28 @@ function gradientColoring(daclass) {
// Edge precompute alt_rgb by new source-target nodes-colours combination
repaintEdges()
// remember in clusters
let bins = TW.Clusters[getActivetypesNames()[0]][daclass]
if (bins && bins.invIdx) {
for (var i in bins.invIdx) {
if (bins.invIdx[i].labl != '_non_numeric_') {
let nidList = bins.invIdx[i]['nids']
if (nidList.length) {
// we take an exemplar in the range, further than middle
// (result optically more representative than with 1/2 of len)
let aNid = nidList[Math.floor(3*nidList.length/4)]
bins.invIdx[i].col = TW.partialGraph.graph.nodes(aNid).color
}
else {
bins.invIdx[i].col = "#111" // empty bin
}
}
else {
bins.invIdx[i].col = "#bbb" // non numeric values bin
}
}
}
// NB legend will group different possible values using
// precomputed ticks from TW.Clusters.terms[daclass]
set_ClustersLegend ( daclass)
......@@ -814,9 +833,8 @@ function heatmapColoring(daclass) {
binColors = getHeatmapColors(nColors)
// let's go
// £TODO group as an option of cancelSelection to avoid 2 loops
cancelSelection(false);
graphResetLabelsAndSizes()
cancelSelection(false); // loops only on selected
graphResetLabelsAndSizes() // full loop
// global flag
TW.gui.handpickedcolor = true
......@@ -825,31 +843,39 @@ function heatmapColoring(daclass) {
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]}
let theColor
// skip grouped NaN values case => grey
if (tickThresholds[k].labl == '_non_numeric_') {
continue
theColor = '#bbb'
}
else {
theColor = binColors[k]
}
// ex: {labl: "terms||growth_rate||[0 ; 0.583]", nids: Array(99), range: [0 ; 0.583210]}
// color the referred nodes
for (var j in tickThresholds[k].nids) {
let n = TW.partialGraph.graph.nodes(tickThresholds[k].nids[j])
if (tickThresholds[k].nids.length) {
let rgbColStr = hex2rgba(binColors[k]).slice(0,3).join(',')
n.customAttrs.alt_color = binColors[k]
n.customAttrs.altgrey_color = "rgba("+(hex2rgba(binColors[k]).slice(0,3).join(','))+",0.4)"
// color the referred nodes
for (var j in tickThresholds[k].nids) {
let n = TW.partialGraph.graph.nodes(tickThresholds[k].nids[j])
if (n) {
n.customAttrs.alt_color = binColors[k]
n.customAttrs.altgrey_color = "rgba("+rgbColStr+",0.4)"
var originalLabel = TW.Nodes[n.id].label
if (doModifyLabel) {
var valSt = n.attributes[daclass]
n.label = `(${valSt}) ${originalLabel}`
}
else {
n.label = originalLabel
var originalLabel = TW.Nodes[n.id].label
if (doModifyLabel) {
var valSt = n.attributes[daclass]
n.label = `(${valSt}) ${originalLabel}`
}
}
}
}
// remember
tickThresholds[k].col = theColor
}
// Edge precompute alt_rgb by new source-target nodes-colours combination
......@@ -871,19 +897,18 @@ function clusterColoring(daclass) {
console.log(" = = = = = = = = = = = = = = = = = ")
console.log("")
// £TODO group as an option of cancelSelection to avoid 2 loops
cancelSelection(false);
graphResetLabelsAndSizes()
cancelSelection(false); // now loops only on selected
graphResetLabelsAndSizes() // full loop
// louvain needs preparation
if(daclass=="clust_louvain") {
if(!TW.states.slice(-1)[0].LouvainFait) {
if(!TW.SystemState().LouvainFait) {
try {
RunLouvain()
TW.states.slice(-1)[0].LouvainFait = true
TW.SystemState().LouvainFait = true
}
catch(e) {
TW.states.slice(-1)[0].LouvainFait = false
TW.SystemState().LouvainFait = false
console.warn("skipped error on louvain, falling back to default colors")
daclass == 'clust_default'
}
......@@ -915,35 +940,71 @@ function clusterColoring(daclass) {
let nColors = TW.gui.colorList.length
for(var j in TW.nodeIds) {
var the_node = TW.partialGraph.graph.nodes(TW.nodeIds[j])
if (the_node) {
let facets = TW.Clusters[getActivetypesNames()[0]][daclass]
if (facets && facets.invIdx) {
for (var i in facets.invIdx) {
let valGroup = facets.invIdx[i]
let theColor
if (valGroup.labl == "_non_numeric_") {
theColor == '#bbb'
}
else if (valGroup.val) {
theColor = colList[ valGroup.val ]
}
else if (valGroup.range) {
let someRepresentativeInt = stringToSomeInt(valGroup.range) % nColors
theColor = colList[ someRepresentativeInt ]
}
// 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 (valGroup.nids.length) {
let rgbColStr = hex2rgba(theColor).slice(0,3).join(',')
for (let j in valGroup.nids) {
let theNode = TW.partialGraph.graph.nodes(valGroup.nids[j])
if (theNode) {
theNode.customAttrs.alt_color = theColor
theNode.customAttrs.altgrey_color = "rgba("+rgbColStr+",0.4)"
}
}
}
let theColor
// rembember in TW.Clusters
valGroup.col = theColor
}
}
// fallback on old, slower strategy if scanClusters inactive
else {
for(var j in TW.nodeIds) {
var the_node = TW.partialGraph.graph.nodes(TW.nodeIds[j])
if (attval == '_non_numeric_') {
theColor = '#bbb'
}
else if (! isNaN(parseInt(attval))) {
theColor = colList[ attval ]
}
else {
let someRepresentativeInt = stringToSomeInt(attval) % nColors
theColor = colList[ someRepresentativeInt ]
}
if (the_node) {
// 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];
let theColor
// 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)"
if (attval == '_non_numeric_') {
theColor = '#bbb'
}
else if (! isNaN(parseInt(attval))) {
theColor = colList[ attval ]
}
else {
let someRepresentativeInt = stringToSomeInt(attval) % nColors
theColor = colList[ someRepresentativeInt ]
}
// 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)"
}
}
}
}
}
// set the global state
TW.gui.handpickedcolor = true
}
......
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