Commit e5d1e4ec authored by Romain Loth's avatar Romain Loth

WIP bi colors in bipa cases

parent ce9b46c4
......@@ -159,8 +159,8 @@
<li>
<a>
<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="NGram">Keywords</option>
<option value="Scholars" selected>Scholars</option>
<option value="Keywords">Keywords</option>
</select>
</a>
</li>
......@@ -430,9 +430,9 @@
</div><!-- /row -->
</div>
<!-- class="my-legend" (absolute position bottom left) -->
<div id="legend-for-clusters" class="over-panels"></div>
<!-- attributes' legends (absolute position bottom left)
(contains one div of class "my-legend" per colored nodetype) -->
<div id="legend-for-facets" class="over-panels"></div>
<!-- to reopen the panel -->
<div id="sideunfold">
......
......@@ -21,7 +21,7 @@ TW.conf = (function(TW){
// ...or remote bridge to default source api ajax queries
TWConf.sourceAPI={}
TWConf.sourceAPI["nodetypes"] = {"node0": "NGram", "node1": "Document" }
TWConf.sourceAPI["nodetypes"] = {"node0": "Keywords", "node1": "Scholars" }
TWConf.sourceAPI["forNormalQuery"] = "services/api/graph"
TWConf.sourceAPI["forFilteredQuery"] = "services/api/graph"
......@@ -114,8 +114,8 @@ TW.conf = (function(TW){
// =============
// Node typology: categories (resp. 0 and 1) will get these default labels
TWConf.catSem = "NGram";
TWConf.catSoc = "Document";
TWConf.catSem = "Keywords";
TWConf.catSoc = "Scholars";
// NB: these labels may be superseded by:
// - the input data's node types values cf. sortNodeTypes()
// - in servermenu mode, by the node0 & node1 properties
......
......@@ -49,8 +49,8 @@
}
/* legend re-positioned */
.my-legend {
bottom: calc(40% - 105px);
#legend-for-facets {
bottom: calc(40% - 100px);
/* bot just above #sidebar which has top at calc(105px + 60%); */
}
......@@ -97,17 +97,20 @@
}
/* legend reduction */
#legend-for-facets {
max-width: 25%;
}
.my-legend {
max-width: 27%;
max-height: 25%;
padding: 0 2px;
font-size: 85%;
margin: 0 0 5px 0;
margin: 0;
padding: 0 1px 3px 1px;
}
.my-legend .legend-title {
margin-bottom: 0;
font-size: 90%;
font-size: 85%;
}
.my-legend .legend-scale ul {
margin-bottom: 0;
......
......@@ -33,44 +33,55 @@
}
/* LEGEND PANEL */
#legend-for-facets {
bottom: 10px;
left:0;
position:fixed;
/* width: we set it a bit more than #lefttopbox width */
max-width: 20%;
max-height: 40%;
font-size:120%;
cursor: default;
overflow-y:auto;
overflow-x: hidden;
}
.my-legend {
position:fixed;
/* 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;
border:solid 1px black;
background-color:white;
opacity: 0.8;
position: relative;
height: 100%;
width: 100%;
margin: 0;
padding: 0 5px 5px 5px;
opacity:1;
background-color: white;
color:#250587;
margin: 7px;
font-size:120%;
cursor: default;
overflow: hidden;
z-index: 5;
}
.my-legend .legend-title {
text-align: left;
margin-bottom: 5px;
font-weight: bold;
font-size: 90%;
}
font-size: 75%;
}
.my-legend .legend-scale ul {
margin: 0;
margin-bottom: 5px;
padding: 0;
float: left;
list-style: none;
}
}
.my-legend .legend-scale ul li {
font-size: 80%;
list-style: none;
margin-left: 0;
line-height: 16px;
margin-bottom: 2px;
}
}
.my-legend ul.legend-labels li span.lgdcol {
display: block;
float: left;
......
......@@ -23,8 +23,8 @@ TW.gui.checkBox=false;
TW.gui.shiftKey=false;
TW.gui.foldedSide=false;
TW.gui.manuallyChecked = false;
TW.gui.handpickedcolor = false; // <= changes edge rendering strategy
TW.gui.lastFilters = {}
TW.gui.handpickedcolor = {}; // <= changes rendering, by nodetype
TW.gui.lastFilters = {} // <= last values, by slider id
TW.gui.reldocTabs = [{}, {}] // <= by nodetype and then dbtype
TW.gui.sizeRatios = [1,1] // sizeRatios per nodetype
......@@ -519,7 +519,7 @@ function changeType(optionaltypeFlag) {
// update the gui (POSS could be handled by TW.pushGUIState)
TW.gui.handpickedcolor = false
TW.gui.handpickedcolor = {} // <=> none
updateDynamicFacets()
changeGraphAppearanceByFacets( getActivetypesNames() )
......
......@@ -71,7 +71,7 @@ function changeGraphAppearanceByFacets(actypes) {
let currentNbNodes = 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>';
var color_menu_info = '<li><a href="#" onclick="TW.gui.handpickedcolor = {} ; graphResetLabelsAndSizes() ; TW.partialGraph.refresh()">By Default</a></li>';
let gotPreviousLouvain = false
if( $( "#colorgraph-menu" ).length>0 ) {
......@@ -130,7 +130,7 @@ function changeGraphAppearanceByFacets(actypes) {
}
else attLabel = attTitle
color_menu_info += `<li><a href="#" onclick='${colMethod}("${attTitle}")'>By ${attLabel} (${attNbClasses} | ${attNbNodes})</a></li>`
color_menu_info += `<li><a href="#" onclick='${colMethod}("${attTitle}","${ty}")'>By ${attLabel} (${attNbClasses} | ${attNbNodes})</a></li>`
}
// POSS add cumulated degree via TW.partialGraph.graph.degree(nid)
......@@ -151,8 +151,8 @@ function changeGraphAppearanceByFacets(actypes) {
}
function RunLouvain() {
// @cb: optional callback
function RunLouvain(cb) {
var node_realdata = []
var nodesV = getVisibleNodes()
......@@ -216,6 +216,11 @@ function RunLouvain() {
TW.facetOptions['clust_louvain'] = {'col': 'cluster'}
}
// NB the LouvainFait flag is updated by caller fun
// callback
if (cb && typeof cb == 'function') {
cb()
}
}
......@@ -269,139 +274,150 @@ function graphResetLabelsAndSizes(){
n.size = TW.Nodes[n.id].size
}
}
set_ClustersLegend()
}
// @daclass: the name of a numeric/categorical attribute from node.attributes
// @forTypes: optional array of which typenames are concerned
// @groupingTicks: an optional threshold's array expressing ranges with their low/up bounds label and ref to matchin nodeIds
function set_ClustersLegend ( daclass, groupedByTicks ) {
$("#legend-for-clusters").removeClass( "my-legend" )
$("#legend-for-clusters").html("")
if(daclass==null) return;
var actypes = getActivetypesNames()
// TODO test more for multiple types
// we have no specifications yet for colors (and legends) on multiple types
if (actypes.length > 1) {
console.warn("colors by bins will only color nodes of type 0")
function set_ClustersLegend ( daclass, forTypes, groupedByTicks ) {
// shortcut to erase legends for all types
if(daclass == null) {
$("#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')
var curType = actypes[0]
// all infos in a bin array
var legendInfo = []
for (var k in forTypes) {
let curType = forTypes[k]
var LegendDiv = "<div id=legend-for-"+curType+" class=\"over-panels my-legend\">"
// sample node color
var ClustNB_CurrentColor = {}
// all infos in a bin array
var legendInfo = []
// passed as arg or prepared in parseCustom
if (!groupedByTicks && (!TW.Facets[curType] || !TW.Facets[curType][daclass])) {
console.warn(`no class bins for ${daclass}, displaying no legend`)
// sample node color
var ClustNB_CurrentColor = {}
$("#legend-for-clusters").hide()
}
else {
let daclassLabel = daclass
if (TW.facetOptions
&& TW.facetOptions[daclass]
&& TW.facetOptions[daclass].legend) {
daclassLabel = TW.facetOptions[daclass].legend
// passed as arg or prepared in parseCustom
if (!groupedByTicks && (!TW.Facets[curType] || !TW.Facets[curType][daclass])) {
console.warn(`no class bins for ${curType} ${daclass}`)
}
var LegendDiv = ""
LegendDiv += ` <div class="legend-title">${daclassLabel}</div>`
LegendDiv += ' <div class="legend-scale">'
LegendDiv += ' <ul class="legend-labels">'
else {
let daclassLabel = daclass
if (TW.facetOptions
&& TW.facetOptions[daclass]
&& TW.facetOptions[daclass].legend) {
daclassLabel = TW.facetOptions[daclass].legend
}
var legendInfo = groupedByTicks || TW.Facets[curType][daclass].invIdx
LegendDiv += ` <div class="legend-title">${curType}:${daclassLabel}</div>`
LegendDiv += ' <div class="legend-scale">'
LegendDiv += ' <ul class="legend-labels">'
// valueclasses (values or intervals or classes) are already sorted in TW.Facets
for (var l in legendInfo) {
var nMatchedNodes = legendInfo[l]['nids'].length
var legendInfo = groupedByTicks || TW.Facets[curType][daclass].invIdx
// get a sample node color for each bin/class
let theColor = legendInfo[l].col || "#777" // grey if empty
// valueclasses (values or intervals or classes) are already sorted in TW.Facets
for (var l in legendInfo) {
var nMatchedNodes = legendInfo[l]['nids'].length
// create the legend item
var preparedLabel = legendInfo[l]['labl']
// get a sample node color for each bin/class
let theColor = legendInfo[l].col || "#777" // grey if empty
if (/^_non_numeric_/.test(preparedLabel)) {
if (!nMatchedNodes) {
continue // we skip "trash" category if empty
}
else {
preparedLabel = "not numeric"
// create the legend item
var preparedLabel = legendInfo[l]['labl']
if (/^_non_numeric_/.test(preparedLabel)) {
if (!nMatchedNodes) {
continue // we skip "trash" category if empty
}
else {
preparedLabel = "not numeric"
}
}
}
// we add a title to cluster classes by ranking their nodes and taking k best labels, except if type is "social"
if (TW.facetOptions[daclass] && TW.facetOptions[daclass].col == 'cluster' && curType != TW.categories[1]) {
// we add a title to cluster classes by ranking their nodes and taking k best labels, except if type is "social"
if (TW.facetOptions[daclass] && TW.facetOptions[daclass].col == 'cluster' && curType != TW.categories[1]) {
// let t0 = performance.now()
// let t0 = performance.now()
let titles = []
let theRankingAttr = TW.facetOptions[daclass].titlingMetric
let maxLen = TW.facetOptions[daclass].titlingNTerms || 2
let titles = []
let theRankingAttr = TW.facetOptions[daclass].titlingMetric
let maxLen = TW.facetOptions[daclass].titlingNTerms || 2
// custom accessor (sigma auto attr or user settings or by default)
let getVal
if(theRankingAttr) {
// one of the 3 sigma dynamic attributes 'degree', etc
if (theRankingAttr in TW.sigmaAttributes) {
getVal = TW.sigmaAttributes[theRankingAttr](TW.partialGraph)
// custom accessor (sigma auto attr or user settings or by default)
let getVal
if(theRankingAttr) {
// one of the 3 sigma dynamic attributes 'degree', etc
if (theRankingAttr in TW.sigmaAttributes) {
getVal = TW.sigmaAttributes[theRankingAttr](TW.partialGraph)
}
// a user setting for a source data attribute
else {
getVal = function(node) {return node.attributes[theRankingAttr]}
}
}
// a user setting for a source data attribute
// default ranking: by size
else {
getVal = function(node) {return node.attributes[theRankingAttr]}
getVal = function(node) {return node.size}
}
}
// default ranking: by size
else {
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) {
titles.push({'key':n.label, 'val':theRankingVal})
}
else {
// we keep titles sorted for this
let lastMax = titles.slice(-1)[0].val
if (theRankingVal > lastMax) {
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.sort(function(a,b) {return b.val - a.val})
titles = titles.slice(0,maxLen)
}
titles.sort(function(a,b) {return b.val - a.val})
titles = titles.slice(0,maxLen)
}
// replacing the cluster numbers by those k best titles in the legend
preparedLabel = "["+titles.map(function(x){return x.key}).join(' / ')+"...]" + ` (${nMatchedNodes})`
// replacing the cluster numbers by those k best titles in the legend
preparedLabel = "["+titles.map(function(x){return x.key}).join(' / ')+"...]" + ` (${nMatchedNodes})`
// console.log("finding title perf", performance.now() - t0, titles)
}
// console.log("finding title perf", performance.now() - t0, titles)
}
// all-in-one argument for SomeEffect
var valueclassId = `${curType}::${daclass}::${l}`
// all-in-one argument for SomeEffect
var valueclassId = `${curType}::${daclass}::${l}`
var colorBg = `<span class="lgdcol" style="background:${theColor};"></span>`
var colorBg = `<span class="lgdcol" style="background:${theColor};"></span>`
LegendDiv += `<li onclick='SomeEffect("${valueclassId}")'>`
LegendDiv += colorBg + preparedLabel
LegendDiv += "</li>\n"
}
LegendDiv += ' </ul>'
LegendDiv += ' </div>'
LegendDiv += ' </div>'
LegendDiv += `<li onclick='SomeEffect("${valueclassId}")'>`
LegendDiv += colorBg + preparedLabel
LegendDiv += "</li>\n"
let perhapsPreviousLegend = document.getElementById("legend-for-"+curType)
if (perhapsPreviousLegend) {
perhapsPreviousLegend.outerHTML = LegendDiv
}
else {
let newLegend = document.createElement('div')
document.getElementById("legend-for-facets").appendChild(newLegend)
newLegend.outerHTML = LegendDiv
}
}
LegendDiv += ' </ul>'
LegendDiv += ' </div>'
$("#legend-for-clusters").addClass( "my-legend" );
$("#legend-for-clusters").html( LegendDiv )
$("#legend-for-clusters").show()
}
$("#legend-for-facets").show()
}
// = = = = = = = = = = = [ / Clusters Plugin ] = = = = = = = = = = = //
......
......@@ -96,8 +96,10 @@ function isUndef(variable){
function stringToSomeInt (anyString) {
let charCodeSum = 0
for (let i = 0 ; i < anyString.length ; i++) {
charCodeSum += anyString.charCodeAt(i)
if (anyString && anyString.length) {
for (let i = 0 ; i < anyString.length ; i++) {
charCodeSum += anyString.charCodeAt(i)
}
}
return charCodeSum
}
......
......@@ -97,7 +97,7 @@ TW.resetGraph = function() {
// reset rendering gui flags
TW.gui.selectionActive = false
TW.gui.handpickedcolor = false
TW.gui.handpickedcolor = {}
// reset circle size and cursor
TW.gui.circleSize = 0
......@@ -734,7 +734,7 @@ function prepareNodesRenderingProperties(nodesDict) {
// default unselected color
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.handpickedcolor flags)
alt_color: null,
altgrey_color: null,
}
......
......@@ -211,8 +211,8 @@ function scanGexf(gexfContent) {
// ex: terms
// ex: ISItermsriskV2_140 & ISItermsriskV2_140
// optional arg optionalNodeConf should contain keys of the form:
// "node0": "NGram",
// "node1": "Document"
// "node0": "Keywords",
// "node1": "Scholars"
// etc.
// (it's read from project_conf.json)
function sortNodeTypes(observedTypesDict, optionalNodeConf) {
......
......@@ -118,7 +118,7 @@ var SigmaUtils = function () {
context.beginPath();
if (settings('twSelectedColor') == "node")
context.fillStyle = TW.gui.handpickedcolor? node.customAttrs.alt_color : node.color; // node's
context.fillStyle = TW.gui.handpickedcolor[node.type] ? node.customAttrs.alt_color : node.color; // node's
else
context.fillStyle = "#fff"; // default
......@@ -180,7 +180,6 @@ var SigmaUtils = function () {
var color, size,
prefix = settings('prefix') || ''
//debug
// console.warn("rendering edge", edge)
......@@ -188,7 +187,7 @@ var SigmaUtils = function () {
// precomputed color with no opacity
// 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) {
size = (defSize * 2) + 1
......@@ -291,7 +290,7 @@ var SigmaUtils = function () {
// mode variants 1: if a coloringFunction is active
if (! TW.gui.handpickedcolor) {
if (! TW.gui.handpickedcolor[node.type]) {
nodeColor = node.color
}
else {
......@@ -301,7 +300,6 @@ var SigmaUtils = function () {
// mode variants 2: if node is selected, highlighted or unselected
if (TW.gui.selectionActive) {
// the selected node(s)
if (node.customAttrs.active) {
// called by label+background overlay cf. "subcall"
......@@ -317,7 +315,7 @@ var SigmaUtils = function () {
// passive nodes should blend in the grey of twEdgeGreyColor
// cf settings_explorerjs, defgrey_color and deselectNodes()
else {
if (! TW.gui.handpickedcolor) {
if (! TW.gui.handpickedcolor[node.type]) {
nodeColor = node.customAttrs.defgrey_color
}
else {
......@@ -656,11 +654,15 @@ function edgeInfos(anEdge) {
}
// FIXME this function could be optimized
function gradientColoring(daclass) {
graphResetLabelsAndSizes() // full loop
TW.gui.handpickedcolor = true
// strategy on multiple types: color all types that have the attr
let forTypes = getActivetypesNames().filter(function (ty){
return daclass in TW.Facets[ty]
})
// value getter
let getVal
......@@ -672,18 +674,22 @@ function gradientColoring(daclass) {
}
var min_pow = 0;
for(var nid in TW.Nodes) {
var the_node = TW.Nodes[ nid ]
var attval = getVal(the_node);
for (var k in forTypes) {
let nids = TW.partialGraph.graph.getNodesByType(
TW.catDict[forTypes[k]]
)
for (var j in nids) {
let attval = getVal(TW.Nodes[ nids[j] ]);
if( !isNaN(parseFloat(attval)) ) { //is float
while(true) {
var themult = Math.pow(10,min_pow);
if(parseFloat(attval)==0.0) break;
if ( (parseFloat(attval)*themult)<1.0 ) {
min_pow++;
} else break;
}
while(true) {
var themult = Math.pow(10,min_pow);
if(parseFloat(attval)==0.0) break;
if ( (parseFloat(attval)*themult)<1.0 ) {
min_pow++;
} else break;
}
}
}
}
var NodeID_Val = {}
......@@ -692,9 +698,13 @@ function gradientColoring(daclass) {
var themult = Math.pow(10,min_pow);
// console.log('themult', themult)
for(var nid in TW.Nodes) {
var the_node = TW.Nodes[ nid ]
var attval = getVal(the_node)
for (var k in forTypes) {
let nids = TW.partialGraph.graph.getNodesByType(
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);
if (isNaN(attnumber)) {
......@@ -707,6 +717,7 @@ function gradientColoring(daclass) {
if (round_number<real_min) real_min = round_number;
if (round_number>real_max) real_max = round_number;
}
}
// console.log("NodeID_Val", NodeID_Val)
......@@ -718,14 +729,21 @@ function gradientColoring(daclass) {
// console.log("the mult: "+themult)
// console.log(" - - - - - - - - -- - - ")
// [ Scaling node colours(0-255) and sizes(2-7) ]
var Min_color = 0;
var Max_color = 255;
var Min_size = 1;
var Max_size= 8;
var setSize = (TW.facetOptions[daclass] && TW.facetOptions[daclass].setsize)
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)
let n = TW.partialGraph.graph.nodes(nid)
......@@ -736,7 +754,7 @@ function gradientColoring(daclass) {
n.customAttrs.altgrey_color = "rgba("+(hex2rgba(hex_color).slice(0,3).join(','))+",0.4)"
// 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)) ) );
n.size = newval_size;
}
......@@ -752,36 +770,40 @@ function gradientColoring(daclass) {
// Edge precompute alt_rgb by new source-target nodes-colours combination
repaintEdges()
// remember in clusters
let bins = TW.Facets[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 first non null exemplar from last in the range
// (possible skip due to changeLevel or filters)
for (var k = nidList.length-1 ; k >= 0 ; k--) {
let nd = TW.partialGraph.graph.nodes(nidList[k])
if (nd) {
bins.invIdx[i].col = nd.customAttrs.alt_color
break
// remember in clusters of each requested type
for (var k in forTypes) {
let ty = forTypes[k]
TW.gui.handpickedcolor[ty] = true
let bins = TW.Facets[ty][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 first non null exemplar from last in the range
// (possible skip due to changeLevel or filters)
for (var k = nidList.length-1 ; k >= 0 ; k--) {
let nd = TW.partialGraph.graph.nodes(nidList[k])
if (nd) {
bins.invIdx[i].col = nd.customAttrs.alt_color
break
}
}
}
else {
bins.invIdx[i].col = "#777" // empty bin
}
}
else {
bins.invIdx[i].col = "#777" // empty bin
bins.invIdx[i].col = "#bbb" // non numeric values bin
}
}
else {
bins.invIdx[i].col = "#bbb" // non numeric values bin
}
}
}
// NB legend will group different possible values using
// precomputed ticks from TW.Facets.terms[daclass]
set_ClustersLegend ( daclass)
// precomputed ticks from TW.Facets[type][daclass]
set_ClustersLegend (daclass, forTypes)
TW.partialGraph.render();
}
......@@ -808,15 +830,27 @@ function repaintEdges() {
if (src && tgt) {
let src_color
let tgt_color
if (TW.gui.handpickedcolor) {
// handpickedcolor on multiple types may or may not affect
// a given edge so we need the useAltColor individual flag
let useAlt = false
if (TW.gui.handpickedcolor[src.type]) {
src_color = src.customAttrs.alt_color || '#555'
tgt_color = tgt.customAttrs.alt_color || '#555'
useAlt = true
}
else {
else
src_color = src.color || '#555'
tgt_color = tgt.color || '#555'
if (TW.gui.handpickedcolor[tgt.type]) {
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.useAltColor = useAlt
// we don't set e.color because opacity may vary if selected or not
}
}
......@@ -833,7 +867,9 @@ function repaintEdges() {
function heatmapColoring(daclass) {
var binColors
var doModifyLabel = false
var actypes = getActivetypesNames()
// let's go
graphResetLabelsAndSizes() // full loop
// default value
let nColors = TW.conf.legendsBins || 5
......@@ -848,73 +884,72 @@ function heatmapColoring(daclass) {
}
}
// we have no specifications yet for colors and legends on multiple types
if (actypes.length > 1) {
console.warn("colors by bins will only color nodes of type 0")
}
var ty = actypes[0]
// strategy on multiple types: color all types that have the attr
let forTypes = getActivetypesNames().filter(function (ty){
return daclass in TW.Facets[ty]
})
// our binning
var tickThresholds = TW.Facets[ty][daclass].invIdx
for (var k in forTypes) {
var ty = forTypes[k]
// verifications
if (tickThresholds.length - 1 != nColors) {
console.warn (`heatmapColoring setup mismatch: TW.Facets ticks ${tickThresholds.length} - 1 non_numeric from scanAttributes should == nColors ${nColors}`)
nColors = tickThresholds.length - 1
}
// global flag
TW.gui.handpickedcolor[ty] = true
binColors = getHeatmapColors(nColors)
// our binning
var tickThresholds = TW.Facets[ty][daclass].invIdx
// let's go
graphResetLabelsAndSizes() // full loop
// verifications
if (tickThresholds.length - 1 != nColors) {
console.warn (`heatmapColoring setup mismatch: TW.Facets ticks ${tickThresholds.length} - 1 non_numeric from scanAttributes should == nColors ${nColors}`)
nColors = tickThresholds.length - 1
}
// global flag
TW.gui.handpickedcolor = true
binColors = getHeatmapColors(nColors)
// use our valueclass => ids mapping
for (var k in tickThresholds) {
// 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]}
// console.debug('tick infos', tickThresholds[k])
// ex: {labl: "terms||growth_rate||[0 ; 0.583]", nids: Array(99), range: [0 ; 0.583210]}
let theColor
let theColor
// skip grouped NaN values case => grey
if (tickThresholds[k].labl == '_non_numeric_') {
theColor = '#bbb'
}
else {
theColor = binColors[k]
}
// skip grouped NaN values case => grey
if (tickThresholds[k].labl == '_non_numeric_') {
theColor = '#bbb'
}
else {
theColor = binColors[k]
}
if (tickThresholds[k].nids.length) {
let rgbColStr = hex2rgba(binColors[k]).slice(0,3).join(',')
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])
if (n) {
n.customAttrs.alt_color = binColors[k]
n.customAttrs.altgrey_color = "rgba("+rgbColStr+",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}`
var originalLabel = TW.Nodes[n.id].label
if (doModifyLabel) {
var valSt = n.attributes[daclass]
n.label = `(${valSt}) ${originalLabel}`
}
}
}
}
}
// remember
tickThresholds[k].col = theColor
// remember
tickThresholds[k].col = theColor
}
}
// Edge precompute alt_rgb by new source-target nodes-colours combination
repaintEdges()
set_ClustersLegend ( daclass )
set_ClustersLegend ( daclass, forTypes )
TW.partialGraph.render();
}
......@@ -924,18 +959,25 @@ function clusterColoring(daclass) {
graphResetLabelsAndSizes() // full loop
let forTypes = getActivetypesNames().filter(function (ty){
return daclass in TW.Facets[ty]
})
// louvain needs preparation
if(daclass=="clust_louvain") {
if(!TW.SystemState().LouvainFait) {
try {
RunLouvain()
TW.SystemState().LouvainFait = true
RunLouvain(function() {
TW.SystemState().LouvainFait = true
clusterColoring("clust_louvain")
})
}
catch(e) {
TW.SystemState().LouvainFait = false
console.warn("skipped error on louvain, falling back to default colors")
daclass == 'clust_default'
}
return
}
}
......@@ -949,10 +991,9 @@ function clusterColoring(daclass) {
}
// reset the global state
TW.gui.handpickedcolor = false
TW.gui.handpickedcolor = {}
}
else {
let colList = []
if (TW.conf.randomizeClusterColors) {
// shuffle on entire array is better than random sorting function on each element
......@@ -964,47 +1005,51 @@ function clusterColoring(daclass) {
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) {
for (var i in facets.invIdx) {
let valGroup = facets.invIdx[i]
let theColor
if (valGroup.labl == "_non_numeric_") {
theColor == '#bbb'
}
else {
let val = valGroup.val || valGroup.range
// use the int as an index between 0 and nColors
if (parseInt(val) == val) {
theColor = colList [val % nColors]
if (facets && facets.invIdx) {
for (var i in facets.invIdx) {
let valGroup = facets.invIdx[i]
let theColor
if (valGroup.labl == "_non_numeric_") {
theColor == '#bbb'
}
// or create a representative int on the same range
else {
let someRepresentativeInt = stringToSomeInt(val) % nColors
theColor = colList[ someRepresentativeInt ]
let val = valGroup.val || valGroup.range
// use the int as an index between 0 and nColors
if (parseInt(val) == val) {
theColor = colList [val % nColors]
}
// or create a representative int on the same range
else {
let someRepresentativeInt = stringToSomeInt(val) % nColors
theColor = colList[ someRepresentativeInt ]
}
}
}
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)"
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)"
}
}
}
}
// remember in TW.Facets
valGroup.col = theColor
// remember in TW.Facets
valGroup.col = theColor
}
}
}
// fallback on old, slower strategy if scanAttributes inactive
else {
for(var nid in TW.Nodes) {
var the_node = TW.partialGraph.graph.nodes(nid)
// fallback on old, slower strategy if scanAttributes inactive
else {
let nids = TW.partialGraph.graph.getNodesByType(TW.catDict[typeName])
for (var j in nids) {
let nid = nids[j]
let the_node = TW.partialGraph.graph.nodes(nid)
if (the_node) {
......@@ -1031,17 +1076,17 @@ function clusterColoring(daclass) {
the_node.customAttrs.altgrey_color = "rgba("+(hex2rgba(theColor).slice(0,3).join(','))+",0.4)"
}
}
}
}
// set the global state
TW.gui.handpickedcolor[typeName] = true
}
// set the global state
TW.gui.handpickedcolor = true
}
// Edge precompute alt_rgb by new source-target nodes-colours combination
repaintEdges()
set_ClustersLegend ( daclass )
set_ClustersLegend ( daclass, forTypes )
TW.partialGraph.render();
}
......
......@@ -21,7 +21,7 @@ TW.conf = (function(TW){
// ...or remote bridge to default source api ajax queries
TWConf.sourceAPI={}
TWConf.sourceAPI["nodetypes"] = {"node0": "NGram", "node1": "Document" }
TWConf.sourceAPI["nodetypes"] = {"node0": "Keywords", "node1": "Scholars" }
TWConf.sourceAPI["forNormalQuery"] = "services/api/graph"
TWConf.sourceAPI["forFilteredQuery"] = "services/api/graph"
......@@ -59,7 +59,7 @@ TW.conf = (function(TW){
TWConf.scanAttributes = true
// use a facet for default color
TWConf.defaultColoring = "clust_louvain"
TWConf.defaultColoring = null
// facetOptions: choose here the default visual result of your node attributes
// ---------------------------------------------------------------------------
......
......@@ -21,7 +21,7 @@ TW.conf = (function(TW){
// ...or remote bridge to default source api ajax queries
TWConf.sourceAPI={}
TWConf.sourceAPI["nodetypes"] = {"node0": "NGram", "node1": "Document" }
TWConf.sourceAPI["nodetypes"] = {"node0": "Keywords", "node1": "Scholars" }
TWConf.sourceAPI["forNormalQuery"] = "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