Commit 6ffe4344 authored by Romain Loth's avatar Romain Loth

facet legends 2/2

heatmap coloring, better facetOptions cases, better handling NaN in attributes and renamed coloring functions
parent 735605a4
...@@ -43,18 +43,7 @@ This will still evolve but the main steps for any graph initialization messily u ...@@ -43,18 +43,7 @@ This will still evolve but the main steps for any graph initialization messily u
- `somenode.attributes`: the `attributes` property is always an object - `somenode.attributes`: the `attributes` property is always an object
- any attribute listed in the sourcenode.attributes will be indexed if the TW.scanClusters flag is true - any attribute listed in the sourcenode.attributes will be indexed if the TW.scanClusters flag is true
- data type and style of processing (for heatmap, or for classes, etc.) should be stipulated in settings - data type and style of processing (for heatmap, or for classes, etc.) should be stipulated in settings (cf. **data facets** below)
- the mapping from attribute values to matching nodes is in TW.Clusters.aType.anAttr.aValue.map
- finally in GUI we can associate 3 types of coloration
- `"gradient"` coloration
- available for any attribute that looks like a continuous metric
- `"heatmap"` coloration
- colors from cold to hot centered on a white "neutral" color
- applied for attributes stipulated in settings: eg "`age`" "`growth_rate`"
- `"cluster"` coloration for str or num classes like modularity_class, affiliation, etc.
- we use contrasted values from colorList
- automatically applied for "`cluster_index`" or any name in `TW.conf.nodeClusAtt`
## User interaction mecanisms ## User interaction mecanisms
...@@ -87,10 +76,28 @@ For any node `n` the relevant flags at selection are: ...@@ -87,10 +76,28 @@ For any node `n` the relevant flags at selection are:
#### Facets: node attributes as colors/clusters #### Facets: node attributes as colors/clusters
At parsing time, every node attributes are indexed by values. At parsing time, every node attributes are indexed by values (allows to highlight them from legend, other uses are possible).
The values can be binned or not and can be linked to different color schemes:
- we can associate 3 types of coloration
- `"gradient"` coloration
- available for any attribute that looks like a continuous metric
- `"heatmap"` coloration
- colors from cold to hot centered on a white "neutral" color
- applied for attributes stipulated in settings: eg "`growth_rate`"
- `"cluster"` coloration for str or num classes like modularity_class, affiliation, etc.
- we use contrasted values from colorList
- automatically applied for "`cluster_index`" or any name in `TW.conf.nodeClusAtt`
- and also 3 possible binning modes:
- 'samerange': constant intervals between each bin
- 'samepop': constant cardinality inside each class (~ quantiles)
- 'off' : no binning (each distinct value will be a legend item)
These choices can be specified in the conf `facetOptions` entry.
If an attribute is **not** described in `facetOptions`, it will get `"gradient"` coloration and will be binned iff it has more disctinct values than `maxDiscreteValues`, into `legendBins` intervals.
This indexes are stored in TW.Clusters and provide an access to sets of nodes that have a given value or range of values. These indexes are stored in TW.Clusters and provide an access to sets of nodes that have a given value or range of values
- if discrete attrvalues with <= 30 classes (colorsBy, clustersBy), the storage structure is: `TW.Clusters[nodeType][clusterType].classes.[possibleValue]` - the mapping from attribute values to matching nodes is always in TW.Clusters.aType.anAttr.aClass.map
(the content is a list of ids with the value `possibleValue`) (where aClass is the chosen interval or distinct value)
- if continuous or many possible values (>30) (clustersBy, colorsRelByBins), the storage uses ordered ranges ("bins"):
`TW.Clusters[nodeType][clusterType].ranges.[interval]`
...@@ -639,11 +639,11 @@ ...@@ -639,11 +639,11 @@
<!-- <script src="tinawebJS/sigma.v1.customized.js" type="text/javascript" language="javascript"></script> --> <!-- <script src="tinawebJS/sigma.v1.customized.js" type="text/javascript" language="javascript"></script> -->
<!-- <script src="tinawebJS/sigma.forceatlas2.js" type="text/javascript" language="javascript"></script> --> <!-- <script src="tinawebJS/sigma.forceatlas2.js" type="text/javascript" language="javascript"></script> -->
<script src="settings_explorerjs.js" type="text/javascript" language="javascript"></script> <script src="settings_explorerjs.js" type="text/javascript" language="javascript"></script>
<script src="tinawebJS/enviroment.js" type="text/javascript" language="javascript"></script>
<script src="tinawebJS/sigma.parseCustom.js" type="text/javascript" language="javascript"></script> <script src="tinawebJS/sigma.parseCustom.js" type="text/javascript" language="javascript"></script>
<script src="extras_explorerjs.js" type="text/javascript" language="javascript"></script> <script src="extras_explorerjs.js" type="text/javascript" language="javascript"></script>
<script src="tinawebJS/sigmaUtils.js" type="text/javascript" language="javascript"></script> <script src="tinawebJS/sigmaUtils.js" type="text/javascript" language="javascript"></script>
<script src="tinawebJS/methods.js" type="text/javascript" language="javascript"></script> <script src="tinawebJS/methods.js" type="text/javascript" language="javascript"></script>
<script src="tinawebJS/enviroment.js" type="text/javascript" language="javascript"></script>
<!-- <script src="tinawebJS/asyncFA2.js" type="text/javascript" language="javascript"></script> --> <!-- <script src="tinawebJS/asyncFA2.js" type="text/javascript" language="javascript"></script> -->
<script src="tinawebJS/Tinaweb.js" type="text/javascript" language="javascript"></script> <script src="tinawebJS/Tinaweb.js" type="text/javascript" language="javascript"></script>
<script src="tinawebJS/main.js" type="text/javascript" language="javascript"></script> <script src="tinawebJS/main.js" type="text/javascript" language="javascript"></script>
......
...@@ -11,7 +11,7 @@ function newPopup(url) { ...@@ -11,7 +11,7 @@ function newPopup(url) {
// = = = = = = = = = = = [ Clusters Plugin ] = = = = = = = = = = = // // = = = = = = = = = = = [ Clusters Plugin ] = = = = = = = = = = = //
// Execution: changeGraphAppearanceByFacets( true ) // Execution: changeGraphAppearanceByFacets( true )
// It reads scanned node-attributes and prepared legends in TW.Clusters // It reads scanned node-attributes and prepared legends in TW.Clusters
// to add the button in the html with the sigmaUtils.clustersBy(x) listener. // to add the button in the html with the sigmaUtils.gradientColoring(x) listener.
function changeGraphAppearanceByFacets( manualflag ) { function changeGraphAppearanceByFacets( manualflag ) {
if ( !isUndef(manualflag) && !TW.conf.colorByAtt ) TW.conf.colorByAtt = manualflag; if ( !isUndef(manualflag) && !TW.conf.colorByAtt ) TW.conf.colorByAtt = manualflag;
...@@ -20,16 +20,21 @@ function changeGraphAppearanceByFacets( manualflag ) { ...@@ -20,16 +20,21 @@ function changeGraphAppearanceByFacets( manualflag ) {
// for GUI html: if present, rename raw attribute key by a proper label // for GUI html: if present, rename raw attribute key by a proper label
var AttsTranslations = { var AttsTranslations = {
'clust_louvain': 'Groupes de voisins, méthode de Louvain', 'clust_louvain': 'Groupes de voisins, méthode de Louvain',
'pageranks': 'Importance dans le réseau, méthode Google', 'PageRank': 'Importance dans le réseau, méthode Google',
'age': 'Date initiale d\'apparition du terme dans le corpus', 'age': 'Date initiale d\'apparition du terme dans le corpus',
'growth_rate': 'Tendances et oubliés de la semaine', 'growth_rate': 'Tendances et oubliés de la semaine',
'modularity_class': 'Groupes de voisins, méthode des classes de modularité' 'Modularity Class': 'Groupes de voisins, méthode des classes de modularité'
} }
// create colormenu // settings to function name
var colorFuns = {
'heatmap': "heatmapColoring",
'gradient': "gradientColoring",
'cluster': "clusterColoring"
}
// create colormenu
var color_menu_info = '<li><a href="#" onclick="graphResetColor()">By Default</a></li>'; var color_menu_info = '<li><a href="#" onclick="graphResetColor()">By Default</a></li>';
if( $( "#colorgraph-menu" ).length>0 ) { if( $( "#colorgraph-menu" ).length>0 ) {
...@@ -43,15 +48,26 @@ function changeGraphAppearanceByFacets( manualflag ) { ...@@ -43,15 +48,26 @@ function changeGraphAppearanceByFacets( manualflag ) {
// POSS here distinguish [ty][att_s].classes.length and ranges.length // POSS here distinguish [ty][att_s].classes.length and ranges.length
var att_c = TW.Clusters[ty][att_s].length var att_c = TW.Clusters[ty][att_s].length
var the_method = "clustersBy"
// variants
if(att_s.indexOf("clust")>-1||att_s.indexOf("class")>-1) { // coloringFunction
// for classes and clusters var the_method
the_method = "colorsBy"
// read from user settings
if (TW.conf.facetOptions[att_s] && TW.conf.facetOptions[att_s]['col']) {
the_method = colorFuns[TW.conf.facetOptions[att_s]['col']]
}
// default values
if (! the_method) {
if(att_s.indexOf("clust")>-1||att_s.indexOf("class")>-1) {
// for classes and clusters
the_method = "clusterColoring"
}
else {
the_method = "gradientColoring"
}
} }
if(att_s == "growth_rate") the_method = "colorsRelByBins"
if(att_s == "age") the_method = "colorsRelByBins"
// family label :) // family label :)
var lab_att_s ; var lab_att_s ;
...@@ -65,30 +81,15 @@ function changeGraphAppearanceByFacets( manualflag ) { ...@@ -65,30 +81,15 @@ function changeGraphAppearanceByFacets( manualflag ) {
} }
// we also add clust_louvain in all cases // we also add clust_louvain in all cases
color_menu_info += `<li><a href="#" onclick='colorsBy("clust_louvain")'>By Louvain clustering (${TW.partialGraph.graph.nNodes()})</a></li>` color_menu_info += `<li><a href="#" onclick='clusterColoring("clust_louvain")'>By Louvain clustering (${TW.partialGraph.graph.nNodes()})</a></li>`
// for debug
console.warn('color_menu_info', color_menu_info)
$("#colorgraph-menu").html(color_menu_info) $("#colorgraph-menu").html(color_menu_info)
} }
// // 2) prepare legend slots // Legend slots were prepared in TW.Clusters
// 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
// }
// }
// }
} }
......
...@@ -31,8 +31,8 @@ ...@@ -31,8 +31,8 @@
.my-legend { .my-legend {
position:fixed; position:fixed;
/* width: we set it equal or smaller than #lefttopbox width */ /* width: we set it equal or smaller than #lefttopbox width */
max-width:18%; max-width:20%;
max-height: 25%; max-height: 30%;
padding: 0 5px; padding: 0 5px;
overflow-y:scroll; overflow-y:scroll;
bottom:0; bottom:0;
...@@ -42,7 +42,7 @@ ...@@ -42,7 +42,7 @@
opacity: 0.8; opacity: 0.8;
color:#250587; color:#250587;
margin: 7px; margin: 7px;
font-size:small; font-size:120%;
} }
.my-legend .legend-title { .my-legend .legend-title {
......
...@@ -51,39 +51,43 @@ TW.conf = (function(TW){ ...@@ -51,39 +51,43 @@ TW.conf = (function(TW){
// create facets ? // create facets ?
TWConf.scanClusters = true TWConf.scanClusters = true
// for continuous attrvalues/colors (cf. clustersBy), how many levels in legend?
TWConf.legendsBins = 7 ;
// max discrete levels in facet legend (if attribute has more distinct values then binning)
TWConf.maxDiscreteValues = 40
// £TODO transform for new specifications
// some specific attributes may have other number of levels
TWConf.customLegendsBins = {
'age': 8,
'growth_rate': 12
}
// facetOptions: choose here the visual result of your node attributes // facetOptions: choose here the visual result of your node attributes
// -------------------------------------------------------------------
// 3 possible coloring functions // 3 possible coloring functions
// - cluster (contrasted colors for attributes describing *classes*) // - cluster (contrasted colors for attributes describing *classes*)
// - gradient (uniform map from a numeric attribute to red/yellow gradient) // - gradient (uniform map from a numeric attribute to red/yellow gradient)
// - heatmap (from blue to red/brown, centered on a white "neutral" color) // - heatmap (from blue to red, centered on a white "neutral" color)
// 2 possible binning modes // 3 possible binning modes
// - samerange: constant intervals between each bin // - 'samerange': constant intervals between each bin
// - samepop: constant cardinality inside each class (~ quantiles) // - 'samepop': constant cardinality inside each class (~ quantiles)
// Cases with no binning: if type is not numeric or if there is less than n vdistinct values // - 'off' : no binning (each distinct value will be a legend item)
TWConf.facetOptions = { TWConf.facetOptions = {
// attribute | coloring | number | binning // gexf | | custom |
// name | function | of bins | mode // attribute | coloring | number | binning
// title | function | of bins | mode
// -------------------------------------------------------------------- // --------------------------------------------------------------------
'numuniform' : {'col': "gradient", 'n': 3, 'binmode': 'samerange'}, 'age' : {'col': "gradient", 'n': 2, 'binmode': 'samerange'},
'numpareto' : {'col': "gradient", 'n': 8, 'binmode': 'samepop' }, 'growth_rate' : {'col': "heatmap", 'n': 11, 'binmode': 'samepop' },
'intfewvalues' : {'col': "heatmap" , 'n': 4, 'binmode': 'samerange'}, 'PageRank' : {'col': "gradient", 'n': 8, 'binmode': 'samepop' },
'countryuniform':{'col': "cluster" }, 'numuniform' : {'col': "heatmap", 'n': 7, 'binmode': 'samepop' },
'numpareto' : {'col': "gradient", 'n': 5, 'binmode': 'samerange'},
'intfewvalues' : {'col': "cluster" , 'n': 4, 'binmode': 'samerange'},
'Modularity Class': {'col': "cluster", 'binmode': 'off'}, // <== exemple with no binning
'countryuniform' : {'col': "cluster" , 'binmode': 'off'},
'countrypareto' : {'col': "cluster" , 'binmode': 'off'},
} }
// NB other cases with no binning:
// - if data type is not numeric
// - if there is less than distinct values that facetOptions[attr][n]
// NB for heatmapColoring:
// - you should prefer odd number of bins
// - if the number of bins is even, the 2 classes in the middle get white
// - the maximum number of bins is 24
// other POSS option: display attribute value in label or not ? // other POSS option: display attribute value in label or not ?
...@@ -125,7 +129,7 @@ TW.conf = (function(TW){ ...@@ -125,7 +129,7 @@ TW.conf = (function(TW){
// ----------------------------- // -----------------------------
TWConf.filterSliders = true // show sliders for nodes/edges subsets TWConf.filterSliders = true // show sliders for nodes/edges subsets
TWConf.colorsByAtt = true; // show "Set colors" menu TWConf.clusterColorsAtt = true; // show "Set colors" menu
TWConf.deselectOnclickStage = true // click on background remove selection ? TWConf.deselectOnclickStage = true // click on background remove selection ?
// (except when dragging) // (except when dragging)
...@@ -245,7 +249,7 @@ TW.conf = (function(TW){ ...@@ -245,7 +249,7 @@ TW.conf = (function(TW){
// show verbose console logs... // show verbose console logs...
logFetchers: false, // ...about ajax/fetching of graph data logFetchers: false, // ...about ajax/fetching of graph data
logParsers: false, // ...about parsing said data logParsers: false, // ...about parsing said data
logFacets: true, // ...about parsing node attribute:value facets logFacets: false, // ...about parsing node attribute:value facets
logSettings: false, // ...about settings at Tina and Sigma init time logSettings: false, // ...about settings at Tina and Sigma init time
logSelections: false logSelections: false
} }
......
...@@ -756,7 +756,7 @@ var TinaWebJS = function ( sigmacanvas ) { ...@@ -756,7 +756,7 @@ var TinaWebJS = function ( sigmacanvas ) {
TW.partialGraph.camera.goTo({x:0, y:0, ratio:1.2}) TW.partialGraph.camera.goTo({x:0, y:0, ratio:1.2})
}); });
if (!TW.conf.colorsByAtt) { if (!TW.conf.clusterColorsAtt) {
$("#setcolorsMenu").hide() $("#setcolorsMenu").hide()
} }
......
...@@ -3,6 +3,118 @@ ...@@ -3,6 +3,118 @@
// always useful // always useful
var theHtml = document.getElementsByTagName('html')[0] var theHtml = document.getElementsByTagName('html')[0]
// GUI vars
TW.gui = {}
TW.gui.selectionActive = false // <== changes rendering mode
TW.gui.circleSize = 0;
TW.gui.checkBox=false;
TW.gui.shiftKey=false;
TW.gui.manuallyChecked = false;
TW.gui.lastFilters = {}
// contrasted color list for clusterColoring()
TW.gui.colorList = ["#000000", "#FFFF00", "#1CE6FF", "#FF34FF", "#FF4A46", "#008941",
"#006FA6", "#A30059", "#FFDBE5", "#7A4900", "#0000A6", "#63FFAC", "#B79762",
"#004D43", "#8FB0FF", "#997D87", "#5A0007", "#809693", "#FEFFE6", "#1B4400",
"#4FC601", "#3B5DFF", "#4A3B53", "#FF2F80", "#61615A", "#BA0900", "#6B7900",
"#00C2A0", "#FFAA92", "#FF90C9", "#B903AA", "#D16100", "#DDEFFF", "#000035",
"#7B4F4B", "#A1C299", "#300018", "#0AA6D8", "#013349", "#00846F", "#372101",
"#FFB500", "#C2FFED", "#A079BF", "#CC0744", "#C0B9B2", "#C2FF99", "#001E09",
"#00489C", "#6F0062", "#0CBD66", "#EEC3FF", "#456D75", "#B77B68", "#7A87A1",
"#788D66", "#885578", "#FAD09F", "#FF8A9A", "#D157A0", "#BEC459", "#456648",
"#0086ED", "#886F4C","#34362D", "#B4A8BD", "#00A6AA", "#452C2C", "#636375",
"#A3C8C9", "#FF913F", "#938A81", "#575329", "#00FECF", "#B05B6F", "#8CD0FF",
"#3B9700", "#04F757", "#C8A1A1", "#1E6E00", "#7900D7", "#A77500", "#6367A9",
"#A05837", "#6B002C", "#772600", "#D790FF", "#9B9700", "#549E79", "#FFF69F",
"#201625", "#72418F", "#BC23FF", "#99ADC0", "#3A2465", "#922329", "#5B4534",
"#FDE8DC", "#404E55", "#0089A3", "#CB7E98", "#A4E804", "#324E72", "#6A3A4C",
"#83AB58", "#001C1E", "#D1F7CE", "#004B28", "#C8D0F6", "#A3A489", "#806C66",
"#222800", "#BF5650", "#E83000", "#66796D", "#DA007C", "#FF1A59", "#8ADBB4",
"#1E0200", "#5B4E51", "#C895C5", "#320033", "#FF6832", "#66E1D3", "#CFCDAC",
"#D0AC94", "#7ED379", "#012C58"];
// 24 colors + White, divided in cold and warm range for getHeatmapColors() fun
TW.gui.heatmapColorListWhite = "#F9F7ED"
TW.gui.heatmapColorListToColdest = [
"#B4FF50",
"#A4FF24",
"#79FF23",
"#42F923",
"#22F226",
"#02CB36",
"#01C462",
"#01BC8D",
"#00B5B1",
"#0088AE",
"#005197",
"#002FA0"
]
TW.gui.heatmapColorListToWarmest = [
"#FFE37A",
"#FFE008",
"#F0C508",
"#E89A09",
"#E48509",
"#DF7009",
"#DB5B09",
"#D7450A",
"#D3300A",
"#CF1B0A",
"#CB060B",
"#B21014"
];
// provides a heatmap color list centered on a white class
// arg: number of classes
// NB: if number of classes is *even*,
// then the 2 middle categories
// will get the white "epsilon" color
function getHeatmapColors(nClasses) {
// our dev param == colorListToWarmest.length == colorListToColdest.length
let listsLen = 12
// our result
var outColors = []
if (nClasses > listsLen*2) {
throw(`this function implementation can only give up to ${listsLen*2} classes`)
}
let nHalfToPick = 0
if (nClasses % 2 == 0) {
nHalfToPick = nClasses/2 - 1
}
else {
nHalfToPick = (nClasses-1)/2
}
// floor
let skipStep = parseInt(listsLen / nHalfToPick)
// cold colors
for (let i = listsLen-1 ; i > 0 ; i -= skipStep ) {
outColors.push(TW.gui.heatmapColorListToColdest[i])
}
// white
outColors.push(TW.gui.heatmapColorListWhite)
if (nClasses % 2 == 0) {
outColors.push(TW.gui.heatmapColorListWhite)
}
// warm colors
for (let i = 0 ; i < listsLen ; i += skipStep ) {
outColors.push(TW.gui.heatmapColorListToWarmest[i])
}
return outColors
}
function writeBrand (brandString) { function writeBrand (brandString) {
document.getElementById('twbrand').innerHTML = brandString document.getElementById('twbrand').innerHTML = brandString
} }
......
...@@ -44,19 +44,6 @@ var bipartiteD2N = {}; ...@@ -44,19 +44,6 @@ var bipartiteD2N = {};
var bipartiteN2D = {}; var bipartiteN2D = {};
// GUI vars
TW.gui = {}
TW.gui.selectionActive = false // <== changes rendering mode
TW.gui.circleSize = 0;
TW.gui.checkBox=false;
TW.gui.shiftKey=false;
TW.gui.manuallyChecked = false;
TW.gui.lastFilters = {}
TW.colorList = ["#000000", "#FFFF00", "#1CE6FF", "#FF34FF", "#FF4A46", "#008941", "#006FA6", "#A30059", "#FFDBE5", "#7A4900", "#0000A6", "#63FFAC", "#B79762", "#004D43", "#8FB0FF", "#997D87", "#5A0007", "#809693", "#FEFFE6", "#1B4400", "#4FC601", "#3B5DFF", "#4A3B53", "#FF2F80", "#61615A", "#BA0900", "#6B7900", "#00C2A0", "#FFAA92", "#FF90C9", "#B903AA", "#D16100", "#DDEFFF", "#000035", "#7B4F4B", "#A1C299", "#300018", "#0AA6D8", "#013349", "#00846F", "#372101", "#FFB500", "#C2FFED", "#A079BF", "#CC0744", "#C0B9B2", "#C2FF99", "#001E09", "#00489C", "#6F0062", "#0CBD66", "#EEC3FF", "#456D75", "#B77B68", "#7A87A1", "#788D66", "#885578", "#FAD09F", "#FF8A9A", "#D157A0", "#BEC459", "#456648", "#0086ED", "#886F4C","#34362D", "#B4A8BD", "#00A6AA", "#452C2C", "#636375", "#A3C8C9", "#FF913F", "#938A81", "#575329", "#00FECF", "#B05B6F", "#8CD0FF", "#3B9700", "#04F757", "#C8A1A1", "#1E6E00", "#7900D7", "#A77500", "#6367A9", "#A05837", "#6B002C", "#772600", "#D790FF", "#9B9700", "#549E79", "#FFF69F", "#201625", "#72418F", "#BC23FF", "#99ADC0", "#3A2465", "#922329", "#5B4534", "#FDE8DC", "#404E55", "#0089A3", "#CB7E98", "#A4E804", "#324E72", "#6A3A4C", "#83AB58", "#001C1E", "#D1F7CE", "#004B28", "#C8D0F6", "#A3A489", "#806C66", "#222800", "#BF5650", "#E83000", "#66796D", "#DA007C", "#FF1A59", "#8ADBB4", "#1E0200", "#5B4E51", "#C895C5", "#320033", "#FF6832", "#66E1D3", "#CFCDAC", "#D0AC94", "#7ED379", "#012C58"];
// ======== [ what to do at start ] ========= // // ======== [ what to do at start ] ========= //
console.log("Starting TWJS") console.log("Starting TWJS")
...@@ -255,17 +242,16 @@ function syncRemoteGraphData () { ...@@ -255,17 +242,16 @@ function syncRemoteGraphData () {
var preRES = AjaxSync({ url: infofile, datatype:"json" }); var preRES = AjaxSync({ url: infofile, datatype:"json" });
if (preRES['OK'] && preRES.data) { if (preRES['OK'] && preRES.data) {
console.log('initial AjaxSync result preRES', preRES) if (TW.conf.debug.logFetchers) console.log('initial AjaxSync result preRES', preRES)
} }
var first_file = "" , first_path = "" var first_file = "" , first_path = ""
for( var path in preRES.data ) { for( var path in preRES.data ) {
// console.log("db.json path", path) if (TW.conf.debug.logFetchers) console.log("db.json path", path)
first_file = preRES.data[path]["first"] first_file = preRES.data[path]["first"]
first_path = path first_path = path
console.log("db.json first_file", first_path, first_file)
break; break;
} }
......
...@@ -101,8 +101,9 @@ function gexfCheckAttributesMap (someXMLContent) { ...@@ -101,8 +101,9 @@ function gexfCheckAttributesMap (someXMLContent) {
// excerpt from targeted XML: // excerpt from targeted XML:
// <graph defaultedgetype="undirected" mode="static"> // <graph defaultedgetype="undirected" mode="static">
// | <attributes class="node" mode="static"> // | <attributes class="node" mode="static">
// | <attribute id="0" title="category" type="string"></attribute> // | <attribute id="0" title="category" type="string"></attribute>
// | <attribute id="1" title="country" type="float"></attribute> // | <attribute id="1" title="country" type="float"></attribute>
// | <attribute id="mod_class" title="Modularity Class" type="float"></attribute>
// | </attributes> // | </attributes>
// (...) // (...)
...@@ -127,7 +128,7 @@ function gexfCheckAttributesMap (someXMLContent) { ...@@ -127,7 +128,7 @@ function gexfCheckAttributesMap (someXMLContent) {
type = attributeNode.getAttribute('type'); type = attributeNode.getAttribute('type');
// ex: id = "in-degree" or "3" <= can be an int to be resolved into the title // ex: id = "in-degree" or "3" <= can be an int to be resolved into the title
// ex: title = "in-degree" // ex: title = "in-degree" or "In Degree" <= can be a label != id
// ex: type = "string" // ex: type = "string"
var attribute = { var attribute = {
...@@ -294,6 +295,9 @@ function facetsBinning (valuesIdx) { ...@@ -294,6 +295,9 @@ function facetsBinning (valuesIdx) {
for (var at in valuesIdx[cat]) { for (var at in valuesIdx[cat]) {
if (TW.conf.debug.logFacets) console.log(`======= ${cat}::${at} =======`) if (TW.conf.debug.logFacets) console.log(`======= ${cat}::${at} =======`)
// console.warn("all raw vals before binning" valuesIdx[cat][at].vals)
// new array of valueclass/interval/bin objects // new array of valueclass/interval/bin objects
facetIdx[cat][at] = [] facetIdx[cat][at] = []
...@@ -333,9 +337,11 @@ function facetsBinning (valuesIdx) { ...@@ -333,9 +337,11 @@ function facetsBinning (valuesIdx) {
workingVals = valuesIdx[cat][at].vals.vstr workingVals = valuesIdx[cat][at].vals.vstr
} }
console.debug("datatyping:", dataType) if (TW.conf.debug.logFacets) {
console.debug("valuesIdx after datatyping:", valuesIdx[cat][at]) console.debug("datatyping:", dataType)
console.debug("workingVals after datatyping:", workingVals) // console.debug("valuesIdx after datatyping:", valuesIdx[cat][at])
// console.debug("workingVals after datatyping:", workingVals)
}
// default options // default options
let maxDiscreteValues = TW.conf.maxDiscreteValues let maxDiscreteValues = TW.conf.maxDiscreteValues
...@@ -353,14 +359,27 @@ function facetsBinning (valuesIdx) { ...@@ -353,14 +359,27 @@ function facetsBinning (valuesIdx) {
console.warn(`Can't use user-specified number of bins value 0 for attribute ${at}, using TW.conf.legendsBins ${TW.conf.legendsBins} instead`) console.warn(`Can't use user-specified number of bins value 0 for attribute ${at}, using TW.conf.legendsBins ${TW.conf.legendsBins} instead`)
nBins = TW.conf.legendsBins nBins = TW.conf.legendsBins
} }
if (TW.conf.debug.logFacets) console.log("TW.conf.facetOptions[at]", TW.conf.facetOptions[at])
}
else {
if (TW.conf.debug.logFacets) console.log("(no specified options in settings for this attribute)")
} }
// POSSible: auto-detect if vtypes ==> color // POSSible: auto-detect if vtypes ==> color
// else { // else {
// } // }
// if (binningMode != "off") console.warn("maxDiscreteValues from settings", maxDiscreteValues)
var nDistinctVals = Object.keys(valuesIdx[cat][at].map).length
// if small number of distinct values doesn't need binify // if small number of distinct values doesn't need binify
if (Object.keys(valuesIdx[cat][at].map).length <= maxDiscreteValues) { if ( dataType == 'str'
|| (TW.conf.facetOptions[at] // case with custom facetOptions
&& (nDistinctVals <= nBins || binningMode == "off"))
|| (nDistinctVals <= maxDiscreteValues ) // case with unspecified options
) {
for (var pval in valuesIdx[cat][at].map) { for (var pval in valuesIdx[cat][at].map) {
var idList = valuesIdx[cat][at].map[pval] var idList = valuesIdx[cat][at].map[pval]
...@@ -416,12 +435,18 @@ function facetsBinning (valuesIdx) { ...@@ -416,12 +435,18 @@ function facetsBinning (valuesIdx) {
if (TW.conf.debug.logFacets) console.debug("intervals for", at, legendRefTicks, "(list of minima)") if (TW.conf.debug.logFacets) console.debug("intervals for", at, legendRefTicks, "(list of minima)")
// the unique-d array will allow us to group ranges // the unique-d array will serve as a todolist with lastCursor and k
var sortedDistinctVals = Object.keys(valuesIdx[cat][at].map).sort(function(a,b){return Number(a)-Number(b)}) // won't use keys(map) because of _non_numeric_ entry
let uniqueVals = {}
for (let k in workingVals) {
if (! uniqueVals[workingVals[k]]) {
uniqueVals[workingVals[k]] = 1
}
}
var sortedDistinctVals = Object.keys(uniqueVals).sort(function(a,b){return Number(a)-Number(b)})
var nTicks = legendRefTicks.length var nTicks = legendRefTicks.length
var nDistinctVals = sortedDistinctVals.length
var lastCursor = 0 var lastCursor = 0
// create ticks objects with retrieved full info // create ticks objects with retrieved full info
...@@ -449,11 +474,27 @@ function facetsBinning (valuesIdx) { ...@@ -449,11 +474,27 @@ function facetsBinning (valuesIdx) {
'range':[lowThres, hiThres] 'range':[lowThres, hiThres]
} }
if (TW.conf.debug.logFacets) console.debug("...new interval:",[lowThres, hiThres])
// 1) union of idmaps // 1) union of idmaps
for (var k = lastCursor ; k <= nDistinctVals ; k++) { for (var k = lastCursor ; k <= nDistinctVals ; k++) {
var val = Number(sortedDistinctVals[k]) var val = Number(sortedDistinctVals[k])
if (val == '_non_numeric_') {
continue
}
// FIXME why still NaN sometimes ?
// NB: however skipping them is enough for work
else if (isNaN(val)) {
console.debug('skipped undetected NaN ? attribute, lastCursor, k, sortedDistinctVals[k], nodes:', at, lastCursor, k, val, valuesIdx[cat][at].map[sortedDistinctVals[k]])
continue
}
// for debug
// console.debug('lastCursor, k, val', lastCursor, k, val)
if (val < lowThres) { if (val < lowThres) {
console.error("mixup !!", val, lowThres, at) console.error("mixup !!", val, lowThres, at)
} }
else if ((val >= lowThres) && (val < hiThres)) { else if ((val >= lowThres) && (val < hiThres)) {
if (!valuesIdx[cat][at].map[val]) { if (!valuesIdx[cat][at].map[val]) {
...@@ -470,8 +511,12 @@ function facetsBinning (valuesIdx) { ...@@ -470,8 +511,12 @@ function facetsBinning (valuesIdx) {
// we're over the interval upper bound // we're over the interval upper bound
else if (val >= hiThres) { else if (val >= hiThres) {
// console.log("over hiThres", val, hiThres)
// normal case // normal case
if (binningMode != 'samerange' || l != nTicks-1 ) { if (binningMode != 'samerange' || l != nTicks-1 ) {
// console.log("...moving on to next interval")
// we just need to remember where we were for next interval // we just need to remember where we were for next interval
lastCursor = k lastCursor = k
break break
...@@ -479,6 +524,8 @@ function facetsBinning (valuesIdx) { ...@@ -479,6 +524,8 @@ function facetsBinning (valuesIdx) {
// samerange && last interval case: inclusive last interval upper bound // samerange && last interval case: inclusive last interval upper bound
else { else {
// console.warn("last interval for samepop")
for (var j in valuesIdx[cat][at].map[val]) { for (var j in valuesIdx[cat][at].map[val]) {
newTick.nids.push(valuesIdx[cat][at].map[val][j]) newTick.nids.push(valuesIdx[cat][at].map[val][j])
} }
...@@ -878,6 +925,11 @@ function updateValueFacets(facetIdx, aNode) { ...@@ -878,6 +925,11 @@ function updateValueFacets(facetIdx, aNode) {
if (!facetIdx[aNode.type]) facetIdx[aNode.type]={} if (!facetIdx[aNode.type]) facetIdx[aNode.type]={}
for (var at in aNode.attributes) { for (var at in aNode.attributes) {
// we're not interested in node type/category at this point
if (at == 'category')
continue
let val = aNode.attributes[at] let val = aNode.attributes[at]
if (!facetIdx[aNode.type][at]) facetIdx[aNode.type][at]={'vals':{'vstr':[], 'vnum':[]},'map':{}} if (!facetIdx[aNode.type][at]) facetIdx[aNode.type][at]={'vals':{'vstr':[], 'vnum':[]},'map':{}}
...@@ -1106,11 +1158,13 @@ function dictfyJSON( data , categories ) { ...@@ -1106,11 +1158,13 @@ function dictfyJSON( data , categories ) {
// } // }
// } // }
TW.colorList.sort(function(){ return Math.random()-0.5; });
// £TODO this could be a call to clusterColoring()
TW.gui.colorList.sort(function(){ return Math.random()-0.5; });
for (var i in nodes ){ for (var i in nodes ){
if (nodes[i].color=="#FFFFFF") { if (nodes[i].color=="#FFFFFF") {
var attval = ( isUndef(nodes[i].attributes) || isUndef(nodes[i].attributes["clust_default"]) )? 0 : nodes[i].attributes["clust_default"] ; var attval = ( isUndef(nodes[i].attributes) || isUndef(nodes[i].attributes["clust_default"]) )? 0 : nodes[i].attributes["clust_default"] ;
nodes[i].color = TW.colorList[ attval ] nodes[i].color = TW.gui.colorList[ attval ]
} }
} }
......
This diff is collapsed.
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