Commit c73f90ca authored by Alexandre Delanoë's avatar Alexandre Delanoë

Merge branch 'dev-sigmajs-selector' into dev

parents f3fdb94b 2ed26a3c
......@@ -17,7 +17,7 @@
position: absolute;
max-height: 600px;
overflow-y: scroll;
top: 150px;
top: 170px;
z-index: 1;
}
#graph-explorer #graph-view {
......@@ -27,7 +27,7 @@
position: absolute;
max-height: 600px;
overflow-y: scroll;
top: 150px;
top: 170px;
z-index: 1;
left: 70%;
border: 1px white solid;
......
......@@ -2,7 +2,7 @@
position: absolute
max-height: 600px
overflow-y: scroll
top: 150px
top: 170px
z-index: 1
#graph-explorer
......
......@@ -20,7 +20,7 @@
"prop-types": "15.6.2",
"react": "^16.10",
"react-dom": "^16.10",
"sigma": "git://github.com/jjl/sigma.js#garg"
"sigma": "git://github.com/poorscript/sigma.js#garg"
},
"eslintConfig": {
"extends": "react-app"
......
......@@ -81,9 +81,8 @@ appCpt = R.hooksComponent "G.C.App.app" cpt where
forestLayout :: Frontends -> Sessions -> AppRoute -> R2.Setter Boolean -> R.Element -> R.Element
forestLayout frontends sessions route showLogin child =
R.fragment [ topBar {}, row main, footer {} ]
R.fragment [ topBar {}, R2.row [main], footer {} ]
where
row child' = H.div {className: "row"} [child']
main =
R.fragment
[ H.div {className: "col-md-2", style: {paddingTop: "60px"}}
......
......@@ -254,7 +254,7 @@ docViewCpt = R.hooksComponent "G.C.DocsTable.docView" cpt where
, layout: { frontends, session, nodeId, tabType, listId
, corpusId, totalRecords, chart, showSearch } } _ = do
pure $ H.div {className: "container1"}
[ H.div {className: "row"}
[ R2.row
[ chart
, if showSearch then searchBar query else H.div {} []
, H.div {className: "col-md-12"}
......
......@@ -28,6 +28,7 @@ import Gargantext.Sessions (Session, sessionId, post, deleteWithBody)
import Gargantext.Types (NodeType(..), OrderBy(..), NodePath(..))
import Gargantext.Utils (toggleSet, zeroPad)
import Gargantext.Utils.DecodeMaybe ((.|))
import Gargantext.Utils.Reactix as R2
------------------------------------------------------------------------
type NodeID = Int
......@@ -191,7 +192,7 @@ docViewCpt = R.hooksComponent "G.C.FacetsTable.DocView" cpt
snd path $ const ipp
pure $ H.div { className: "container1" }
[ H.div { className: "row" }
[ R2.row
[ chart
, H.div { className: "col-md-12" }
[ pageLayout { deletions, frontends, totalRecords, container, session, path } ]
......@@ -241,7 +242,7 @@ docViewGraphCpt = R.hooksComponent "FacetsDocViewGraph" cpt
, H.p {} [ H.text "" ]
, H.br {}
, H.div { className: "container-fluid" }
[ H.div { className: "row" }
[ R2.row
[ chart
, H.div { className: "col-md-12" }
[ pageLayout { frontends, totalRecords, deletions, container, session, path }
......
......@@ -224,7 +224,7 @@ nodePopupView d p mPop@(Just NodePopup /\ setPopupOpen) = R.createElement el p [
panelHeading renameBoxOpen@(open /\ _) =
H.div {className: "panel-heading"}
[ H.div {className: "row" }
[ R2.row
[ H.div {className: "col-md-8"}
[ renameBox d {id, name, nodeType} renameBoxOpen ]
......
......@@ -4,13 +4,11 @@ module Gargantext.Components.Graph
-- , forceAtlas2Settings, ForceAtlas2Settings, ForceAtlas2OptionalSettings
-- )
where
import Prelude (bind, const, discard, pure, ($), unit, map, not, show)
import Prelude (bind, const, discard, not, pure, unit, ($))
import Data.Array as A
import Data.Either (Either(..))
import Data.Maybe (Maybe(..))
import Data.Nullable (Nullable)
import Data.Tuple (fst)
import Data.Tuple.Nested ((/\))
import DOM.Simple.Console (log, log2)
import DOM.Simple.Types (Element)
......@@ -32,7 +30,6 @@ type Props sigma forceatlas2 =
, graph :: SigmaxTypes.SGraph
, multiSelectEnabledRef :: R.Ref Boolean
, selectedNodeIds :: R.State SigmaxTypes.SelectedNodeIds
, selectorSize :: R.State Int
, showEdges :: R.State SigmaxTypes.ShowEdgesState
, sigmaRef :: R.Ref Sigmax.Sigma
, sigmaSettings :: sigma
......@@ -72,6 +69,7 @@ graphCpt = R.hooksComponent "Graph" cpt
_ <- Sigma.addRenderer sig {
"type": "canvas"
, container: c
, additionalContexts: ["mouseSelector"]
}
pure unit
......@@ -80,6 +78,8 @@ graphCpt = R.hooksComponent "Graph" cpt
Sigmax.dependOnSigma (R.readRef sigmaRef) "[graphCpt (Ready)] no sigma" $ \sigma -> do
-- bind the click event only initially, when ref was empty
Sigmax.bindSelectedNodesClick sigma selectedNodeIds multiSelectEnabledRef
_ <- Sigma.bindMouseSelectorPlugin sigma
pure unit
Sigmax.setEdges sig false
Sigma.startForceAtlas2 sig props.forceAtlas2Settings
......@@ -103,10 +103,6 @@ graphCpt = R.hooksComponent "Graph" cpt
Sigmax.updateNodes sigma tNodesMap
Sigmax.setEdges sigma (not $ SigmaxTypes.edgeStateHidden showEdges)
-- R.useEffect1' (fst props.selectorSize) $ do
-- Sigmax.dependOnSigma (R.readRef sigmaRef) "[graphCpt (Ready)] no sigma" $ \sigma -> do
-- Sigmax.selectorWithSize sigma $ fst props.selectorSize
stageHooks _ = pure unit
......@@ -172,6 +168,7 @@ type SigmaSettings =
, mouseEnabled :: Boolean
-- , mouseInertiaDuration :: Number
-- , mouseInertiaRatio :: Number
, mouseSelectorSize :: Number
-- , mouseWheelEnabled :: Boolean
, mouseZoomDuration :: Number
, nodeBorderColor :: String
......@@ -239,6 +236,7 @@ sigmaSettings =
, minEdgeSize: 0.5 -- in fact used in tina as edge size
, minNodeSize: 1.0
, mouseEnabled: true
, mouseSelectorSize: 15.0
, mouseZoomDuration: 150.0
, nodeBorderColor: "default" -- choices: "default" color vs. "node" color
--, nodesPowRatio : 10.8
......
......@@ -98,7 +98,7 @@ explorerCpt = R.hooksComponent "G.C.GraphExplorer.explorer" cpt
pure $
RH.div
{ id: "graph-explorer" }
[ row
[ R2.row
[ outer
[ inner
[ row1
......@@ -107,7 +107,8 @@ explorerCpt = R.hooksComponent "G.C.GraphExplorer.explorer" cpt
, col [ pullRight [ Toggle.sidebarToggleButton controls.showSidePanel ] ]
]
, rowControls [ Controls.controls controls ]
, row [ tree (fst controls.showTree) {sessions, mCurrentRoute, frontends} (snd showLogin)
, R2.row [
tree (fst controls.showTree) {sessions, mCurrentRoute, frontends} (snd showLogin)
, RH.div { ref: graphRef, id: "graph-view", className: "col-md-12" } [] -- graph container
, graphView { controls
, elRef: graphRef
......@@ -130,7 +131,6 @@ explorerCpt = R.hooksComponent "G.C.GraphExplorer.explorer" cpt
outer = RH.div { className: "col-md-12" }
inner = RH.div { className: "container-fluid", style: { paddingTop: "90px" } }
row1 = RH.div { className: "row", style: { paddingBottom: "10px", marginTop: "-24px" } }
row = RH.div { className: "row" }
rowControls = RH.div { className: "row controls" }
col = RH.div { className: "col-md-4" }
pullLeft = RH.div { className: "pull-left" }
......@@ -189,7 +189,6 @@ graphViewCpt = R.hooksComponent "GraphView" cpt
, graph
, multiSelectEnabledRef
, selectedNodeIds: controls.selectedNodeIds
, selectorSize: controls.selectorSize
, showEdges: controls.showEdges
, sigmaRef: controls.sigmaRef
, sigmaSettings: Graph.sigmaSettings
......@@ -201,7 +200,7 @@ convert :: GET.GraphData -> Tuple (Maybe GET.MetaData) SigmaxTypes.SGraph
convert (GET.GraphData r) = Tuple r.metaData $ SigmaxTypes.Graph {nodes, edges}
where
nodes = foldMapWithIndex nodeFn r.nodes
nodeFn i (GET.Node n) =
nodeFn _i (GET.Node n) =
Seq.singleton
{ borderColor: color
, color : color
......@@ -224,12 +223,14 @@ convert (GET.GraphData r) = Tuple r.metaData $ SigmaxTypes.Graph {nodes, edges}
, hidden : false
, size: 1.0
, source : e.source
, sourceNode
, target : e.target
, targetNode
, weight : e.weight }
where
color = case Map.lookup e.source nodesMap of
Nothing -> "#000000"
Just node -> node.color
sourceNode = unsafePartial $ fromJust $ Map.lookup e.source nodesMap
targetNode = unsafePartial $ fromJust $ Map.lookup e.target nodesMap
color = sourceNode.color
defaultPalette :: Array String
defaultPalette = ["#5fa571","#ab9ba2","#da876d","#bdd3ff"
......@@ -371,16 +372,14 @@ transformGraph controls graph = SigmaxTypes.Graph {nodes: newNodes, edges: newEd
where
edges = SigmaxTypes.graphEdges graph
nodes = SigmaxTypes.graphNodes graph
graphEdgesMap = SigmaxTypes.edgesGraphMap graph
graphNodesMap = SigmaxTypes.nodesGraphMap graph
selectedEdgeIds =
Set.fromFoldable
$ Seq.map _.id
$ Seq.filter (\e -> Set.member e.source (fst controls.selectedNodeIds)) edges
$ SigmaxTypes.neighbouringEdges graph (fst controls.selectedNodeIds)
hasSelection = not $ Set.isEmpty (fst controls.selectedNodeIds)
newNodes = nodeSizeFilter <$> nodeMarked <$> nodes
newEdges = edgeConfluenceFilter <$> edgeWeightFilter <$> edgeShowFilter <$> edgeMarked <$> edges
newNodes = Seq.map (nodeSizeFilter <<< nodeMarked) nodes
newEdges = Seq.map (edgeConfluenceFilter <<< edgeWeightFilter <<< edgeShowFilter <<< edgeMarked) edges
nodeSizeFilter node@{ size } =
if Range.within (fst controls.nodeSize) size then
......@@ -404,16 +403,15 @@ transformGraph controls graph = SigmaxTypes.Graph {nodes: newNodes, edges: newEd
else
edge { hidden = true }
edgeMarked edge@{ id } = do
edgeMarked edge@{ id, sourceNode } = do
let isSelected = Set.member id selectedEdgeIds
let sourceNode = Map.lookup edge.source graphNodesMap
case Tuple hasSelection isSelected of
Tuple false true -> edge { color = "#ff0000" }
Tuple true true -> edge { color = (unsafePartial $ fromJust sourceNode).color }
Tuple true false -> edge { color = "#dddddd" }
Tuple true true -> edge { color = sourceNode.color }
Tuple true false -> edge { color = "rgba(221, 221, 221, 0.5)" }
_ -> edge
nodeMarked node@{ id } =
if Set.member id (fst controls.selectedNodeIds) then
node { borderColor = "#000", type = "hovered" }
node { borderColor = "#000", type = "selected" }
else
node
module Gargantext.Components.GraphExplorer.Controls
( Controls
, controlsToSigmaSettings
, useGraphControls
, controls
, controlsCpt
, getShowTree, setShowTree
, getShowControls, setShowControls
, getCursorSize, setCursorSize
) where
import Data.Array as A
......@@ -25,7 +23,7 @@ import Gargantext.Components.Graph as Graph
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 (cursorSizeButton, labelSizeButton)
import Gargantext.Components.GraphExplorer.SlideButton (labelSizeButton, mouseSelectorSizeButton)
import Gargantext.Components.GraphExplorer.ToggleButton (multiSelectEnabledButton, edgesToggleButton, pauseForceAtlasButton)
import Gargantext.Components.GraphExplorer.Types as GET
import Gargantext.Hooks.Sigmax as Sigmax
......@@ -34,8 +32,7 @@ import Gargantext.Utils.Range as Range
import Gargantext.Utils.Reactix as R2
type Controls =
( cursorSize :: R.State Number
, edgeConfluence :: R.State Range.NumberRange
( edgeConfluence :: R.State Range.NumberRange
, edgeWeight :: R.State Range.NumberRange
, forceAtlasState :: R.State SigmaxTypes.ForceAtlasState
, graph :: SigmaxTypes.SGraph
......@@ -43,7 +40,6 @@ type Controls =
, multiSelectEnabled :: R.State Boolean
, nodeSize :: R.State Range.NumberRange
, selectedNodeIds :: R.State SigmaxTypes.SelectedNodeIds
, selectorSize :: R.State Int
, showControls :: R.State Boolean
, showEdges :: R.State SigmaxTypes.ShowEdgesState
, showSidePanel :: R.State GET.SidePanelState
......@@ -51,19 +47,19 @@ type Controls =
, sigmaRef :: R.Ref Sigmax.Sigma
)
controlsToSigmaSettings :: Record Controls -> Record Graph.SigmaSettings
controlsToSigmaSettings { cursorSize: (cursorSize /\ _)} = Graph.sigmaSettings
type LocalControls =
( labelSize :: R.State Number
, mouseSelectorSize :: R.State Number
)
initialLocalControls :: R.Hooks (Record LocalControls)
initialLocalControls = do
labelSize <- R.useState' 14.0
mouseSelectorSize <- R.useState' 15.0
pure $ {
labelSize
, mouseSelectorSize
}
controls :: Record Controls -> R.Element
......@@ -145,7 +141,6 @@ controlsCpt = R.hooksComponent "GraphControls" cpt
-- run demo
-- search button
-- search topics
, RH.li {} [ cursorSizeButton props.cursorSize ] -- cursor size: 0-100
, RH.li {} [ labelSizeButton props.sigmaRef localControls.labelSize ] -- labels size: 1-4
, RH.li {} [ nodeSizeControl nodeSizeRange props.nodeSize ]
-- zoom: 0 -100 - calculate ratio
......@@ -153,13 +148,13 @@ controlsCpt = R.hooksComponent "GraphControls" cpt
-- save button
, RH.li {} [ nodeSearchControl { graph: props.graph
, selectedNodeIds: props.selectedNodeIds } ]
, RH.li {} [ mouseSelectorSizeButton props.sigmaRef localControls.mouseSelectorSize ]
]
]
]
useGraphControls :: SigmaxTypes.SGraph -> R.Hooks (Record Controls)
useGraphControls graph = do
cursorSize <- R.useState' 10.0
edgeConfluence <- R.useState' $ Range.Closed { min: 0.0, max: 1.0 }
edgeWeight <- R.useState' $ Range.Closed { min: 0.0, max: 1.0 }
forceAtlasState <- R.useState' SigmaxTypes.InitialRunning
......@@ -168,15 +163,13 @@ useGraphControls graph = do
nodeSize <- R.useState' $ Range.Closed { min: 0.0, max: 100.0 }
showTree <- R.useState' false
selectedNodeIds <- R.useState' $ Set.empty
selectorSize <- R.useState' 5
showControls <- R.useState' false
showEdges <- R.useState' SigmaxTypes.EShow
showSidePanel <- R.useState' GET.InitialClosed
sigma <- Sigmax.initSigma
sigmaRef <- R.useRef sigma
pure { cursorSize
, edgeConfluence
pure { edgeConfluence
, edgeWeight
, forceAtlasState
, graph
......@@ -184,7 +177,6 @@ useGraphControls graph = do
, multiSelectEnabled
, nodeSize
, selectedNodeIds
, selectorSize
, showControls
, showEdges
, showSidePanel
......@@ -198,14 +190,8 @@ getShowControls { showControls: ( should /\ _ ) } = should
getShowTree :: Record Controls -> Boolean
getShowTree { showTree: ( should /\ _ ) } = should
getCursorSize :: Record Controls -> Number
getCursorSize { cursorSize: ( size /\ _ ) } = size
setShowControls :: Record Controls -> Boolean -> Effect Unit
setShowControls { showControls: ( _ /\ set ) } v = set $ const v
setShowTree :: Record Controls -> Boolean -> Effect Unit
setShowTree { showTree: ( _ /\ set ) } v = set $ not <<< const v
setCursorSize :: Record Controls -> Number -> Effect Unit
setCursorSize { cursorSize: ( _ /\ setSize ) } v = setSize $ const v
......@@ -12,8 +12,6 @@ import Reactix as R
import Reactix.DOM.HTML as H
import Gargantext.Components.RangeSlider as RS
import Gargantext.Hooks.Sigmax as Sigmax
import Gargantext.Hooks.Sigmax.Sigma as Sigma
import Gargantext.Utils.Range as Range
type Props = (
......
......@@ -4,20 +4,29 @@ module Gargantext.Components.GraphExplorer.Sidebar
import Prelude
import Data.Array (head)
import Data.Int (fromString)
import Data.Map as Map
import Data.Maybe (Maybe(..))
import Data.Sequence as Seq
import Data.Set as Set
import Data.Traversable (traverse_)
import Data.Tuple.Nested((/\))
import DOM.Simple.Console (log2)
import Effect (Effect)
import Effect.Aff (Aff, launchAff_)
import Reactix as R
import Reactix.DOM.HTML as RH
import Gargantext.Data.Array (catMaybes)
import Gargantext.Components.RandomText (words)
import Gargantext.Components.Nodes.Corpus.Graph.Tabs as GT
import Gargantext.Components.GraphExplorer.Types as GET
import Gargantext.Data.Array (mapMaybe)
import Gargantext.Ends (Frontends)
import Gargantext.Hooks.Sigmax.Types as SigmaxTypes
import Gargantext.Sessions (Session)
import Gargantext.Routes (SessionRoute(NodeAPI))
import Gargantext.Sessions (Session, delete)
import Gargantext.Types (NodeType(..))
import Gargantext.Utils.Reactix as R2
type Props =
( frontends :: Frontends
......@@ -44,20 +53,35 @@ sidebarCpt = R.hooksComponent "Sidebar" cpt
pure $
RH.div { id: "sp-container", className: "col-md-3" }
[ RH.div {}
[ RH.div { className: "row" }
[ RH.div { className: "col-md-12" }
[ R2.row
[ R2.col12
[ RH.ul { id: "myTab", className: "nav nav-tabs", role: "tablist"}
[ RH.li { className: "nav-item" }
[ RH.div { className: "tab-content" }
[ RH.div { className: "", role: "tabpanel" }
(Seq.toUnfoldable $ (Seq.map (badge props.selectedNodeIds) (badges props.graph props.selectedNodeIds)))
]
, RH.div { className: "tab-content" }
[
RH.button { className: "btn btn-danger"
, on: { click: onClickRemove props.session props.selectedNodeIds }}
[ RH.text "Remove" ]
]
, RH.li { className: "nav-item" }
[ RH.a { id: "home-tab"
, className: "nav-link active"
, data: {toggle: "tab"}
, href: "#home"
, role: "tab"
, aria: {controls: "home", selected: "true"}}
[ RH.text "Selected nodes" ] ] ]
, aria: {controls: "home", selected: "true"}
}
[ RH.text "Neighbours" ]
]
]
, RH.div { className: "tab-content", id: "myTabContent" }
[ RH.div { className: "", id: "home", role: "tabpanel" }
(badge <$> badges props.selectedNodeIds nodesMap) ] ]
(Seq.toUnfoldable $ (Seq.map (badge props.selectedNodeIds) (neighbourBadges props.graph props.selectedNodeIds)))
]
]
{-, RH.div { className: "col-md-12", id: "horizontal-checkbox" }
[ RH.ul {}
[ checkbox "Pubs"
......@@ -74,8 +98,13 @@ sidebarCpt = R.hooksComponent "Sidebar" cpt
]
]
]
badge text =
RH.a { className: "badge badge-light" } [ RH.text text ]
badge (_ /\ setSelectedNodeIds) {id, label} =
RH.a { className: "badge badge-light"
, on: { click: onClick }
} [ RH.text label ]
where
onClick e = do
setSelectedNodeIds $ const $ Set.singleton id
checkbox text =
RH.li {}
[ RH.span {} [ RH.text text ]
......@@ -83,10 +112,24 @@ sidebarCpt = R.hooksComponent "Sidebar" cpt
, className: "checkbox"
, checked: true
, title: "Mark as completed" } ]
badges (selectedNodeIds /\ _) nodesMap = map (\n -> n.label)
$ catMaybes
$ map (\n -> Map.lookup n nodesMap)
$ Set.toUnfoldable selectedNodeIds
badges :: SigmaxTypes.SGraph -> R.State SigmaxTypes.SelectedNodeIds -> Seq.Seq (Record SigmaxTypes.Node)
badges graph (selectedNodeIds /\ _) = SigmaxTypes.nodesById graph selectedNodeIds
neighbourBadges :: SigmaxTypes.SGraph -> R.State SigmaxTypes.SelectedNodeIds -> Seq.Seq (Record SigmaxTypes.Node)
neighbourBadges graph (selectedNodeIds /\ _) = SigmaxTypes.neighbours graph selectedNodes
where
selectedNodes = SigmaxTypes.nodesById graph selectedNodeIds
onClickRemove session (selectedNodeIds /\ _) e = do
log2 "[onClickRemove] selectedNodeIds" selectedNodeIds
let nodeIds = mapMaybe fromString $ Set.toUnfoldable selectedNodeIds
deleteNodes session nodeIds
deleteNodes :: Session -> Array Int -> Effect Unit
deleteNodes session nodeIds = do
traverse_ (launchAff_ <<< deleteNode session) nodeIds
deleteNode :: Session -> Int -> Aff Int
deleteNode session nodeId = delete session $ NodeAPI Node (Just nodeId) ""
query _ _ _ _ (selectedNodeIds /\ _) | Set.isEmpty selectedNodeIds = RH.div {} []
query frontends (GET.MetaData metaData) session nodesMap (selectedNodeIds /\ _) =
......
module Gargantext.Components.GraphExplorer.SlideButton
( Props
, sizeButton
, cursorSizeButton
, labelSizeButton
, mouseSelectorSizeButton
) where
import Global (readFloat)
import Prelude
import Data.Tuple (snd)
import Data.Tuple.Nested ((/\))
import Effect (Effect)
import Reactix as R
......@@ -45,30 +44,38 @@ sizeButtonCpt = R.hooksComponent "SizeButton" cpt
}
]
cursorSizeButton :: R.State Number -> R.Element
cursorSizeButton state =
sizeButton {
state: state
, caption: "Cursor Size"
, min: 1.0
, max: 4.0
, onChange: \e -> snd state $ const $ readFloat $ R2.unsafeEventValue e
}
labelSizeButton :: R.Ref Sigmax.Sigma -> R.State Number -> R.Element
labelSizeButton sigmaRef state =
sizeButton {
state: state
state
, caption: "Label Size"
, min: 5.0
, max: 30.0
, onChange: \e -> do
let sigma = R.readRef sigmaRef
let newValue = readFloat $ R2.unsafeEventValue e
let (value /\ setValue) = state
let (_ /\ setValue) = state
Sigmax.dependOnSigma sigma "[labelSizeButton] sigma: Nothing" $ \s -> do
Sigma.setSettings s {
defaultLabelSize: newValue
}
setValue $ const newValue
}
mouseSelectorSizeButton :: R.Ref Sigmax.Sigma -> R.State Number -> R.Element
mouseSelectorSizeButton sigmaRef state =
sizeButton {
state
, caption: "Selector Size"
, min: 1.0
, max: 50.0
, onChange: \e -> do
let sigma = R.readRef sigmaRef
let (_ /\ setValue) = state
let newValue = readFloat $ R2.unsafeEventValue e
Sigmax.dependOnSigma sigma "[mouseSelectorSizeButton] sigma: Nothing" $ \s -> do
Sigma.setSettings s {
mouseSelectorSize: newValue
}
setValue $ const newValue
}
......@@ -19,7 +19,6 @@ import Reactix as R
import Reactix.DOM.HTML as H
import Gargantext.Components.GraphExplorer.Types as GET
import Gargantext.Hooks.Sigmax as Sigmax
import Gargantext.Hooks.Sigmax.Types as SigmaxTypes
type Props = (
......
......@@ -79,7 +79,6 @@ instance showSelectedNode :: Show SelectedNode where
type State = (
-- corpusId :: R.State Int
--, cursorSize :: R.State Number
--, filePath :: R.State String
--, graphData :: R.State GraphData
--, legendData :: R.State (Array Legend)
......
......@@ -157,7 +157,7 @@ formCpt = R.hooksComponent "G.C.Login.form" cpt where
error <- R.useState' ""
username <- R.useState' ""
password <- R.useState' ""
pure $ H.div {className: "row"}
pure $ R2.row
[ cardGroup
[ card
[ cardBlock
......
......@@ -149,14 +149,14 @@ tableContainer { path: {searchQuery, termListFilter, termSizeFilter} /\ setPath
} props =
H.div {className: "container-fluid"}
[ H.div {className: "jumbotron1"}
[ H.div {className: "row"}
[ R2.row
[ H.div {className: "panel panel-default"}
[ H.div {className: "panel-heading"}
[ H.h2 {className: "panel-title", style: {textAlign : "center"}}
[ H.span {className: "glyphicon glyphicon-hand-down"} []
, H.text "Extracted Terms"
]
, H.div {className: "row"}
, R2.row
[ H.div {className: "col-md-3", style: {marginTop: "6px"}}
[ H.input { className: "form-control"
, name: "search"
......
......@@ -90,12 +90,12 @@ tableHeaderLayoutCpt = R.hooksComponent "G.C.Table.tableHeaderLayout" cpt
where
cpt {title, desc, query, date, user} _ =
pure $ R.fragment
[ H.div {className: "row"}
[ R2.row
[ H.div {className: "col-md-3"} [ H.h3 {} [H.text title] ]
, H.div {className: "col-md-9"}
[ H.hr {style: {height: "2px", backgroundColor: "black"}} ]
]
, H.div {className: "row"}
, R2.row
[ H.div {className: "jumbotron1", style: {padding: "12px 0px 20px 12px"}}
[ H.div {className: "col-md-8 content"}
[ H.p {}
......@@ -158,7 +158,7 @@ tableCpt = R.hooksComponent "G.C.Table.table" cpt
defaultContainer :: {title :: String} -> Record TableContainerProps -> R.Element
defaultContainer {title} props = R.fragment
[ H.div {className: "row"}
[ R2.row
[ H.div {className: "col-md-4"} [ props.pageSizeDescription ]
, H.div {className: "col-md-4"} [ props.paginationLinks ]
, H.div {className: "col-md-4"} [ props.pageSizeControl ]
......
......@@ -3,7 +3,7 @@ module Gargantext.Data.Array
import Data.Array as DA
import Data.Maybe
import Data.Sequence as DS
import Data.Sequence as Seq
import Data.Tuple (Tuple(..))
import Prelude (bind, flip, identity, (<<<))
......@@ -16,9 +16,9 @@ splitEvery n xs =
in DA.cons h (splitEvery n t)
splitAt :: forall a. Int -> Array a -> Tuple (Array a) (Array a)
splitAt n ls = Tuple (DS.toUnfoldable x) (DS.toUnfoldable xs)
splitAt n ls = Tuple (Seq.toUnfoldable x) (Seq.toUnfoldable xs)
where
Tuple x xs = DS.splitAt n (DS.fromFoldable ls)
Tuple x xs = Seq.splitAt n (Seq.fromFoldable ls)
----------------------------------------------------------------------
-- | Array with Maybe tools
......@@ -36,4 +36,15 @@ concatMap = flip bind
singleton :: forall a. a -> Array a
singleton a = [a]
----------------------------------------------------------------------
-- | Seq with Maybe tools
seqMapMaybe :: forall a b. (a -> Maybe b) -> Seq.Seq a -> Seq.Seq b
seqMapMaybe f = seqConcatMap (maybe Seq.empty Seq.singleton <<< f)
seqCatMaybes :: forall a. Seq.Seq (Maybe a) -> Seq.Seq a
seqCatMaybes = seqMapMaybe identity
----------------------------------------------------------------------
-- | Seq misc tools
seqConcatMap :: forall a b. (a -> Seq.Seq b) -> Seq.Seq a -> Seq.Seq b
seqConcatMap = flip bind
module Gargantext.Hooks.Sigmax
where
import Prelude (Unit, bind, discard, flip, pure, unit, ($), (*>), (<<<), (<>), (>>=), (||), not, const, map)
import Prelude (Unit, bind, discard, flip, pure, unit, ($), (*>), (<<<), (<>), (>>=), not, const, map)
import Data.Array as A
import Data.Either (either)
......
......@@ -9,7 +9,7 @@ if (typeof window !== 'undefined') {
const CustomShapes = require('sigma/plugins/garg.js').init(sigma, window).customShapes;
require('sigma/src/utils/sigma.utils.js').init(sigma);
sigma.canvas.nodes.hovered = (node, context, settings) => {
sigma.canvas.nodes.selected = (node, context, settings) => {
// hack
// We need to temporarily set node.type to 'def'. This is for 2 reasons
// 1. Make it render as a normal node
......@@ -17,11 +17,129 @@ sigma.canvas.nodes.hovered = (node, context, settings) => {
// again with node.type = 'hovered')
node.type = 'def';
sigma.canvas.hovers.def(node, context, settings);
node.type = 'hovered';
node.type = 'selected';
};
CustomShapes.init();
let sigmaMouseSelector = (sigma, options) => {
sigma.plugins = sigma.plugins || {};
sigma.plugins.mouseSelector = (s, renderer) => {
var _self = this;
var _offset = null;
const _s = s;
const _renderer = renderer;
const _container = _renderer.container;
//renderer.initDOM('canvas', 'mouseSelector');
// A hack to force resize to be called (there is a width/height equality
// check which can't be escaped in any other way).
//renderer.resize(renderer.width - 1, renderer.height - 1);
//renderer.resize(renderer.width + 1, renderer.height + 1);
const _context = _renderer.contexts.mouseSelector;
// These are used to prevent using the 'click' event when in fact this was a drag
let _clickPositionX = null;
let _clickPositionY = null;
let _isValidClick = false;
_container.onmousemove = function(e) { return mouseMove(e); };
_context.canvas.onclick = function(e) { return onClick(e); };
_container.onmousedown = function(e) { return onMouseDown(e); }
_container.onmouseup = function(e) { return onMouseUp(e); }
s.bind('click', function(e) { return onClick(e); })
// The mouseSelector canvas will pass its events down to the "mouse" canvas.
_context.canvas.style.pointerEvents = 'none';
s.bind('kill', () => _self.unbindAll());
this.unbindAll = () => {
console.log('[sigmaMouseSelector] unbinding');
_container.onclick = null;
_context.canvas.onmousemove = null;
_container.onmousedown = null;
_container.onmouseup = null;
}
const onMouseDown = (e) => {
_clickPositionX = e.clientX;
_clickPositionY = e.clientY;
}
const onMouseUp = (e) => {
// Prevent triggering click when in fact this was a drag
if ((_clickPositionX != e.clientX) || (_clickPositionY != e.clientY)) {
_clickPositionX = null;
_clickPositionY = null;
_isValidClick = false;
} else {
_isValidClick = true;
}
}
const mouseMove = (e) => {
const size = _s.settings('mouseSelectorSize') || 3;
const x = e.clientX - _offset.left - size/2;
const y = e.clientY - _offset.top - size/2;
_context.clearRect(0, 0, _context.canvas.width, _context.canvas.height);
_context.fillStyle = 'rgba(91, 192, 222, 0.7)';
_context.beginPath();
_context.arc(
x,
y,
size,
0,
Math.PI * 2,
true
);
_context.closePath();
_context.fill();
}
const onClick = (e) => {
if(!_isValidClick) {
return;
}
const size = _s.settings('mouseSelectorSize') || 3;
const x = e.data.clientX - _offset.left - size/2;
const y = e.data.clientY - _offset.top - size/2;
const prefix = _renderer.options.prefix;
//console.log('[sigmaMouseSelector] clicked', e, x, y, size);
let nodes = [];
_s.graph.nodes().forEach((node) => {
const nodeX = node[prefix + 'x'];
const nodeY = node[prefix + 'y'];
if(sigma.utils.getDistance(x, y, nodeX, nodeY) <= size) {
nodes.push(node);
}
});
//console.log('[sigmaMouseSelector] nodes', nodes);
_renderer.dispatchEvent('clickNodes', {
node: nodes,
captor: e.data
})
_clickPositionX = null;
_clickPositionY = null;
}
const calculateOffset = (element) => {
var style = window.getComputedStyle(element);
var getCssProperty = function(prop) {
return parseInt(style.getPropertyValue(prop).replace('px', '')) || 0;
};
return {
left: element.getBoundingClientRect().left + getCssProperty('padding-left'),
top: element.getBoundingClientRect().top + getCssProperty('padding-top')
};
};
_offset = calculateOffset(renderer.container);
}
}
sigmaMouseSelector(sigma);
function _sigma(left, right, opts) {
try {
return right(new sigma(opts));
......@@ -45,6 +163,14 @@ function addRenderer(left, right, sigma, renderer) {
return left(e);
}
}
function bindMouseSelectorPlugin(left, right, sig) {
try {
return right(sigma.plugins.mouseSelector(sig, sig.renderers[0]));
} catch(e) {
console.log('[bindMouseSelectorPlugin] error', e);
return left(e);
}
}
function killRenderer(left, right, sigma, renderer) {
try {
sigma.killRenderer(renderer);
......@@ -91,6 +217,7 @@ exports._sigma = _sigma;
exports._graphRead = graphRead;
exports._refresh = refresh;
exports._addRenderer = addRenderer;
exports._bindMouseSelectorPlugin = bindMouseSelectorPlugin;
exports._killRenderer = killRenderer;
exports._getRendererContainer = getRendererContainer;
exports._setRendererContainer = setRendererContainer;
......
......@@ -70,6 +70,16 @@ foreign import _addRenderer
r
(Either err Unit)
bindMouseSelectorPlugin :: forall err. Sigma -> Effect (Either err Unit)
bindMouseSelectorPlugin = runEffectFn3 _bindMouseSelectorPlugin Left Right
foreign import _bindMouseSelectorPlugin
:: forall a b err.
EffectFn3 (a -> Either a b)
(b -> Either a b)
Sigma
(Either err Unit)
killRenderer :: forall r err. Sigma -> r -> Effect (Either err Unit)
killRenderer = runEffectFn4 _killRenderer Left Right
......
......@@ -15,69 +15,3 @@ exports.pauseForceAtlas2 = function() {
}
}
};
var trackMouse = function(cursorSize, e) {
if(!e.shiftKey) {
var partialGraph = window.sigmaGargInstance;
// new sigma.js 2D mouse context
var ctx = partialGraph.renderers[0].contexts.mouse;
ctx.globalCompositeOperation = "source-over";
// clear zone each time to prevent repeated frame artifacts
ctx.clearRect(50, 50,
partialGraph.renderers[0].container.offsetWidth,
partialGraph.renderers[0].container.offsetHeight);
// classic mousemove event or other similar non-sigma events
var coord = window.sigma.utils.mouseCoords(e)
var x = (coord.x + coord.clientX) / 2 // ; // sigma.utils.getX(e);
var y = (coord.y + coord.clientY) /2 // ; // sigma.utils.getY(e);
console.log('trackMouse', coord);
// optional: make more labels appear on circle hover (/!\ costly /!\ esp. on large graphs)
// if (partialGraph.conf.moreLabelsUnderArea) {
// // convert screen => mouse => cam
// var mouseCoords = (50,50); // sigma.utils.mouseCoords(e)
// var camCoords = partialGraph.cam.cameraPosition(mouseCoords.x, mouseCoords.y)
//
// var exactNodeset = circleGetAreaNodes(camCoords.x,camCoords.y)
// // console.log("nodes under circle:", exactNodeset)
//
// // we'll use labelThreshold / 3 as the "boosted" cam:size threshold
// var pfx = partialGraph.cam.readPrefix
// var toRedraw = []
// for (var k in exactNodeset) {
// var n = partialGraph.graph.nodes(exactNodeset[k])
// if(!n.hidden && n[pfx+'size'] > (partialGraph.customSettings.labelThreshold / 3)) {
// toRedraw.push(n)
// }
// }
// redrawNodesInHoverLayer(toRedraw, "hovers")
// }
// draw the circle itself
ctx.strokeStyle = '#000';
ctx.lineWidth = 1;
ctx.fillStyle = "#71C3FF";
ctx.globalAlpha = 0.5;
ctx.beginPath();
ctx.arc(x, y, cursorSize, 0, Math.PI * 2, true);
ctx.closePath();
ctx.fill();
ctx.stroke();
ctx.globalAlpha = 1
}
};
exports.sigmaOnMouseMove = function(props) {
return function(e) {
return function() {
if(typeof(window.sigmaGargInstance) !== "undefined") {
if(props.cursorSize > 0) trackMouse(props.cursorSize, e);
}
};
};
};
......@@ -6,7 +6,6 @@ import Data.Nullable (Nullable)
import Effect (Effect)
import Effect.Uncurried (EffectFn1, runEffectFn1)
import React (ReactRef, SyntheticEventHandler)
import React.SyntheticEvent (SyntheticMouseEvent)
import Record.Unsafe (unsafeGet)
import Unsafe.Coerce (unsafeCoerce)
import Gargantext.Types (class Optional)
......@@ -92,7 +91,6 @@ foreign import data CameraInstance' :: # Type
type SigmaInstance = { | SigmaInstance' }
type CameraInstance = { | CameraInstance' }
foreign import sigmaOnMouseMove :: {cursorSize :: Number} -> SyntheticMouseEvent -> Effect Unit
cameras :: SigmaInstance -> Array CameraInstance
cameras = unsafeGet "cameras"
......
module Gargantext.Hooks.Sigmax.Types where
import Prelude (map, ($), (&&), (==), class Eq, class Ord, class Show, Ordering, compare)
import DOM.Simple.Types (Element)
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.Sequence (Seq)
import Data.Sequence as Seq
import Data.Set as Set
import Data.Tuple (Tuple(..))
import DOM.Simple.Types (Element)
import Prelude (map, ($), (&&), (||), (==), class Eq, class Ord, class Show, Ordering, compare)
newtype Graph n e = Graph { nodes :: Seq {|n}, edges :: Seq {|e} }
newtype Graph n e = Graph { nodes :: Seq.Seq {|n}, edges :: Seq.Seq {|e} }
--derive instance eqGraph :: Eq Graph
......@@ -40,7 +40,9 @@ type Edge =
, hidden :: Boolean
, size :: Number
, source :: String
, sourceNode :: Record Node
, target :: String
, targetNode :: Record Node
, weight :: Number )
type SelectedNodeIds = Set.Set String
......@@ -50,24 +52,43 @@ type NodesMap = Map.Map String (Record Node)
type SGraph = Graph Node Edge
graphEdges :: SGraph -> Seq (Record Edge)
graphEdges :: SGraph -> Seq.Seq (Record Edge)
graphEdges (Graph {edges}) = edges
graphNodes :: SGraph -> Seq (Record Node)
graphNodes :: SGraph -> Seq.Seq (Record Node)
graphNodes (Graph {nodes}) = nodes
edgesGraphMap :: Graph Node Edge -> EdgesMap
edgesGraphMap :: SGraph -> EdgesMap
edgesGraphMap graph =
Map.fromFoldable $ map (\e -> Tuple e.id e) $ graphEdges graph
nodesMap :: Seq (Record Node) -> NodesMap
edgesById :: SGraph -> SelectedEdgeIds -> Seq.Seq (Record Edge)
edgesById g edgeIds = Seq.filter (\e -> Set.member e.id edgeIds) $ graphEdges g
nodesMap :: Seq.Seq (Record Node) -> NodesMap
nodesMap nodes = Map.fromFoldable $ map (\n -> Tuple n.id n) nodes
nodesGraphMap :: Graph Node Edge -> NodesMap
nodesGraphMap :: SGraph -> NodesMap
nodesGraphMap graph =
nodesMap $ graphNodes graph
eqGraph :: (Graph Node Edge) -> (Graph Node Edge) -> Boolean
nodesById :: SGraph -> SelectedNodeIds -> Seq.Seq (Record Node)
nodesById g nodeIds = Seq.filter (\n -> Set.member n.id nodeIds) $ graphNodes g
neighbours :: SGraph -> Seq.Seq (Record Node) -> Seq.Seq (Record Node)
neighbours g nodes = Seq.fromFoldable $ Set.unions [Set.fromFoldable nodes, sources, targets]
where
nodeIds = Set.fromFoldable $ Seq.map _.id nodes
selectedEdges = neighbouringEdges g nodeIds
sources = Set.fromFoldable $ nodesById g $ Set.fromFoldable $ Seq.map _.source selectedEdges
targets = Set.fromFoldable $ nodesById g $ Set.fromFoldable $ Seq.map _.target selectedEdges
neighbouringEdges :: SGraph -> SelectedNodeIds -> Seq.Seq (Record Edge)
neighbouringEdges g nodeIds = Seq.filter condition $ graphEdges g
where
condition {source, target} = (Set.member source nodeIds) || (Set.member target nodeIds)
eqGraph :: SGraph -> SGraph -> Boolean
eqGraph (Graph {nodes: n1, edges: e1}) (Graph {nodes: n2, edges: e2}) = (n1 == n2) && (e1 == e2)
......
......@@ -226,3 +226,9 @@ dataTransferFileBlob e = unsafePartial $ do
blur :: DOM.Element -> Effect Unit
blur el = el ... "blur" $ []
row :: Array R.Element -> R.Element
row children = H.div { className: "row" } children
col12 :: Array R.Element -> R.Element
col12 children = H.div { className: "col-md-12" } children
......@@ -5798,9 +5798,9 @@ shelljs@^0.8.2:
interpret "^1.0.0"
rechoir "^0.6.2"
"sigma@git://github.com/jjl/sigma.js#garg":
"sigma@git://github.com/poorscript/sigma.js#garg":
version "1.2.1"
resolved "git://github.com/jjl/sigma.js#64a0e1ccd48550387ad4a56292dcbaef3258c929"
resolved "git://github.com/poorscript/sigma.js#2ad537301ac0b12cae9a687fc1cb444dbe971c7c"
signal-exit@^3.0.0, signal-exit@^3.0.2:
version "3.0.2"
......
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