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 ) { ...@@ -264,74 +264,73 @@ function set_ClustersLegend ( daclass, groupedByTicks ) {
// get a sample node color for each bin/class // get a sample node color for each bin/class
var nMatchedNodes = legendInfo[l]['nids'].length var nMatchedNodes = legendInfo[l]['nids'].length
if (nMatchedNodes) { let theColor = legendInfo[l].col || "#111" // black if empty
var midNid = legendInfo[l]['nids'][Math.floor(3*nMatchedNodes/4)]
var exampleColor
if (TW.gui.handpickedcolor) { // create the legend item
exampleColor = TW.partialGraph.graph.nodes(midNid).customAttrs.alt_color var preparedLabel = legendInfo[l]['labl']
if (preparedLabel == '_non_numeric_') {
if (!nMatchedNodes) {
continue // we skip "trash" category if empty
} }
else { else {
exampleColor = TW.partialGraph.graph.nodes(midNid).color preparedLabel = "not numeric"
} }
}
// create the legend item // we add a title to cluster classes by ranking their nodes and taking k best labels
var preparedLabel = legendInfo[l]['labl'] 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 titles = []
let theRankingAttr = TW.conf.facetOptions[daclass].titlingMetric let theRankingAttr = TW.conf.facetOptions[daclass].titlingMetric
let maxLen = TW.conf.facetOptions[daclass].titlingNTerms || 2 let maxLen = TW.conf.facetOptions[daclass].titlingNTerms || 2
// custom accessor (user settings or by default) // custom accessor (user settings or by default)
let getVal let getVal
if(theRankingAttr) { if(theRankingAttr) {
getVal = function(node) {return node.attributes[theRankingAttr]} getVal = function(node) {return node.attributes[theRankingAttr]}
} }
else { else {
// default ranking: by size // default ranking: by size
getVal = function(node) {return node.size} getVal = function(node) {return node.size}
} }
for (let j in legendInfo[l]['nids']) { for (let j in legendInfo[l]['nids']) {
let n = TW.partialGraph.graph.nodes(legendInfo[l]['nids'][j]) 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}) 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 titles.sort(function(a,b) {return b.val - a.val})
preparedLabel += " ["+titles.map(function(x){return x.key}).join('/')+"...]" titles = titles.slice(0,maxLen)
// console.log("finding title perf", performance.now() - t0, titles)
} }
// all-in-one argument for SomeEffect // adding those k best titles to the legend
var valueclassId = `${curType}::${daclass}::${l}` preparedLabel += " ["+titles.map(function(x){return x.key}).join('/')+"...]"
var colorBg = `<span style="background:${exampleColor};"></span>`
LegendDiv += `<li onclick='SomeEffect("${valueclassId}")'>` // console.log("finding title perf", performance.now() - t0, titles)
LegendDiv += colorBg + preparedLabel
LegendDiv += "</li>\n"
} }
// 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 += ' </ul>'
LegendDiv += ' </div>' LegendDiv += ' </div>'
......
...@@ -48,6 +48,7 @@ ...@@ -48,6 +48,7 @@
color:#250587; color:#250587;
margin: 7px; margin: 7px;
font-size:120%; font-size:120%;
cursor: default;
} }
.my-legend .legend-title { .my-legend .legend-title {
...@@ -70,7 +71,7 @@ ...@@ -70,7 +71,7 @@
line-height: 16px; line-height: 16px;
margin-bottom: 2px; margin-bottom: 2px;
} }
.my-legend ul.legend-labels li span { .my-legend ul.legend-labels li span.lgdcol {
display: block; display: block;
float: left; float: left;
height: 16px; height: 16px;
...@@ -79,6 +80,9 @@ ...@@ -79,6 +80,9 @@
margin-left: 0; margin-left: 0;
border: 1px solid #999; border: 1px solid #999;
} }
.my-legend span.thresh {
display: inline;
}
.my-legend .legend-source { .my-legend .legend-source {
font-size: 70%; font-size: 70%;
color: #999; color: #999;
...@@ -89,6 +93,7 @@ ...@@ -89,6 +93,7 @@
} }
/* ZOOMBAR */ /* ZOOMBAR */
#ctlzoom { #ctlzoom {
...@@ -322,7 +327,7 @@ ...@@ -322,7 +327,7 @@
margin-bottom: 0; margin-bottom: 0;
} }
.my-legend ul.legend-labels li span { .my-legend ul.legend-labels li span.lgdcol {
height: 11px; height: 11px;
width: 11px; width: 11px;
border: 1px solid #999; border: 1px solid #999;
......
...@@ -416,6 +416,8 @@ function facetsBinning (valuesIdx) { ...@@ -416,6 +416,8 @@ function facetsBinning (valuesIdx) {
} }
// NB these ticks are *minimums* so we stop one step *before* vMax // NB these ticks are *minimums* so we stop one step *before* vMax
// and simply include it in last interval // 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') { else if (binningMode == 'samepop') {
...@@ -545,13 +547,11 @@ function facetsBinning (valuesIdx) { ...@@ -545,13 +547,11 @@ function facetsBinning (valuesIdx) {
bracket = ']' bracket = ']'
} }
newTick.labl = `[${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}||[${labLowThres} ; ${labHiThres}${bracket} (${newTick.nids.length})` // newTick.fullLabl = `${cat}||${at}||[${lowThres} ; ${hiThres}${bracket} (${newTick.nids.length})`
// save these bins as the cluster index (aka faceting) // faceting: save these bins as the cluster index (even if empty)
if (newTick.nids.length) { facetIdx[cat][at].invIdx.push(newTick)
facetIdx[cat][at].invIdx.push(newTick)
}
} }
// finally add the 'trash' category with any non_numeric vals // finally add the 'trash' category with any non_numeric vals
......
...@@ -645,9 +645,8 @@ function edgeInfos(anEdge) { ...@@ -645,9 +645,8 @@ function edgeInfos(anEdge) {
function gradientColoring(daclass) { function gradientColoring(daclass) {
// £TODO group as an option of cancelSelection to avoid 2 loops cancelSelection(false); // loops only on selected
cancelSelection(false); graphResetLabelsAndSizes() // full loop
graphResetLabelsAndSizes()
TW.gui.handpickedcolor = true TW.gui.handpickedcolor = true
...@@ -672,8 +671,6 @@ function gradientColoring(daclass) { ...@@ -672,8 +671,6 @@ 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)
// £TODO should use TW.Clusters here
for(var j in TW.nodeIds) { for(var j in TW.nodeIds) {
var the_node = TW.Nodes[ TW.nodeIds[j] ] var the_node = TW.Nodes[ TW.nodeIds[j] ]
var attval = the_node.attributes[daclass]; var attval = the_node.attributes[daclass];
...@@ -732,6 +729,28 @@ function gradientColoring(daclass) { ...@@ -732,6 +729,28 @@ 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
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 // NB legend will group different possible values using
// precomputed ticks from TW.Clusters.terms[daclass] // precomputed ticks from TW.Clusters.terms[daclass]
set_ClustersLegend ( daclass) set_ClustersLegend ( daclass)
...@@ -814,9 +833,8 @@ function heatmapColoring(daclass) { ...@@ -814,9 +833,8 @@ function heatmapColoring(daclass) {
binColors = getHeatmapColors(nColors) binColors = getHeatmapColors(nColors)
// let's go // let's go
// £TODO group as an option of cancelSelection to avoid 2 loops cancelSelection(false); // loops only on selected
cancelSelection(false); graphResetLabelsAndSizes() // full loop
graphResetLabelsAndSizes()
// global flag // global flag
TW.gui.handpickedcolor = true TW.gui.handpickedcolor = true
...@@ -825,31 +843,39 @@ function heatmapColoring(daclass) { ...@@ -825,31 +843,39 @@ function heatmapColoring(daclass) {
for (var k in tickThresholds) { for (var k in tickThresholds) {
// console.debug('tick infos', tickThresholds[k]) // 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 // skip grouped NaN values case => grey
if (tickThresholds[k].labl == '_non_numeric_') { 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]} if (tickThresholds[k].nids.length) {
let rgbColStr = hex2rgba(binColors[k]).slice(0,3).join(',')
// color the referred nodes
for (var j in tickThresholds[k].nids) {
let n = TW.partialGraph.graph.nodes(tickThresholds[k].nids[j])
n.customAttrs.alt_color = binColors[k] // color the referred nodes
n.customAttrs.altgrey_color = "rgba("+(hex2rgba(binColors[k]).slice(0,3).join(','))+",0.4)" 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 var originalLabel = TW.Nodes[n.id].label
if (doModifyLabel) { if (doModifyLabel) {
var valSt = n.attributes[daclass] var valSt = n.attributes[daclass]
n.label = `(${valSt}) ${originalLabel}` n.label = `(${valSt}) ${originalLabel}`
} }
else { }
n.label = originalLabel
} }
} }
// remember
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
...@@ -871,19 +897,18 @@ function clusterColoring(daclass) { ...@@ -871,19 +897,18 @@ function clusterColoring(daclass) {
console.log(" = = = = = = = = = = = = = = = = = ") console.log(" = = = = = = = = = = = = = = = = = ")
console.log("") console.log("")
// £TODO group as an option of cancelSelection to avoid 2 loops cancelSelection(false); // now loops only on selected
cancelSelection(false); graphResetLabelsAndSizes() // full loop
graphResetLabelsAndSizes()
// louvain needs preparation // louvain needs preparation
if(daclass=="clust_louvain") { if(daclass=="clust_louvain") {
if(!TW.states.slice(-1)[0].LouvainFait) { if(!TW.SystemState().LouvainFait) {
try { try {
RunLouvain() RunLouvain()
TW.states.slice(-1)[0].LouvainFait = true TW.SystemState().LouvainFait = true
} }
catch(e) { catch(e) {
TW.states.slice(-1)[0].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'
} }
...@@ -915,35 +940,71 @@ function clusterColoring(daclass) { ...@@ -915,35 +940,71 @@ function clusterColoring(daclass) {
let nColors = TW.gui.colorList.length 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 if (valGroup.nids.length) {
// then this condition would be more useful here let rgbColStr = hex2rgba(theColor).slice(0,3).join(',')
if (! the_node.hidden) { for (let j in valGroup.nids) {
var attval = ( !isUndef(the_node.attributes) && !isUndef(the_node.attributes[daclass]) )? the_node.attributes[daclass] : TW.partialGraph.graph.nodes(TW.nodeIds[j])[daclass]; 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_') { if (the_node) {
theColor = '#bbb'
} // POSS: use "hidden" in filters instead of remove/readd
else if (! isNaN(parseInt(attval))) { // then this condition would be more useful here
theColor = colList[ attval ] 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];
else {
let someRepresentativeInt = stringToSomeInt(attval) % nColors let theColor
theColor = colList[ someRepresentativeInt ]
}
// TW.partialGraph.graph.nodes(TW.nodeIds[j]).color = theColor if (attval == '_non_numeric_') {
the_node.customAttrs.alt_color = theColor theColor = '#bbb'
the_node.customAttrs.altgrey_color = "rgba("+(hex2rgba(theColor).slice(0,3).join(','))+",0.4)" }
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 // set the global state
TW.gui.handpickedcolor = true 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