Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
P
purescript-gargantext
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Grégoire Locqueville
purescript-gargantext
Commits
33609eac
Commit
33609eac
authored
Dec 24, 2019
by
Przemyslaw Kaminski
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
[Graph] add Louvain clustering
parent
6109c49c
Changes
10
Show whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
501 additions
and
16 deletions
+501
-16
packages.json
.psc-package/local/.set/packages.json
+10
-0
packages.dhall
packages.dhall
+5
-0
psc-package.json
psc-package.json
+1
-0
Graph.purs
src/Gargantext/Components/Graph.purs
+2
-2
GraphExplorer.purs
src/Gargantext/Components/GraphExplorer.purs
+9
-10
Controls.purs
src/Gargantext/Components/GraphExplorer/Controls.purs
+6
-2
ToggleButton.purs
src/Gargantext/Components/GraphExplorer/ToggleButton.purs
+11
-1
Louvain.js
src/Gargantext/Data/Louvain.js
+384
-0
Louvain.purs
src/Gargantext/Data/Louvain.purs
+36
-0
Types.purs
src/Gargantext/Hooks/Sigmax/Types.purs
+37
-1
No files found.
.psc-package/local/.set/packages.json
View file @
33609eac
...
...
@@ -3069,6 +3069,16 @@
"repo"
:
"https://github.com/purescript/purescript-tuples.git"
,
"version"
:
"v5.1.0"
},
"tuples-native"
:
{
"dependencies"
:
[
"generics-rep"
,
"prelude"
,
"typelevel"
,
"unsafe-coerce"
],
"repo"
:
"https://github.com/athanclark/purescript-tuples-native"
,
"version"
:
"v2.0.1"
},
"type-equality"
:
{
"dependencies"
:
[],
"repo"
:
"https://github.com/purescript/purescript-type-equality.git"
,
...
...
packages.dhall
View file @
33609eac
...
...
@@ -204,6 +204,11 @@ let additions =
]
"https://github.com/irresponsible/purescript-reactix"
"v0.4.2"
, tuples-native =
mkPackage
[ "generics-rep", "prelude", "typelevel", "unsafe-coerce" ]
"https://github.com/athanclark/purescript-tuples-native"
"v2.0.1"
, uint =
mkPackage
[ "maybe", "math", "generics-rep" ]
...
...
psc-package.json
View file @
33609eac
...
...
@@ -33,6 +33,7 @@
"string-parsers"
,
"strings"
,
"thermite"
,
"tuples-native"
,
"uint"
,
"uri"
,
"web-html"
...
...
src/Gargantext/Components/Graph.purs
View file @
33609eac
...
...
@@ -9,6 +9,7 @@ import Prelude (bind, const, discard, not, pure, unit, ($))
import Data.Either (Either(..))
import Data.Maybe (Maybe(..))
import Data.Nullable (Nullable)
import Data.Sequence as Seq
import Data.Tuple.Nested ((/\))
import DOM.Simple.Console (log, log2)
import DOM.Simple.Types (Element)
...
...
@@ -85,8 +86,7 @@ graphCpt = R.hooksComponent "G.C.Graph" cpt
Sigmax.setEdges sig false
Sigma.startForceAtlas2 sig props.forceAtlas2Settings
louvain <- Louvain.init unit
log2 "[graphCpt] louvain" louvain
pure unit
Just sig -> do
pure unit
...
...
src/Gargantext/Components/GraphExplorer.purs
View file @
33609eac
...
...
@@ -18,6 +18,7 @@ import Partial.Unsafe (unsafePartial)
import Reactix as R
import Reactix.DOM.HTML as RH
import Gargantext.Data.Louvain as Louvain
import Gargantext.Hooks.Loader (useLoader)
import Gargantext.Hooks.Sigmax as Sigmax
import Gargantext.Hooks.Sigmax.Types as SigmaxTypes
...
...
@@ -178,7 +179,14 @@ graphViewCpt = R.hooksComponent "GraphView" cpt
where
cpt {controls, elRef, graphId, graph, multiSelectEnabledRef} _children = do
-- TODO Cache this?
let transformedGraph = transformGraph controls graph
let louvainGraph =
if (fst controls.showLouvain) then
let louvain = Louvain.louvain unit in
let cluster = Louvain.init louvain (SigmaxTypes.louvainNodes graph) (SigmaxTypes.louvainEdges graph) in
SigmaxTypes.louvainGraph graph cluster
else
graph
let transformedGraph = transformGraph controls louvainGraph
R.useEffect1' (fst controls.multiSelectEnabled) $ do
R.setRef multiSelectEnabledRef $ fst controls.multiSelectEnabled
...
...
@@ -232,15 +240,6 @@ convert (GET.GraphData r) = Tuple r.metaData $ SigmaxTypes.Graph {nodes, edges}
targetNode = unsafePartial $ fromJust $ Map.lookup e.target nodesMap
color = sourceNode.color
defaultPalette :: Array String
defaultPalette = ["#5fa571","#ab9ba2","#da876d","#bdd3ff"
,"#b399df","#ffdfed","#33c8f3","#739e9a"
,"#caeca3","#f6f7e5","#f9bcca","#ccb069"
,"#c9ffde","#c58683","#6c9eb0","#ffd3cf"
,"#ccffc7","#52a1b0","#d2ecff","#99fffe"
,"#9295ae","#5ea38b","#fff0b3","#d99e68"
]
-- clusterColor :: Cluster -> Color
-- clusterColor (Cluster {clustDefault}) = unsafePartial $ fromJust $ defaultPalette !! (clustDefault `molength defrultPalette)
...
...
src/Gargantext/Components/GraphExplorer/Controls.purs
View file @
33609eac
...
...
@@ -24,7 +24,7 @@ import Gargantext.Components.GraphExplorer.Button (centerButton)
import Gargantext.Components.GraphExplorer.RangeControl (edgeConfluenceControl, edgeWeightControl, nodeSizeControl)
import Gargantext.Components.GraphExplorer.Search (nodeSearchControl)
import Gargantext.Components.GraphExplorer.SlideButton (labelSizeButton, mouseSelectorSizeButton)
import Gargantext.Components.GraphExplorer.ToggleButton (multiSelectEnabledButton, edgesToggleButton, pauseForceAtlasButton)
import Gargantext.Components.GraphExplorer.ToggleButton (multiSelectEnabledButton, edgesToggleButton,
louvainToggleButton,
pauseForceAtlasButton)
import Gargantext.Components.GraphExplorer.Types as GET
import Gargantext.Hooks.Sigmax as Sigmax
import Gargantext.Hooks.Sigmax.Types as SigmaxTypes
...
...
@@ -42,6 +42,7 @@ type Controls =
, selectedNodeIds :: R.State SigmaxTypes.SelectedNodeIds
, showControls :: R.State Boolean
, showEdges :: R.State SigmaxTypes.ShowEdgesState
, showLouvain :: R.State Boolean
, showSidePanel :: R.State GET.SidePanelState
, showTree :: R.State Boolean
, sigmaRef :: R.Ref Sigmax.Sigma
...
...
@@ -134,6 +135,7 @@ controlsCpt = R.hooksComponent "GraphControls" cpt
RH.li {} [ centerButton props.sigmaRef ]
, RH.li {} [ pauseForceAtlasButton {state: props.forceAtlasState} ]
, RH.li {} [ edgesToggleButton {state: props.showEdges} ]
, RH.li {} [ louvainToggleButton props.showLouvain ]
, RH.li {} [ edgeConfluenceControl edgeConfluenceRange props.edgeConfluence ]
, RH.li {} [ edgeWeightControl edgeWeightRange props.edgeWeight ]
-- change level
...
...
@@ -161,11 +163,12 @@ useGraphControls graph = do
graphStage <- R.useState' Graph.Init
multiSelectEnabled <- R.useState' false
nodeSize <- R.useState' $ Range.Closed { min: 0.0, max: 100.0 }
showTree <- R.useState' false
selectedNodeIds <- R.useState' $ Set.empty
showControls <- R.useState' false
showEdges <- R.useState' SigmaxTypes.EShow
showLouvain <- R.useState' false
showSidePanel <- R.useState' GET.InitialClosed
showTree <- R.useState' false
sigma <- Sigmax.initSigma
sigmaRef <- R.useRef sigma
...
...
@@ -179,6 +182,7 @@ useGraphControls graph = do
, selectedNodeIds
, showControls
, showEdges
, showLouvain
, showSidePanel
, showTree
, sigmaRef
...
...
src/Gargantext/Components/GraphExplorer/ToggleButton.purs
View file @
33609eac
...
...
@@ -2,9 +2,10 @@ module Gargantext.Components.GraphExplorer.ToggleButton
( Props
, toggleButton
, toggleButtonCpt
, multiSelectEnabledButton
, controlsToggleButton
, edgesToggleButton
, louvainToggleButton
, multiSelectEnabledButton
, sidebarToggleButton
, pauseForceAtlasButton
, treeToggleButton
...
...
@@ -78,6 +79,15 @@ edgesToggleButtonCpt = R.hooksComponent "EdgesToggleButton" cpt
-- TODO: Move this to Graph.purs to the R.useEffect handler which renders nodes/edges
onClick setState _ = setState SigmaxTypes.toggleShowEdgesState
louvainToggleButton :: R.State Boolean -> R.Element
louvainToggleButton state =
toggleButton {
state: state
, onMessage: "Louvain on"
, offMessage: "Louvain off"
, onClick: \_ -> snd state not
}
multiSelectEnabledButton :: R.State Boolean -> R.Element
multiSelectEnabledButton state =
toggleButton {
...
...
src/Gargantext/Data/Louvain.js
0 → 100755
View file @
33609eac
/*
Author: Corneliu S. (github.com/upphiminn)
This is a javascript implementation of the Louvain
community detection algorithm (http://arxiv.org/abs/0803.0476)
Based on https://bitbucket.org/taynaud/python-louvain/overview
*/
exports
.
_jLouvain
=
(
function
(){
return
function
(){
//Constants
var
__PASS_MAX
=
-
1
var
__MIN
=
0.0000001
//Local vars
var
original_graph_nodes
;
var
original_graph_edges
;
var
original_graph
=
{};
var
partition_init
;
//Helpers
function
make_set
(
array
){
var
set
=
{};
array
.
forEach
(
function
(
d
,
i
){
set
[
d
]
=
true
;
});
return
Object
.
keys
(
set
);
};
function
obj_values
(
obj
){
var
vals
=
[];
for
(
var
key
in
obj
)
{
if
(
obj
.
hasOwnProperty
(
key
)
)
{
vals
.
push
(
obj
[
key
]);
}
}
return
vals
;
};
function
get_degree_for_node
(
graph
,
node
){
var
neighbours
=
graph
.
_assoc_mat
[
node
]
?
Object
.
keys
(
graph
.
_assoc_mat
[
node
])
:
[];
var
weight
=
0
;
neighbours
.
forEach
(
function
(
neighbour
,
i
){
var
value
=
graph
.
_assoc_mat
[
node
][
neighbour
]
||
1
;
if
(
node
==
neighbour
)
value
*=
2
;
weight
+=
value
;
});
return
weight
;
};
function
get_neighbours_of_node
(
graph
,
node
){
if
(
typeof
graph
.
_assoc_mat
[
node
]
==
'undefined'
)
return
[];
var
neighbours
=
Object
.
keys
(
graph
.
_assoc_mat
[
node
]);
return
neighbours
;
}
function
get_edge_weight
(
graph
,
node1
,
node2
){
return
graph
.
_assoc_mat
[
node1
]
?
graph
.
_assoc_mat
[
node1
][
node2
]
:
undefined
;
}
function
get_graph_size
(
graph
){
var
size
=
0
;
graph
.
edges
.
forEach
(
function
(
edge
){
size
+=
edge
.
weight
;
});
return
size
;
}
function
add_edge_to_graph
(
graph
,
edge
){
update_assoc_mat
(
graph
,
edge
);
var
edge_index
=
graph
.
edges
.
map
(
function
(
d
){
return
d
.
source
+
'_'
+
d
.
target
;
}).
indexOf
(
edge
.
source
+
'_'
+
edge
.
target
);
if
(
edge_index
!=
-
1
)
graph
.
edges
[
edge_index
].
weight
=
edge
.
weight
;
else
graph
.
edges
.
push
(
edge
);
}
function
make_assoc_mat
(
edge_list
){
var
mat
=
{};
edge_list
.
forEach
(
function
(
edge
,
i
){
mat
[
edge
.
source
]
=
mat
[
edge
.
source
]
||
{};
mat
[
edge
.
source
][
edge
.
target
]
=
edge
.
weight
;
mat
[
edge
.
target
]
=
mat
[
edge
.
target
]
||
{};
mat
[
edge
.
target
][
edge
.
source
]
=
edge
.
weight
;
});
return
mat
;
}
function
update_assoc_mat
(
graph
,
edge
){
graph
.
_assoc_mat
[
edge
.
source
]
=
graph
.
_assoc_mat
[
edge
.
source
]
||
{};
graph
.
_assoc_mat
[
edge
.
source
][
edge
.
target
]
=
edge
.
weight
;
graph
.
_assoc_mat
[
edge
.
target
]
=
graph
.
_assoc_mat
[
edge
.
target
]
||
{};
graph
.
_assoc_mat
[
edge
.
target
][
edge
.
source
]
=
edge
.
weight
;
}
function
clone
(
obj
){
if
(
obj
==
null
||
typeof
(
obj
)
!=
'object'
)
return
obj
;
var
temp
=
obj
.
constructor
();
for
(
var
key
in
obj
)
temp
[
key
]
=
clone
(
obj
[
key
]);
return
temp
;
}
//Core-Algorithm Related
function
init_status
(
graph
,
status
,
part
){
status
[
'nodes_to_com'
]
=
{};
status
[
'total_weight'
]
=
0
;
status
[
'internals'
]
=
{};
status
[
'degrees'
]
=
{};
status
[
'gdegrees'
]
=
{};
status
[
'loops'
]
=
{};
status
[
'total_weight'
]
=
get_graph_size
(
graph
);
if
(
typeof
part
==
'undefined'
){
graph
.
nodes
.
forEach
(
function
(
node
,
i
){
status
.
nodes_to_com
[
node
]
=
i
;
var
deg
=
get_degree_for_node
(
graph
,
node
);
if
(
deg
<
0
)
throw
'Bad graph type, use positive weights!'
;
status
.
degrees
[
i
]
=
deg
;
status
.
gdegrees
[
node
]
=
deg
;
status
.
loops
[
node
]
=
get_edge_weight
(
graph
,
node
,
node
)
||
0
;
status
.
internals
[
i
]
=
status
.
loops
[
node
];
});
}
else
{
graph
.
nodes
.
forEach
(
function
(
node
,
i
){
var
com
=
part
[
node
];
status
.
nodes_to_com
[
node
]
=
com
;
var
deg
=
get_degree_for_node
(
graph
,
node
);
status
.
degrees
[
com
]
=
(
status
.
degrees
[
com
]
||
0
)
+
deg
;
status
.
gdegrees
[
node
]
=
deg
;
var
inc
=
0.0
;
var
neighbours
=
get_neighbours_of_node
(
graph
,
node
);
neighbours
.
forEach
(
function
(
neighbour
,
i
){
var
weight
=
graph
.
_assoc_mat
[
node
][
neighbour
];
if
(
weight
<=
0
){
throw
"Bad graph type, use positive weights"
;
}
if
(
part
[
neighbour
]
==
com
){
if
(
neighbour
==
node
){
inc
+=
weight
;
}
else
{
inc
+=
weight
/
2.0
;
}
}
});
status
.
internals
[
com
]
=
(
status
.
internals
[
com
]
||
0
)
+
inc
;
});
}
}
function
__modularity
(
status
){
var
links
=
status
.
total_weight
;
var
result
=
0.0
;
var
communities
=
make_set
(
obj_values
(
status
.
nodes_to_com
));
communities
.
forEach
(
function
(
com
,
i
){
var
in_degree
=
status
.
internals
[
com
]
||
0
;
var
degree
=
status
.
degrees
[
com
]
||
0
;
if
(
links
>
0
){
result
=
result
+
in_degree
/
links
-
Math
.
pow
((
degree
/
(
2.0
*
links
)),
2
);
}
});
return
result
;
}
function
__neighcom
(
node
,
graph
,
status
){
// compute the communities in the neighb. of the node, with the graph given by
// node_to_com
var
weights
=
{};
var
neighboorhood
=
get_neighbours_of_node
(
graph
,
node
);
//make iterable;
neighboorhood
.
forEach
(
function
(
neighbour
,
i
){
if
(
neighbour
!=
node
){
var
weight
=
graph
.
_assoc_mat
[
node
][
neighbour
]
||
1
;
var
neighbourcom
=
status
.
nodes_to_com
[
neighbour
];
weights
[
neighbourcom
]
=
(
weights
[
neighbourcom
]
||
0
)
+
weight
;
}
});
return
weights
;
}
function
__insert
(
node
,
com
,
weight
,
status
){
//insert node into com and modify status
status
.
nodes_to_com
[
node
]
=
+
com
;
status
.
degrees
[
com
]
=
(
status
.
degrees
[
com
]
||
0
)
+
(
status
.
gdegrees
[
node
]
||
0
);
status
.
internals
[
com
]
=
(
status
.
internals
[
com
]
||
0
)
+
weight
+
(
status
.
loops
[
node
]
||
0
);
}
function
__remove
(
node
,
com
,
weight
,
status
){
//remove node from com and modify status
status
.
degrees
[
com
]
=
((
status
.
degrees
[
com
]
||
0
)
-
(
status
.
gdegrees
[
node
]
||
0
));
status
.
internals
[
com
]
=
((
status
.
internals
[
com
]
||
0
)
-
weight
-
(
status
.
loops
[
node
]
||
0
));
status
.
nodes_to_com
[
node
]
=
-
1
;
}
function
__renumber
(
dict
){
var
count
=
0
;
var
ret
=
clone
(
dict
);
//deep copy :)
var
new_values
=
{};
var
dict_keys
=
Object
.
keys
(
dict
);
dict_keys
.
forEach
(
function
(
key
){
var
value
=
dict
[
key
];
var
new_value
=
typeof
new_values
[
value
]
==
'undefined'
?
-
1
:
new_values
[
value
];
if
(
new_value
==
-
1
){
new_values
[
value
]
=
count
;
new_value
=
count
;
count
=
count
+
1
;
}
ret
[
key
]
=
new_value
;
});
return
ret
;
}
function
__one_level
(
graph
,
status
){
//Compute one level of the Communities Dendogram.
var
modif
=
true
,
nb_pass_done
=
0
,
cur_mod
=
__modularity
(
status
),
new_mod
=
cur_mod
;
while
(
modif
&&
nb_pass_done
!=
__PASS_MAX
){
cur_mod
=
new_mod
;
modif
=
false
;
nb_pass_done
+=
1
graph
.
nodes
.
forEach
(
function
(
node
,
i
){
var
com_node
=
status
.
nodes_to_com
[
node
];
var
degc_totw
=
(
status
.
gdegrees
[
node
]
||
0
)
/
(
status
.
total_weight
*
2.0
);
var
neigh_communities
=
__neighcom
(
node
,
graph
,
status
);
__remove
(
node
,
com_node
,
(
neigh_communities
[
com_node
]
||
0.0
),
status
);
var
best_com
=
com_node
;
var
best_increase
=
0
;
var
neigh_communities_entries
=
Object
.
keys
(
neigh_communities
);
//make iterable;
neigh_communities_entries
.
forEach
(
function
(
com
,
i
){
var
incr
=
neigh_communities
[
com
]
-
(
status
.
degrees
[
com
]
||
0.0
)
*
degc_totw
;
if
(
incr
>
best_increase
){
best_increase
=
incr
;
best_com
=
com
;
}
});
__insert
(
node
,
best_com
,
neigh_communities
[
best_com
]
||
0
,
status
);
if
(
best_com
!=
com_node
)
modif
=
true
;
});
new_mod
=
__modularity
(
status
);
if
(
new_mod
-
cur_mod
<
__MIN
)
break
;
}
}
function
induced_graph
(
partition
,
graph
){
var
ret
=
{
nodes
:[],
edges
:[],
_assoc_mat
:
{}};
var
w_prec
,
weight
;
//add nodes from partition values
var
partition_values
=
obj_values
(
partition
);
ret
.
nodes
=
ret
.
nodes
.
concat
(
make_set
(
partition_values
));
//make set
graph
.
edges
.
forEach
(
function
(
edge
,
i
){
weight
=
edge
.
weight
||
1
;
var
com1
=
partition
[
edge
.
source
];
var
com2
=
partition
[
edge
.
target
];
w_prec
=
(
get_edge_weight
(
ret
,
com1
,
com2
)
||
0
);
var
new_weight
=
(
w_prec
+
weight
);
add_edge_to_graph
(
ret
,
{
'source'
:
com1
,
'target'
:
com2
,
'weight'
:
new_weight
});
});
return
ret
;
}
function
partition_at_level
(
dendogram
,
level
){
var
partition
=
clone
(
dendogram
[
0
]);
for
(
var
i
=
1
;
i
<
level
+
1
;
i
++
)
Object
.
keys
(
partition
).
forEach
(
function
(
key
,
j
){
var
node
=
key
;
var
com
=
partition
[
key
];
partition
[
node
]
=
dendogram
[
i
][
com
];
});
return
partition
;
}
function
generate_dendogram
(
graph
,
part_init
){
if
(
graph
.
edges
.
length
==
0
){
var
part
=
{};
graph
.
nodes
.
forEach
(
function
(
node
,
i
){
part
[
node
]
=
node
;
});
return
part
;
}
var
status
=
{};
init_status
(
original_graph
,
status
,
part_init
);
var
mod
=
__modularity
(
status
);
var
status_list
=
[];
__one_level
(
original_graph
,
status
);
var
new_mod
=
__modularity
(
status
);
var
partition
=
__renumber
(
status
.
nodes_to_com
);
status_list
.
push
(
partition
);
mod
=
new_mod
;
var
current_graph
=
induced_graph
(
partition
,
original_graph
);
init_status
(
current_graph
,
status
);
while
(
true
){
__one_level
(
current_graph
,
status
);
new_mod
=
__modularity
(
status
);
if
(
new_mod
-
mod
<
__MIN
)
break
;
partition
=
__renumber
(
status
.
nodes_to_com
);
status_list
.
push
(
partition
);
mod
=
new_mod
;
current_graph
=
induced_graph
(
partition
,
current_graph
);
init_status
(
current_graph
,
status
);
}
return
status_list
;
}
var
core
=
function
(){
var
status
=
{};
var
dendogram
=
generate_dendogram
(
original_graph
,
partition_init
);
return
partition_at_level
(
dendogram
,
dendogram
.
length
-
1
);
};
core
.
nodes
=
function
(
nds
){
if
(
arguments
.
length
>
0
){
original_graph_nodes
=
nds
;
return
core
;
}
else
{
return
original_graph_nodes
;
}
};
core
.
edges
=
function
(
edgs
){
if
(
typeof
original_graph_nodes
==
'undefined'
)
throw
'Please provide the graph nodes first!'
;
if
(
arguments
.
length
>
0
){
original_graph_edges
=
edgs
;
var
assoc_mat
=
make_assoc_mat
(
edgs
);
original_graph
=
{
'nodes'
:
original_graph_nodes
,
'edges'
:
original_graph_edges
,
'_assoc_mat'
:
assoc_mat
};
return
core
;
}
else
{
return
original_graph_edges
;
}
};
core
.
partition_init
=
function
(
prttn
){
if
(
arguments
.
length
>
0
){
partition_init
=
prttn
;
}
return
core
;
};
return
core
;
}
})();
exports
.
_init
=
function
(
louvain
,
nodes
,
edges
)
{
return
Object
.
entries
(
louvain
.
nodes
(
nodes
).
edges
(
edges
)());
}
src/Gargantext/Data/Louvain.purs
0 → 100644
View file @
33609eac
module Gargantext.Data.Louvain where
import Prelude (Unit, unit, ($), (<$>))
import Data.Function.Uncurried (Fn1, runFn1, Fn3, runFn3)
import Data.Map as Map
import Data.Tuple (Tuple(..))
import Data.Tuple.Native (T2, prj)
import Data.Typelevel.Num (d0, d1)
foreign import data Louvain :: Type
type Node = String
type Edge =
(
source :: Node
, target :: Node
, weight :: Number
)
type Cluster = Int
type LouvainCluster_ = T2 Node Cluster
type LouvainCluster = Map.Map Node Cluster
foreign import _jLouvain :: Fn1 Unit Louvain
louvain :: Unit -> Louvain
louvain unit = runFn1 _jLouvain unit
foreign import _init :: Fn3 Louvain (Array Node) (Array (Record Edge)) (Array LouvainCluster_)
init :: Louvain -> Array Node -> Array (Record Edge) -> LouvainCluster
init l nds edgs = Map.fromFoldable clusterTuples
where
clusterArr = runFn3 _init l nds edgs
clusterTuples = (\t2 -> Tuple (prj d0 t2) (prj d1 t2)) <$> clusterArr
src/Gargantext/Hooks/Sigmax/Types.purs
View file @
33609eac
module Gargantext.Hooks.Sigmax.Types where
import DOM.Simple.Types (Element)
import Data.Array as A
import Data.Generic.Rep (class Generic)
import Data.Generic.Rep.Eq (genericEq)
import Data.Generic.Rep.Show (genericShow)
import Data.Map as Map
import Data.Maybe (Maybe(..), fromJust)
import Data.Sequence as Seq
import Data.Set as Set
import Data.Tuple (Tuple(..))
import Prelude (class Eq, class Show, map, ($), (&&), (==), (||))
import Gargantext.Data.Louvain as Louvain
import Partial.Unsafe (unsafePartial)
import Prelude (class Eq, class Show, map, ($), (&&), (==), (||), (<$>), mod)
newtype Graph n e = Graph { nodes :: Seq.Seq {|n}, edges :: Seq.Seq {|e} }
...
...
@@ -155,3 +159,35 @@ forceAtlasEdgeState Running EShow = ETempHiddenThenShow
forceAtlasEdgeState Running es = es
forceAtlasEdgeState Paused ETempHiddenThenShow = EShow
forceAtlasEdgeState Paused es = es
louvainEdges :: SGraph -> Array (Record Louvain.Edge)
louvainEdges g = Seq.toUnfoldable $ Seq.map (\{source, target, weight} -> {source, target, weight}) (graphEdges g)
louvainNodes :: SGraph -> Array Louvain.Node
louvainNodes g = Seq.toUnfoldable $ Seq.map _.id (graphNodes g)
louvainGraph :: SGraph -> Louvain.LouvainCluster -> SGraph
louvainGraph g cluster = Graph {nodes: newNodes, edges: newEdges}
where
nodes = graphNodes g
edges = graphEdges g
newNodes = (nodeClusterColor cluster) <$> nodes
newEdges = edges
nodeClusterColor cluster n = n { color = newColor }
where
newColor = case Map.lookup n.id cluster of
Nothing -> n.color
Just c -> do
let idx = c `mod` (A.length defaultPalette)
unsafePartial $ fromJust $ defaultPalette A.!! idx
defaultPalette :: Array String
defaultPalette = ["#5fa571","#ab9ba2","#da876d","#bdd3ff"
,"#b399df","#ffdfed","#33c8f3","#739e9a"
,"#caeca3","#f6f7e5","#f9bcca","#ccb069"
,"#c9ffde","#c58683","#6c9eb0","#ffd3cf"
,"#ccffc7","#52a1b0","#d2ecff","#99fffe"
,"#9295ae","#5ea38b","#fff0b3","#d99e68"
]
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment