Commit d19bacea authored by Romain Loth's avatar Romain Loth

project conf: allow local facet options + doc and facets code cleanup

facets: rationalize varnames, update doc
parent edcc91b7
......@@ -188,3 +188,135 @@ An additional variable `${{score}}` is always available in the templating contex
In this last exemple, we have two nodetypes:
- node0 allows both CSV and twitter relatedDocs tabs.
- node1 allows only the CSV relatedDocs tab.
------------------------------------------------------
#### Configuring facets (node attributes) rendering
Your graph nodes may contain attributes (aka **data facets**) and project_conf can allow you to specify how to use them.
For instance let's assume a node in your gexf input file may contain something like this:
```xml
<node id="99262" label="entreprises">
<attvalues>
<attvalue for="modularity_class" value="3"/>
<attvalue for="age" value="2012"/>
</attvalues>
<viz:size value="100.0"/>
<viz:color r="0" g="173" b="38"/>
</node>
```
The input data here has two attributes: "age" and "modularity_class"
These attributes (attvalues) can be processed at input time to:
- color the nodes in the interface
- create a legend with close values grouped into [statistical bins](https://en.wikipedia.org/wiki/Data_binning) by defining intervals
- replace the attribute name by a human-readable label in the legend and menus
- find a title for each subgroups or class
This processing is default and will take place any way if the value `scanAttributes` is true in the global conf (`settings_explorerjs.js`).
But the project conf `project_conf.json` allows us to fine-tune this, by specifying `facets` properties in the node entry for a source in your project :
###### Exemple 1: gradient coloring and 4 bins
```
"facets": {
"age" : {
"legend": "Date d'entrée dans le corpus" <== label used for legends
"col": "gradient", <== coloring function
"binmode": "samerange", <== binning mode
"n": 4, <== optional: number of bins
}
}
```
Here, `age` is the name of the attribute in the original data.
For the `col` key, the available coloring functions are:
- `cluster`: for attributes describing *classes* (class names or class numbers, contrasted colors)
- `gradient`: for continuous variables (uniform map from light yellow to dark red)
- `heatmap`: for continuous variables (from blue-green to red-brown, centered on a white "neutral" color)
Binning can build the intervals with 3 strategies (`binmode` key):
- `samerange`: constant intervals between each bin (dividing the range into `n` equal intervals)
- `samepop`: constant cardinality inside each class (~ quantiles: dividing the range into `n` intervals with equal population)
- `off`: no binning (each value gets a different color)
###### Exemple 2: `cluster` coloring
```
"facets": {
"Modularity Class" : {
"legend": "Modules dans le graphe",
"col": "cluster",
"binmode": "off" <== no binning: values are kept intact
}
}
```
Remarks:
- Heatmap coloring maximum amount of bins is 24.
- `legend` is optional
- `n` is not needed if `binmode` is off.
- Cluster coloring works best with no binning: each distinct value corresponds to a class and becomes a different color.
###### Real life example 1
```json
{
"ProgrammeDesCandidats.gexf": {
"node0": {
"name": "term",
"reldbs": {...},
"facets": {
"age" : {
"legend": "Date d'entrée dans le corpus",
"col": "gradient",
"binmode": "samerange",
"n": 4
},
"growth_rate" : {
"legend": "Tendances et oubliés de la semaine",
"col": "heatmap",
"binmode": "samepop",
"n": 11
},
"modularity_class" : {
"legend": "Modules dans le graphe",
"col": "cluster",
"binmode": "off"
}
}
}
}
}
```
###### Real life example 2
```json
{
"Maps_S_800.gexf": {
"node0": {
"name": "termsWhitelist",
"reldbs": {...},
"facets":{
"level": {"col": "heatmap" , "binmode": "off" },
"weight": {"col": "heatmap" , "n": 5, "binmode": "samerange" },
"period": {"col": "cluster" , "binmode": "off" },
"in-degree": {"col": "heatmap" , "n": 3, "binmode": "samepop" },
"out-degree": {"col": "heatmap" , "n": 3, "binmode": "samepop" },
"betweeness": {"col": "gradient", "n": 4, "binmode": "samepop" },
"cluster_label": {"col": "cluster" , "binmode": "off" },
"community_orphan":{"col": "cluster" , "binmode": "off" },
"cluster_universal_index": {"col": "cluster" ,"binmode": "off" },
}
}
}
}
```
NB: If an attribute is **not** described in `facets` and `TW.conf.scanAttributes` is true, the attribute will get `"gradient"` coloration by default and the distinct attributes values will be counted:
- if there is few of them (less than 15), they won't be binned
- if there is many distinct values, they will be binned into 7 intervals
The corresponding global conf keys to this default behavior are `TWConf.legendBins` and `TWConf.maxDiscreteValues` in `settings_explorerjs.js`
For more information, see the [developer's manual](https://github.com/moma/ProjectExplorer/blob/master/00.DOCUMENTATION/C-advanced/developer_manual.md#exposed-facets-indices)
......@@ -31,7 +31,7 @@ This will still evolve but the main steps for any graph initialization messily u
- *"scan"*: loop once to list present node categories
- *"dictify"*: loop again to copy all nodes/edges information
- prepares TW.Relations: edges sorted by type (term-term, term-doc...)
- prepares TW.Clusters: bins and facet index (node attr vals => nodes)
- prepares TW.Facets: bins and facet index (node attr vals => nodes)
4. [`main.js`] mainStartGraph() function runs all the rest
1. precomputes display properties (grey color, etc.)
2. calls [`sigmaUtils`] where the function `FillGraph()` was a central point for filtering and preparing properties but now with 2 and 3 it just creates a filtered copy of the nodes and edges of the current active types to a new structure that groups them together (POSSIBLE remove this extra step)
......@@ -46,7 +46,7 @@ This will still evolve but the main steps for any graph initialization messily u
- if the category name is "document" => catSoc (type 1)
- `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.scanAttributes flag is true
- data type and style of processing (for heatmap, or for classes, etc.) should be stipulated in settings (cf. **data facets** below)
......@@ -107,14 +107,19 @@ The values can be binned or not and can be linked to different color schemes:
- '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.
These choices can be specified in each project_conf.json under the `facets` 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.
If an attribute is **not** described in `project_conf.json`, it will get `"gradient"` coloration and will be binned iff it has more disctinct values than `maxDiscreteValues`, into `legendBins` intervals.
These indexes are stored in TW.Clusters and provide an access to sets of nodes that have a given value or range of values
- the mapping from attribute values to matching nodes is always in `TW.Clusters.aType.anAttr.invIdx.aClass.nids`
The allowed coloring functions are declared in TW.gui.colorFuns in `environment.js`.
#### Exposed facets indices
A faceted index is an index "value of an attribute" => nodes having this value.
These indexes are stored in the exposed `TW.Facets` variable by parseCustom time and provide an access to sets of nodes that have a given value or range of values
- the mapping from attribute values to matching nodes is always in `TW.Facets.aType.anAttr.invIdx.aClass.nids`
(where aClass is the chosen interval or distinct value)
- the datatype of the observed values is in `TW.Clusters.aType.anAttr.meta`
- the datatype of the observed values is in `TW.Facets.aType.anAttr.meta`
- the source datatype is always string in gexf, but real type ("vtype") can be numeric
- (ie numeric cast doesn't give NaN or it do so very rarely over the values)
......
<gexf xmlns="http://www.gexf.net/1.1draft" xmlns:viz="http://www.gephi.org/gexf/viz" version="1.1">
<graph defaultedgetype="undirected" type="static">
<attributes class="node" type="static">
<attribute id="0" title="category" type="string"> </attribute>
<attribute id="0" title="category" type="string"> </attribute>
<attribute id="1" title="country" type="string"> </attribute>
<attribute id="2" title="myValue" type="string"> </attribute>
<attribute id="3" title="someOccs" type="string"> </attribute>
</attributes>
<nodes>
<node id="T::0" label="scalajs">
<viz:size value="1"/>
<attvalues>
<attvalue for="0" value="term"/>
<attvalue for="3" value="1798"/>
</attvalues>
</node>
<node id="T::1" label="scala">
<viz:size value="1"/>
<attvalues>
<attvalue for="0" value="term"/>
<attvalue for="3" value="1577"/>
</attvalues>
</node>
<node id="T::2" label="python">
<viz:size value="1"/>
<attvalues>
<attvalue for="0" value="term"/>
<attvalue for="3" value="19"/>
</attvalues>
</node>
<node id="T::3" label="javascript">
<viz:size value="1"/>
<attvalues>
<attvalue for="0" value="term"/>
<attvalue for="3" value="1032"/>
</attvalues>
</node>
<node id="T::4" label="php">
<viz:size value="1"/>
<attvalues>
<attvalue for="0" value="term"/>
<attvalue for="3" value="655"/>
</attvalues>
</node>
<node id="P::0" label="-bb-">
<viz:size value="1"/>
<attvalues>
<attvalue for="0" value="person"/>
<attvalue for="1" value="China"/>
<attvalue for="2" value="14.995"/>
</attvalues>
</node>
<node id="P::1" label="-aa-">
<viz:size value="1"/>
<attvalues>
<attvalue for="0" value="person"/>
<attvalue for="1" value="Bhutan"/>
<attvalue for="2" value="8.9572"/>
</attvalues>
</node>
<node id="P::2" label="-dd-">
<viz:size value="1"/>
<attvalues>
<attvalue for="0" value="person"/>
<attvalue for="1" value="Brazil"/>
<attvalue for="2" value="4.6753"/>
</attvalues>
</node>
<node id="P::3" label="-cc-">
<viz:size value="1"/>
<attvalues>
<attvalue for="0" value="person"/>
<attvalue for="1" value="France"/>
<attvalue for="2" value="0.8481"/>
</attvalues>
</node>
<node id="P::4" label="-cc-">
<viz:size value="1"/>
<attvalues>
<attvalue for="0" value="person"/>
<attvalue for="1" value="Poland"/>
<attvalue for="2" value="8.6959"/>
</attvalues>
</node>
</nodes>
<edges>
<!-- bipartite 1|1 -->
<edge id="1" source="T::1" target="P::1"></edge>
<edge id="2" source="T::1" target="P::2"></edge>
<edge id="3" source="T::2" target="P::3"></edge>
<edge id="4" source="T::3" target="P::2"></edge>
<edge id="5" source="T::3" target="P::3"></edge>
<edge id="6" source="T::4" target="P::0"></edge>
<edge id="7" source="T::4" target="P::3"></edge>
<edge id="1" source="T::1" target="P::1"/>
<edge id="2" source="T::1" target="P::2"/>
<edge id="3" source="T::2" target="P::3"/>
<edge id="4" source="T::3" target="P::2"/>
<edge id="5" source="T::3" target="P::3"/>
<edge id="6" source="T::4" target="P::0"/>
<edge id="7" source="T::4" target="P::3"/>
<edge id="8" source="T::1" target="P::4"/>
<edge id="9" source="T::4" target="P::4"/>
<!-- intra sem 1|0 -->
<edge id="8" source="T::2" target="T::3"></edge>
<edge id="9" source="T::3" target="T::4"></edge>
<edge id="10" source="T::0" target="T::1"></edge>
<edge id="10" source="T::0" target="T::3"></edge>
<edge id="10" source="T::2" target="T::3"/>
<edge id="11" source="T::3" target="T::4"/>
<edge id="12" source="T::0" target="T::1"/>
<edge id="13" source="T::0" target="T::3"/>
<!-- intra soc 0|1 -->
<edge id="10" source="P::0" target="P::1"></edge>
<edge id="11" source="P::1" target="P::2"></edge>
<edge id="12" source="P::2" target="P::3"></edge>
<edge id="14" source="P::0" target="P::1"/>
<edge id="15" source="P::1" target="P::2"/>
<edge id="16" source="P::2" target="P::3"/>
<edge id="17" source="P::1" target="P::4"/>
</edges>
</graph>
</gexf>
{
"mini_for_csv.gexf": {
"mini.gexf": {
"node0": {
"name": "term",
"reldbs": {
......@@ -9,6 +9,14 @@
"template": "bib_details"
},
"twitter": {}
},
"facets": {
"someOccs": {
"col": "gradient",
"n": 3,
"binmode": "samepop",
"legend": "Test Occurrences"
}
}
},
"node1": {
......@@ -19,6 +27,15 @@
"qcols": ["author"],
"template": "bib_details"
}
},
"facets": {
"country": {"col": "cluster" , "legend": "Country", "binmode": "off" },
"myValue": {
"col": "heatmap",
"n": 2,
"binmode": "samerange",
"legend": "Some Important Value"
}
}
}
}
......
......@@ -11,7 +11,7 @@
"graph_example2.gexf"
],
"data/test": [
"mini_for_csv.gexf"
"mini.gexf"
],
"data/politoscope": [
"ProgrammeDesCandidats.gexf"
......
......@@ -38,7 +38,7 @@ TW.conf = (function(TW){
// routes by corresponding type
TWConf.relatedDocsAPIS = {
"twitter": "http://127.0.0.1:5000/twitter_search",
"twitter": "https://134.158.74.111/twitter_search",
"CortextDB": "twbackends/phpAPI",
"csv": "twbackends/phpAPI"
}
......@@ -51,125 +51,52 @@ TW.conf = (function(TW){
// =======================
// to process node attributes values from data
// => colors (continuous numeric attributes)
// => clusters (discrete numeric or str attributes),
// => clusters (discrete numeric or str attributes)
// cf. also "Configuring facets" in the doc under Introduction/project_config
// create facets ?
TWConf.scanClusters = true
TWConf.scanAttributes = true
// use a facet for default color
TWConf.defaultColoring = "clust_louvain"
// facetOptions: choose here the visual result of your node attributes
// -------------------------------------------------------------------
// 3 possible coloring functions
// - cluster (contrasted colors for attributes describing *classes*)
// - gradient (uniform map from a numeric attribute to red/yellow gradient)
// - heatmap (from blue to red, centered on a white "neutral" color)
// 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)
TWConf.facetOptions = {
// facetOptions: choose here the default visual result of your node attributes
// ---------------------------------------------------------------------------
// (values overridden by data/myproject/project_conf.json "facets" if present)
TWConf.defaultFacetOptions = {
// attr title
'age' : {
'col': "gradient", // coloring function
'binmode': 'samerange', // binning mode
'n': 4, // custom number of bins
'menutransl': "Date initiale d'apparition du terme dans le corpus"
},
'growth_rate' : {
'col': "heatmap",
'binmode': 'samepop',
'n': 5,
'menutransl': 'Tendances et oubliés de la semaine'
},
'PageRank' : {
'col': "gradient",
'binmode': 'samerange',
'n': 6,
'menutransl': 'Importance dans le réseau, méthode Google',
},
'Modularity Class' : { // <== exemple with no binning
'col': "cluster",
'binmode': 'off',
'menutransl': 'Groupes de voisins, méthode des classes de modularité'
},
'Eigenvector Centrality':{
'col':"heatmap" ,
'binmode': 'samepop',
'n': 9,
'menutransl': 'Centralité par vecteurs propres'
},
'normfactor':{
'col':"gradient" ,
'binmode': 'samepop',
'n': 5,
'menutransl': 'Focused keywords'
},
'ACR': {
'col':"cluster" ,
'binmode': 'off',
'menutransl': 'Affiliation'
},
'country':{
'cluster_index' : {'col': "cluster" , 'binmode': 'off' },
'clust_louvain' : {'col': "cluster" , 'binmode': 'off',
'legend':'Louvain clustering' },
'country':{
'col':"cluster" ,
'binmode': 'off',
'menutransl': 'Country'
'legend': 'Country'
},
'nbjobs':{
'col':"heatmap" ,
'binmode': 'samerange',
'n': 5,
'menutransl': 'Number of related job ads'
},
'total_occurrences':{
'total_occurrences':{
'col':"heatmap" ,
'binmode': 'samerange',
'n': 3,
'menutransl': 'Total occurrences'
},
'numuniform' : {'col': "heatmap", 'n': 7, 'binmode': 'samepop' },
'numpareto' : {'col': "gradient", 'n': 5, 'binmode': 'samerange' },
'intfewvalues' : {'col': "cluster" , 'n': 4, 'binmode': 'samerange' },
'period' : {'col': "cluster" , 'binmode': 'off' },
'in-degree' : {'col': "heatmap" , 'n': 3, 'binmode': 'samepop' },
'cluster_index' : {'col': "cluster" , 'binmode': 'off' },
'cluster_label' : {'col': "cluster" , 'binmode': 'off' },
'betweeness' : {'col': "gradient", 'n': 4, 'binmode': 'samepop' },
'level' : {'col': "heatmap" , 'binmode': 'off' },
'weight' : {'col': "heatmap" , 'n': 5, 'binmode': 'samerange' },
'Weighted Degree' : {'col': "heatmap", 'n': 8, 'binmode': 'samerange' },
'out-degree' : {'col': "heatmap" , 'n': 3, 'binmode': 'samepop' },
'clust_louvain' : {'col': "cluster" , 'binmode': 'off',
'menutransl':'Louvain clustering' },
'cluster_universal_index': {'col': "cluster" , 'binmode': 'off' },
'community_orphan' : {'col': "cluster" , 'binmode': 'off' }
'legend': 'Total occurrences'
}
}
// NB other cases with no binning:
// NB automatic cases with no binning:
// - if data type is not numeric
// - if there is less than distinct values that facetOptions[attr][n]
// - if there is less than distinct values that maxDiscreteValues
// 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
// - if 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 ?
// when coloring method is "cluster", should the colors change each time ?
TWConf.randomizeClusterColors = true
// default clustering attribute (<---> used for initial node colors)
TWConf.nodeClusAtt = "modularity_class"
// for binning decision and nbins (fallbacks <=> if the attr is not in facetOptions)
// for binning decision and nbins (fallbacks if attr is not in facetOptions)
TWConf.maxDiscreteValues = 15
TWConf.legendsBins = 7
......@@ -249,7 +176,7 @@ TW.conf = (function(TW){
// if fa2Available, the auto-run config:
TWConf.fa2Enabled= true; // fa2 auto-run at start and after graph modified ?
TWConf.fa2Milliseconds=5000; // duration of auto-run
TWConf.fa2Milliseconds=4000; // duration of auto-run
TWConf.minNodesForAutoFA2 = 5 // graph size threshold to auto-run
......@@ -361,3 +288,5 @@ TW.conf = (function(TW){
return TWConf
})()
console.log("TW.conf load OK")
......@@ -1095,8 +1095,8 @@ var TinaWebJS = function ( sigmacanvas ) {
let madeDefaultColor = false
if (TW.conf.defaultColoring) {
let colMethodName, colMethod
if (TW.conf.facetOptions[TW.conf.defaultColoring]) {
colMethodName = TW.gui.colorFuns[TW.conf.facetOptions[TW.conf.defaultColoring]['col']]
if (TW.facetOptions[TW.conf.defaultColoring]) {
colMethodName = TW.gui.colorFuns[TW.facetOptions[TW.conf.defaultColoring]['col']]
}
if (! colMethodName) {
if(TW.conf.defaultColoring.indexOf("clust")>-1||TW.conf.defaultColoring.indexOf("class")>-1) {
......@@ -1116,9 +1116,9 @@ var TinaWebJS = function ( sigmacanvas ) {
madeDefaultColor = true
}
catch(err) {
console.warning(`Settings asked for defaultColoring by the
attribute "${TW.conf.defaultColoring}" but
it's not present in the dataset => skip action`)
console.warn(`Settings asked for defaultColoring by the
attribute "${TW.conf.defaultColoring}" but
it's not present in the dataset => skip action`)
}
}
}
......
......@@ -1003,7 +1003,11 @@ function NodeWeightFilter( sliderDivID , tgtNodeKey) {
// ids per weight level
// we use live index from prepareSigmaCustomIndices
let nodesByTypeNSize = TW.partialGraph.graph.getNodesBySize(tgtNodeKey)
var sortedSizes = Object.keys(nodesByTypeNSize).sort(function(a,b){return a-b})
var sortedSizes = []
if (nodesByTypeNSize)
sortedSizes = Object.keys(nodesByTypeNSize).sort(function(a,b){return a-b})
var stepToIdsArr = []
......
......@@ -20,7 +20,7 @@ TW.sigmaAttributes = {
// Execution: changeGraphAppearanceByFacets( true )
// It reads scanned node-attributes and prepared legends in TW.Clusters
// It reads scanned node-attributes and prepared legends in TW.Facets
// to add the button in the html with the sigmaUtils.gradientColoring(x) listener.
function changeGraphAppearanceByFacets(actypes) {
if(!TW.conf.colorByAtt) return;
......@@ -39,19 +39,20 @@ function changeGraphAppearanceByFacets(actypes) {
// each facet family or clustering type was already prepared
for (var attTitle in TW.Clusters[ty]) {
for (var attTitle in TW.Facets[ty]) {
// console.warn('changeGraphAppearanceByFacets:', ty, attTitle)
// note any previous louvains
if (attTitle == 'clust_louvain') gotPreviousLouvain = true
// attribute counts: nb of classes
// POSS here distinguish [ty][attTitle].classes.length and ranges.length
var attNbClasses = TW.Clusters[ty][attTitle].invIdx.length
var attNbClasses = TW.Facets[ty][attTitle].invIdx.length
var attNbNodes = currentNbNodes
if (attNbClasses) {
let lastClass = TW.Clusters[ty][attTitle].invIdx[attNbClasses-1]
let lastClass = TW.Facets[ty][attTitle].invIdx[attNbClasses-1]
if (lastClass.labl && /^_non_numeric_/.test(lastClass.labl) && lastClass.nids) {
if (lastClass.nids.length) {
attNbNodes -= lastClass.nids.length
......@@ -66,8 +67,8 @@ function changeGraphAppearanceByFacets(actypes) {
var colMethod
// read from user settings
if (TW.conf.facetOptions[attTitle] && TW.conf.facetOptions[attTitle]['col']) {
colMethod = TW.gui.colorFuns[TW.conf.facetOptions[attTitle]['col']]
if (TW.facetOptions[attTitle] && TW.facetOptions[attTitle]['col']) {
colMethod = TW.gui.colorFuns[TW.facetOptions[attTitle]['col']]
}
// fallback guess-values
......@@ -83,8 +84,8 @@ function changeGraphAppearanceByFacets(actypes) {
// family label :)
var attLabel ;
if (TW.conf.facetOptions[attTitle] && TW.conf.facetOptions[attTitle]['menutransl']) {
attLabel = TW.conf.facetOptions[attTitle]['menutransl']
if (TW.facetOptions[attTitle] && TW.facetOptions[attTitle]['legend']) {
attLabel = TW.facetOptions[attTitle]['legend']
}
else attLabel = attTitle
......@@ -105,7 +106,7 @@ function changeGraphAppearanceByFacets(actypes) {
$("#colorgraph-menu").html(color_menu_info)
}
// Legend slots were prepared in TW.Clusters
// Legend slots were prepared in TW.Facets
}
......@@ -147,13 +148,13 @@ function RunLouvain() {
for (let typ in louvainValNids) {
let reinvIdx = louvainValNids[typ]["clust_louvain"]['map']
// init a new legend in TW.Clusters
TW.Clusters[typ]['clust_louvain'] = {'meta':{}, 'invIdx':[]}
// init a new legend in TW.Facets
TW.Facets[typ]['clust_louvain'] = {'meta':{}, 'invIdx':[]}
for (let entry in reinvIdx) {
let len = reinvIdx[entry].length
if (len) {
TW.Clusters[typ]['clust_louvain'].invIdx.push({
TW.Facets[typ]['clust_louvain'].invIdx.push({
'labl': `cluster n°${entry} (${len})`,
'fullLabl': `${typ}||Louvain||${entry} (${len})`,
'nids': reinvIdx[entry],
......@@ -170,8 +171,8 @@ function RunLouvain() {
menu.innerHTML = nClasses
}
if (! TW.conf.facetOptions['clust_louvain']) {
TW.conf.facetOptions['clust_louvain'] = {'col': 'cluster'}
if (! TW.facetOptions['clust_louvain']) {
TW.facetOptions['clust_louvain'] = {'col': 'cluster'}
}
// NB the LouvainFait flag is updated by caller fun
}
......@@ -204,7 +205,7 @@ function SomeEffect( ValueclassCode ) {
// /!\ nodeset can be quite big
// we still filter it due to Level or sliders filters
filteredNodes = TW.Clusters[nodeType][cluType].invIdx[iClu].nids.filter(
filteredNodes = TW.Facets[nodeType][cluType].invIdx[iClu].nids.filter(
function(nid){
return Boolean(TW.partialGraph.graph.nodes(nid))
}
......@@ -239,6 +240,7 @@ function set_ClustersLegend ( daclass, groupedByTicks ) {
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")
......@@ -253,26 +255,26 @@ function set_ClustersLegend ( daclass, groupedByTicks ) {
var ClustNB_CurrentColor = {}
// passed as arg or prepared in parseCustom
if (!groupedByTicks && (!TW.Clusters[curType] || !TW.Clusters[curType][daclass])) {
if (!groupedByTicks && (!TW.Facets[curType] || !TW.Facets[curType][daclass])) {
console.warn(`no class bins for ${daclass}, displaying no legend`)
$("#legend-for-clusters").hide()
}
else {
let daclassLabel = daclass
if (TW.conf.facetOptions
&& TW.conf.facetOptions[daclass]
&& TW.conf.facetOptions[daclass].menutransl) {
daclassLabel = TW.conf.facetOptions[daclass].menutransl
if (TW.facetOptions
&& TW.facetOptions[daclass]
&& TW.facetOptions[daclass].legend) {
daclassLabel = TW.facetOptions[daclass].legend
}
var LegendDiv = ""
LegendDiv += ` <div class="legend-title">${daclassLabel}</div>`
LegendDiv += ' <div class="legend-scale">'
LegendDiv += ' <ul class="legend-labels">'
var legendInfo = groupedByTicks || TW.Clusters[curType][daclass].invIdx
var legendInfo = groupedByTicks || TW.Facets[curType][daclass].invIdx
// valueclasses (values or intervals or classes) are already sorted in TW.Clusters
// valueclasses (values or intervals or classes) are already sorted in TW.Facets
for (var l in legendInfo) {
// get a sample node color for each bin/class
......@@ -295,13 +297,13 @@ function set_ClustersLegend ( daclass, groupedByTicks ) {
}
// we add a title to cluster classes by ranking their nodes and taking k best labels, except if type is "social"
if (TW.conf.facetOptions[daclass] && TW.conf.facetOptions[daclass].col == 'cluster' && curType != TW.categories[1]) {
if (TW.facetOptions[daclass] && TW.facetOptions[daclass].col == 'cluster' && curType != TW.categories[1]) {
// let t0 = performance.now()
let titles = []
let theRankingAttr = TW.conf.facetOptions[daclass].titlingMetric
let maxLen = TW.conf.facetOptions[daclass].titlingNTerms || 2
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
......@@ -953,10 +955,10 @@ function fillAttrsInForm(menuId, optionalAttTypeConstraint) {
}
// each facet family or clustering type was already prepared
for (let att in TW.Clusters[ty]) {
for (let att in TW.Facets[ty]) {
if (!optionalAttTypeConstraint
|| ( TW.Clusters[ty][att].meta.dataType
&& TW.Clusters[ty][att].meta.dataType == optionalAttTypeConstraint)) {
|| ( TW.Facets[ty][att].meta.dataType
&& TW.Facets[ty][att].meta.dataType == optionalAttTypeConstraint)) {
let opt = document.createElement('option')
opt.value = att
opt.innerText = att
......@@ -1005,11 +1007,11 @@ function colChangedHandler() {
function showAttrConf() {
let attrTitle = this.value
let settings = TW.conf.facetOptions[attrTitle]
let settings = TW.facetOptions[attrTitle]
if (settings) {
document.getElementById('attr-col').value = settings.col || 'gradient'
document.getElementById('attr-binmode').value = settings.binmode || 'off'
document.getElementById('attr-translation').value = settings.menutransl || attrTitle
document.getElementById('attr-translation').value = settings.legend || attrTitle
if(settings.n) {
document.getElementById('attr-nbins-div').style.display = 'block'
document.getElementById('attr-nbins').value = settings.n || 5
......@@ -1033,11 +1035,11 @@ function newAttrConfAndColor() {
let attrTitle = document.getElementById('choose-attr').value
// read values from GUI
TW.conf.facetOptions[attrTitle] = {
TW.facetOptions[attrTitle] = {
'col': document.getElementById('attr-col').value,
'binmode': document.getElementById('attr-binmode').value,
'n': document.getElementById('attr-nbins').value,
'menutransl': document.getElementById('attr-translation').value,
'legend': document.getElementById('attr-translation').value,
// only for clusterings (ie currently <=> (col == "cluster"))
'titlingMetric': document.getElementById('attr-titling-metric').value,
......@@ -1046,8 +1048,8 @@ function newAttrConfAndColor() {
// find the corresponding types
let relevantTypes = {}
for (let ty in TW.Clusters) {
if (TW.Clusters[ty][attrTitle]) {
for (let ty in TW.Facets) {
if (TW.Facets[ty][attrTitle]) {
relevantTypes[ty] = true
}
}
......@@ -1063,9 +1065,9 @@ function newAttrConfAndColor() {
let newClustering = facetsBinning (tmpVals)
// write result to global TW.Clusters
// write result to global TW.Facets
for (let ty in newClustering) {
TW.Clusters[ty][attrTitle] = newClustering[ty][attrTitle]
TW.Facets[ty][attrTitle] = newClustering[ty][attrTitle]
}
// console.log("reparse raw result", tmpVals)
......@@ -1075,6 +1077,6 @@ function newAttrConfAndColor() {
changeGraphAppearanceByFacets()
// run the new color
let colMethod = TW.gui.colorFuns[TW.conf.facetOptions[attrTitle]['col']]
let colMethod = TW.gui.colorFuns[TW.facetOptions[attrTitle]['col']]
window[colMethod](attrTitle)
}
......@@ -23,6 +23,10 @@ TW.states = [TW.initialSystemState]
// SystemState() returns the current situation
TW.SystemState = function() { return TW.states.slice(-1)[0] }
// options for facetting the data attributes
// (project conf values will override these global conf defaults)
TW.facetOptions = TW.conf.defaultFacetOptions
// gracefully degrade our most costly settings if the user agent is mobile
if (/mobile/i.test(navigator.userAgent)) mobileAdaptConf()
......@@ -311,7 +315,7 @@ function mainStartGraph(inFormat, inData, twInstance) {
TW.Edges = [];
TW.ByType = {} // node ids sorted by nodetype id (0, 1)
TW.Relations = {} // edges sorted by source/target type id ("00", "11")
TW.Clusters = []; // "by value" facet index built in parseCustom
TW.Facets = []; // "by value" facet index built in parseCustom
TW.partialGraph = null // will contain the sigma visible graph instance
......@@ -327,6 +331,7 @@ function mainStartGraph(inFormat, inData, twInstance) {
else {
let optNodeTypes = null
let optRelDBs = null
let optProjectFacets = null
if (TW.sourcemode == "api") {
optNodeTypes = TW.conf.sourceAPI.nodetypes
......@@ -344,13 +349,20 @@ function mainStartGraph(inFormat, inData, twInstance) {
let srcBasename = pathsplit[2] ;
// try and retrieve associated conf
[optNodeTypes, optRelDBs] = readProjectConf(srcDirname, srcBasename)
[optNodeTypes,
optRelDBs,
optProjectFacets] = readProjectConf(srcDirname, srcBasename)
// export to globals for getTopPapers and makeRendererFromTemplate
if (optRelDBs) {
TW.currentRelDocsDBs = optRelDBs
TW.Project = srcDirname
}
// same for facet options
if (optProjectFacets) {
TW.facetOptions = Object.assign(TW.facetOptions, optProjectFacets)
}
}
}
......@@ -396,7 +408,7 @@ function mainStartGraph(inFormat, inData, twInstance) {
prepareNodesRenderingProperties(TW.Nodes)
prepareEdgesRenderingProperties(TW.Edges, TW.Nodes)
if (inData.clusters) TW.Clusters = inData.clusters
if (inData.clusters) TW.Facets = inData.clusters
// main console info
// ===================
......
......@@ -166,6 +166,7 @@ function readMenu(infofile) {
function readProjectConf(projectPath, filePath) {
let declaredNodetypes
let declaredDBConf
let declaredFacetsConf
// ££TODO declaredFacetOptions
......@@ -233,13 +234,23 @@ function readProjectConf(projectPath, filePath) {
}
}
}
// optional facets -----------------------
if (confEntry[ndtype].facets) {
if (! declaredFacetsConf) declaredFacetsConf = {}
// POSS store facets conf by type ?
declaredFacetsConf = Object.assign(
declaredFacetsConf,
confEntry[ndtype].facets
)
}
// ----------------------------------------
}
}
}
}
return [declaredNodetypes, declaredDBConf]
return [declaredNodetypes, declaredDBConf, declaredFacetsConf]
}
// settings: {norender: Bool}
......@@ -294,18 +305,19 @@ function cancelSelection (fromTagCloud, settings) {
// - that all typenames have a mapping to cat[0] (terms) or cat[1] (contexts)
// - that currentState.activetypes is an array of 2 bools for the currently displayed cat(s)
function getActivetypesNames() {
let currentTypes = []
let currentTypeIdx
let currentTypeNames = []
// for instance [true, false] if type0 is active
let activeFlags = TW.SystemState().activetypes
for (var possType in TW.catDict) {
currentTypeIdx = TW.catDict[possType]
if (TW.SystemState().activetypes[currentTypeIdx]) {
currentTypes.push(possType)
for (var i = 0 ; i < TW.categories.length ; i++) {
if (activeFlags[i]) {
currentTypeNames.push(TW.categories[i])
}
}
// ex: ['Document'] or ['Ngrams'] or ['Document','Ngrams']
return currentTypes
return currentTypeNames
}
function getActiverelsKey(someState) {
......
......@@ -266,10 +266,14 @@ function sortNodeTypes(observedTypesDict, optionalNodeConf) {
// NB: type for nodes0 will be the majoritary by default, unless taken
if (!newcats[0]) {
if (observedTypes[0] != newcats[1])
newcats[0] = observedTypes[0] // 0 is the most frequent here
else
newcats[0] = observedTypes[1] // 1 is second most frequent
if (observedTypes[0] != newcats[1]) {
newcats[0] = observedTypes[0] // 0 is the most frequent here
catDict[observedTypes[0]] = 0;
}
else {
newcats[0] = observedTypes[1] // 1 is second most frequent
catDict[observedTypes[1]] = 0;
}
}
// all the rest
......@@ -310,15 +314,10 @@ function facetsBinning (valuesIdx) {
let facetIdx = {}
if (TW.conf.debug.logFacets) {
console.log('facetsBinning: begin TW.Clusters')
console.log('facetsBinning: begin TW.Facets')
var classvalues_deb = performance.now()
}
// var gotClusters = false
// for (var nodecat in valuesIdx) {
// gotClusters = gotClusters || (valuesIdx[nodecat]['cluster_index'] || valuesIdx[nodecat][TW.conf.nodeClusAtt])
// }
// all scanned attributes get an inverted index
for (var cat in valuesIdx) {
if (!facetIdx[cat]) facetIdx[cat] = {}
......@@ -381,16 +380,16 @@ function facetsBinning (valuesIdx) {
// read stipulated options in user settings
// ----------------------------------------
if (TW.conf.facetOptions[at]) {
binningMode = TW.conf.facetOptions[at]["binmode"]
nBins = TW.conf.facetOptions[at]["n"]
if (TW.facetOptions[at]) {
binningMode = TW.facetOptions[at]["binmode"]
nBins = TW.facetOptions[at]["n"]
maxDiscreteValues = nBins
if (nBins == 0) {
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
}
if (TW.conf.debug.logFacets) console.log("TW.conf.facetOptions[at]", TW.conf.facetOptions[at])
if (TW.conf.debug.logFacets) console.log("TW.facetOptions[at]", TW.facetOptions[at])
}
else {
if (TW.conf.debug.logFacets) console.log("(no specified options in settings for this attribute)")
......@@ -407,7 +406,7 @@ function facetsBinning (valuesIdx) {
// if small number of distinct values doesn't need binify
if ( dataType == 'str'
|| (TW.conf.facetOptions[at] // case with custom facetOptions
|| (TW.facetOptions[at] // case with custom facetOptions
&& (nDistinctVals <= nBins || binningMode == "off"))
|| (nDistinctVals <= maxDiscreteValues ) // case with unspecified options
) {
......@@ -619,7 +618,7 @@ function facetsBinning (valuesIdx) {
if (TW.conf.debug.logFacets) {
var classvalues_fin = performance.now()
console.log('end TW.Clusters, own time:', classvalues_fin-classvalues_deb)
console.log('end TW.Facets, own time:', classvalues_fin-classvalues_deb)
}
return facetIdx
......@@ -671,7 +670,7 @@ function dictfyGexf( gexf , categories ){
// let sumSizes = 0
// let sizeStats = {'mean':null, 'median':null, 'max':0, 'min':1000000000}
// if scanClusters, we'll also use:
// if scanAttributes, we'll also use:
var tmpVals = {} // to build inverted index attval => nodes
// (to inventory subclasses for a given attr)
// if < maxDiscreteValues: keep all in legend
......@@ -826,7 +825,7 @@ function dictfyGexf( gexf , categories ){
// console.debug("node.attributes", node.attributes)
// creating a faceted index from node.attributes
if (TW.conf.scanClusters) {
if (TW.conf.scanAttributes) {
tmpVals = updateValueFacets(tmpVals, node)
}
......@@ -850,8 +849,8 @@ function dictfyGexf( gexf , categories ){
// clusters and other facets => type => name => [{label,val/range,nodeids}]
if (TW.conf.scanClusters) {
TW.Clusters = facetsBinning(tmpVals)
if (TW.conf.scanAttributes) {
TW.Facets = facetsBinning(tmpVals)
}
// linear rescale node sizes
......@@ -1089,7 +1088,7 @@ function dictfyJSON( data , categories ) {
let minNodeSize = Infinity
let maxNodeSize = 0
// if scanClusters, we'll also use:
// if scanAttributes, we'll also use:
var tmpVals = {}
for(var nid in data.nodes) {
......@@ -1144,7 +1143,7 @@ function dictfyJSON( data , categories ) {
}
// creating a faceted index from node.attributes
if (TW.conf.scanClusters) {
if (TW.conf.scanAttributes) {
tmpVals = updateValueFacets(tmpVals, node)
}
}
......@@ -1152,8 +1151,8 @@ function dictfyJSON( data , categories ) {
// test: json with string facet (eg lab affiliation in comex)
// console.log(tmpVals['Document'])
if (TW.conf.scanClusters) {
TW.Clusters = facetsBinning (tmpVals)
if (TW.conf.scanAttributes) {
TW.Facets = facetsBinning (tmpVals)
}
// £TODO ask if wanted
......
......@@ -742,7 +742,7 @@ function gradientColoring(daclass) {
repaintEdges()
// remember in clusters
let bins = TW.Clusters[getActivetypesNames()[0]][daclass]
let bins = TW.Facets[getActivetypesNames()[0]][daclass]
if (bins && bins.invIdx) {
for (var i in bins.invIdx) {
if (bins.invIdx[i].labl != '_non_numeric_') {
......@@ -769,7 +769,7 @@ function gradientColoring(daclass) {
}
// NB legend will group different possible values using
// precomputed ticks from TW.Clusters.terms[daclass]
// precomputed ticks from TW.Facets.terms[daclass]
set_ClustersLegend ( daclass)
TW.partialGraph.render();
......@@ -809,8 +809,8 @@ function repaintEdges() {
// heatmap from cold to warm with middle white
// (good for values centered around a neutral zone)
// NB - binning is done at parseCustom (cf. TW.Clusters)
// - number of bins can be specified by attribute name in TW.conf.facetOptions[daclass]["n"]
// NB - binning is done at parseCustom (cf. TW.Facets)
// - number of bins can be specified by attribute name in TW.facetOptions[daclass]["n"]
function heatmapColoring(daclass) {
var binColors
var doModifyLabel = false
......@@ -820,9 +820,9 @@ function heatmapColoring(daclass) {
let nColors = TW.conf.legendsBins || 5
// possible user value
if (TW.conf.facetOptions[daclass]) {
if (TW.conf.facetOptions[daclass]["n"] != 0) {
nColors = TW.conf.facetOptions[daclass]["n"]
if (TW.facetOptions[daclass]) {
if (TW.facetOptions[daclass]["n"] != 0) {
nColors = TW.facetOptions[daclass]["n"]
}
else {
console.warn(`Can't use user-specified number of bins value 0 for attribute ${at}, using TW.conf.legendsBins ${TW.conf.legendsBins} instead`)
......@@ -837,11 +837,11 @@ function heatmapColoring(daclass) {
var ty = actypes[0]
// our binning
var tickThresholds = TW.Clusters[ty][daclass].invIdx
var tickThresholds = TW.Facets[ty][daclass].invIdx
// verifications
if (tickThresholds.length - 1 != nColors) {
console.warn (`heatmapColoring setup mismatch: TW.Clusters ticks ${tickThresholds.length} - 1 non_numeric from scanClusters should == nColors ${nColors}`)
console.warn (`heatmapColoring setup mismatch: TW.Facets ticks ${tickThresholds.length} - 1 non_numeric from scanAttributes should == nColors ${nColors}`)
nColors = tickThresholds.length - 1
}
......@@ -946,7 +946,7 @@ function clusterColoring(daclass) {
let nColors = TW.gui.colorList.length
let facets = TW.Clusters[getActivetypesNames()[0]][daclass]
let facets = TW.Facets[getActivetypesNames()[0]][daclass]
if (facets && facets.invIdx) {
for (var i in facets.invIdx) {
let valGroup = facets.invIdx[i]
......@@ -978,11 +978,11 @@ function clusterColoring(daclass) {
}
}
// remember in TW.Clusters
// remember in TW.Facets
valGroup.col = theColor
}
}
// fallback on old, slower strategy if scanClusters inactive
// fallback on old, slower strategy if scanAttributes inactive
else {
for(var nid in TW.Nodes) {
var the_node = TW.partialGraph.graph.nodes(nid)
......@@ -1067,7 +1067,7 @@ function mobileAdaptConf() {
TW.conf.sigmaJsDrawingProperties.mouseZoomDuration = 0
// TW.conf.sigmaJsDrawingProperties.defaultEdgeType = 'line'
// TW.conf.scanClusters = false
// TW.conf.scanAttributes = false
// TW.conf.twRendering = false
// £TODO better CSS for histogram on mobile
......
......@@ -51,125 +51,72 @@ TW.conf = (function(TW){
// =======================
// to process node attributes values from data
// => colors (continuous numeric attributes)
// => clusters (discrete numeric or str attributes),
// => clusters (discrete numeric or str attributes)
// cf. also "Configuring facets" in the doc under Introduction/project_config
// create facets ?
TWConf.scanClusters = true
TWConf.scanAttributes = true
// use a facet for default color
TWConf.defaultColoring = null
// facetOptions: choose here the visual result of your node attributes
// -------------------------------------------------------------------
// 3 possible coloring functions
// - cluster (contrasted colors for attributes describing *classes*)
// - gradient (uniform map from a numeric attribute to red/yellow gradient)
// - heatmap (from blue to red, centered on a white "neutral" color)
// 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)
TWConf.facetOptions = {
TWConf.defaultColoring = "clust_louvain"
// facetOptions: choose here the default visual result of your node attributes
// ---------------------------------------------------------------------------
// (values overridden by data/myproject/project_conf.json "facets" if present)
TWConf.defaultFacetOptions = {
// attr title
'age' : {
'col': "gradient", // coloring function
'binmode': 'samerange', // binning mode
'n': 4, // custom number of bins
'menutransl': "Date initiale d'apparition du terme dans le corpus"
},
'growth_rate' : {
'col': "heatmap",
'binmode': 'samepop',
'n': 5,
'menutransl': 'Tendances et oubliés de la semaine'
},
'PageRank' : {
'col': "gradient",
'binmode': 'samerange',
'n': 6,
'menutransl': 'Importance dans le réseau, méthode Google',
},
'Modularity Class' : { // <== exemple with no binning
'col': "cluster",
'cluster_index' : {'col': "cluster" , 'binmode': 'off' },
'clust_louvain' : {'col': "cluster" , 'binmode': 'off',
'legend':'Louvain clustering' },
'country':{
'col':"cluster" ,
'binmode': 'off',
'menutransl': 'Groupes de voisins, méthode des classes de modularité'
'legend': 'Country'
},
'Eigenvector Centrality':{
'normfactor':{
'col':"heatmap" ,
'binmode': 'samepop',
'n': 9,
'menutransl': 'Centralité par vecteurs propres'
},
'normfactor':{
'col':"gradient" ,
'binmode': 'samepop',
'n': 5,
'menutransl': 'Focused keywords'
'legend': 'Focused keywords'
},
'ACR': {
'ACR':{
'col':"cluster" ,
'binmode': 'off',
'menutransl': 'Affiliation'
'legend': 'Affiliation'
},
'country':{
'col':"cluster" ,
'binmode': 'off',
'menutransl': 'Country'
},
'nbjobs':{
'nbjobs':{
'col':"heatmap" ,
'binmode': 'samerange',
'n': 3,
'menutransl': 'Number of related job ads'
'n': 5,
'legend': 'Number of related job ads'
},
'total_occurrences':{
'total_occurrences':{
'col':"heatmap" ,
'binmode': 'samerange',
'n': 4,
'menutransl': 'Total occurrences'
},
'numuniform' : {'col': "heatmap", 'n': 7, 'binmode': 'samepop' },
'numpareto' : {'col': "gradient", 'n': 5, 'binmode': 'samerange' },
'intfewvalues' : {'col': "cluster" , 'n': 4, 'binmode': 'samerange' },
'period' : {'col': "cluster" , 'binmode': 'off' },
'in-degree' : {'col': "heatmap" , 'n': 3, 'binmode': 'samepop' },
'cluster_index' : {'col': "cluster" , 'binmode': 'off' },
'cluster_label' : {'col': "cluster" , 'binmode': 'off' },
'betweeness' : {'col': "gradient", 'n': 4, 'binmode': 'samepop' },
'level' : {'col': "heatmap" , 'binmode': 'off' },
'weight' : {'col': "heatmap" , 'n': 5, 'binmode': 'samerange' },
'Weighted Degree' : {'col': "heatmap", 'n': 8, 'binmode': 'samerange' },
'out-degree' : {'col': "heatmap" , 'n': 3, 'binmode': 'samepop' },
'clust_louvain' : {'col': "cluster" , 'binmode': 'off',
'menutransl':'Louvain clustering' },
'cluster_universal_index': {'col': "cluster" , 'binmode': 'off' },
'community_orphan' : {'col': "cluster" , 'binmode': 'off' }
'n': 3,
'legend': 'Total occurrences'
}
}
// NB we keep the defaults here for API sourcemode as it has no "project_conf"
// NB other cases with no binning:
// NB automatic cases with no binning:
// - if data type is not numeric
// - if there is less than distinct values that facetOptions[attr][n]
// - if there is less than distinct values that maxDiscreteValues
// 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
// - if 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 ?
// when coloring method is "cluster", should the colors change each time ?
TWConf.randomizeClusterColors = true
// default clustering attribute (<---> used for initial node colors)
TWConf.nodeClusAtt = "modularity_class"
// for binning decision and nbins (fallbacks <=> if the attr is not in facetOptions)
// for binning decision and nbins (fallbacks if attr is not in facetOptions)
TWConf.maxDiscreteValues = 15
TWConf.legendsBins = 7
......@@ -231,13 +178,6 @@ TW.conf = (function(TW){
TWConf.histogramStartThreshold = 10 ; // for daily histo module
// (from how many docs are significant)
// £TODO these exist only in git branches
// (geomap: ademe, timeline: tweetoscope)
// ==> ask if need to be restored
// TW.geomap = false;
// TW.twittertimeline = false;
TWConf.maxPastStates = 5 ; // number of TW.states to remember (~CTRL-Z)
......@@ -361,3 +301,5 @@ TW.conf = (function(TW){
return TWConf
})()
console.log("TW.conf load OK")
......@@ -51,125 +51,52 @@ TW.conf = (function(TW){
// =======================
// to process node attributes values from data
// => colors (continuous numeric attributes)
// => clusters (discrete numeric or str attributes),
// => clusters (discrete numeric or str attributes)
// cf. also "Configuring facets" in the doc under Introduction/project_config
// create facets ?
TWConf.scanClusters = true
TWConf.scanAttributes = true
// use a facet for default color
TWConf.defaultColoring = "clust_louvain"
// facetOptions: choose here the visual result of your node attributes
// -------------------------------------------------------------------
// 3 possible coloring functions
// - cluster (contrasted colors for attributes describing *classes*)
// - gradient (uniform map from a numeric attribute to red/yellow gradient)
// - heatmap (from blue to red, centered on a white "neutral" color)
// 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)
TWConf.facetOptions = {
// facetOptions: choose here the default visual result of your node attributes
// ---------------------------------------------------------------------------
// (values overridden by data/myproject/project_conf.json "facets" if present)
TWConf.defaultFacetOptions = {
// attr title
'age' : {
'col': "gradient", // coloring function
'binmode': 'samerange', // binning mode
'n': 4, // custom number of bins
'menutransl': "Date initiale d'apparition du terme dans le corpus"
},
'growth_rate' : {
'col': "heatmap",
'binmode': 'samepop',
'n': 5,
'menutransl': 'Tendances et oubliés de la semaine'
},
'PageRank' : {
'col': "gradient",
'binmode': 'samerange',
'n': 6,
'menutransl': 'Importance dans le réseau, méthode Google',
},
'Modularity Class' : { // <== exemple with no binning
'col': "cluster",
'binmode': 'off',
'menutransl': 'Groupes de voisins, méthode des classes de modularité'
},
'Eigenvector Centrality':{
'col':"heatmap" ,
'binmode': 'samepop',
'n': 9,
'menutransl': 'Centralité par vecteurs propres'
},
'normfactor':{
'col':"gradient" ,
'binmode': 'samepop',
'n': 5,
'menutransl': 'Focused keywords'
},
'ACR': {
'col':"cluster" ,
'binmode': 'off',
'menutransl': 'Affiliation'
},
'country':{
'cluster_index' : {'col': "cluster" , 'binmode': 'off' },
'clust_louvain' : {'col': "cluster" , 'binmode': 'off',
'legend':'Louvain clustering' },
'country':{
'col':"cluster" ,
'binmode': 'off',
'menutransl': 'Country'
'legend': 'Country'
},
'nbjobs':{
'col':"heatmap" ,
'binmode': 'samerange',
'n': 5,
'menutransl': 'Number of related job ads'
},
'total_occurrences':{
'total_occurrences':{
'col':"heatmap" ,
'binmode': 'samerange',
'n': 3,
'menutransl': 'Total occurrences'
},
'numuniform' : {'col': "heatmap", 'n': 7, 'binmode': 'samepop' },
'numpareto' : {'col': "gradient", 'n': 5, 'binmode': 'samerange' },
'intfewvalues' : {'col': "cluster" , 'n': 4, 'binmode': 'samerange' },
'period' : {'col': "cluster" , 'binmode': 'off' },
'in-degree' : {'col': "heatmap" , 'n': 3, 'binmode': 'samepop' },
'cluster_index' : {'col': "cluster" , 'binmode': 'off' },
'cluster_label' : {'col': "cluster" , 'binmode': 'off' },
'betweeness' : {'col': "gradient", 'n': 4, 'binmode': 'samepop' },
'level' : {'col': "heatmap" , 'binmode': 'off' },
'weight' : {'col': "heatmap" , 'n': 5, 'binmode': 'samerange' },
'Weighted Degree' : {'col': "heatmap", 'n': 8, 'binmode': 'samerange' },
'out-degree' : {'col': "heatmap" , 'n': 3, 'binmode': 'samepop' },
'clust_louvain' : {'col': "cluster" , 'binmode': 'off',
'menutransl':'Louvain clustering' },
'cluster_universal_index': {'col': "cluster" , 'binmode': 'off' },
'community_orphan' : {'col': "cluster" , 'binmode': 'off' }
'legend': 'Total occurrences'
}
}
// NB other cases with no binning:
// NB automatic cases with no binning:
// - if data type is not numeric
// - if there is less than distinct values that facetOptions[attr][n]
// - if there is less than distinct values that maxDiscreteValues
// 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
// - if 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 ?
// when coloring method is "cluster", should the colors change each time ?
TWConf.randomizeClusterColors = true
// default clustering attribute (<---> used for initial node colors)
TWConf.nodeClusAtt = "modularity_class"
// for binning decision and nbins (fallbacks <=> if the attr is not in facetOptions)
// for binning decision and nbins (fallbacks if attr is not in facetOptions)
TWConf.maxDiscreteValues = 15
TWConf.legendsBins = 7
......@@ -249,7 +176,7 @@ TW.conf = (function(TW){
// if fa2Available, the auto-run config:
TWConf.fa2Enabled= true; // fa2 auto-run at start and after graph modified ?
TWConf.fa2Milliseconds=5000; // duration of auto-run
TWConf.fa2Milliseconds=4000; // duration of auto-run
TWConf.minNodesForAutoFA2 = 5 // graph size threshold to auto-run
......
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