Commit 8d4b6e57 authored by Alexandre Delanoë's avatar Alexandre Delanoë

Merge remote-tracking branch 'origin/381-dev-graph-legend-rc1.x' into dev-merge

parents 051e70fa 6f6528e4
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
......@@ -27,9 +27,10 @@ import Gargantext.Components.GraphExplorer.Sidebar.DocList (docListWrapper)
import Gargantext.Components.GraphExplorer.Sidebar.Legend as Legend
import Gargantext.Components.GraphExplorer.Store as GraphStore
import Gargantext.Components.GraphExplorer.Types as GET
import Gargantext.Components.GraphExplorer.Utils as GEU
import Gargantext.Components.Lang (Lang(..))
import Gargantext.Core.NgramsTable.Functions as NTC
import Gargantext.Config.REST (AffRESTError)
import Gargantext.Core.NgramsTable.Functions as NTC
import Gargantext.Core.NgramsTable.Types as CNT
import Gargantext.Data.Array (mapMaybe)
import Gargantext.Ends (Frontends)
......@@ -37,7 +38,7 @@ import Gargantext.Hooks.FirstEffect (useFirstEffect')
import Gargantext.Hooks.Sigmax.Types as SigmaxT
import Gargantext.Sessions (Session)
import Gargantext.Types (CTabNgramType, FrontendError(..), NodeID, TabSubType(..), TabType(..), TermList(..), modeTabType)
import Gargantext.Utils (nbsp, setter, (?))
import Gargantext.Utils (getter, nbsp, setter, (?))
import Gargantext.Utils.Reactix as R2
import Gargantext.Utils.Toestand as T2
import Math as Math
......@@ -101,15 +102,53 @@ sideTabLegend :: R2.Leaf Props
sideTabLegend = R2.leaf sideTabLegendCpt
sideTabLegendCpt :: R.Component Props
sideTabLegendCpt = here.component "sideTabLegend" cpt
where
cpt { metaData: GET.MetaData { legend } } _ = pure $
sideTabLegendCpt = here.component "sideTabLegend" cpt where
cpt { metaData: GET.MetaData { legend } } _ = do
-- | States
-- |
store <- GraphStore.use
hyperdataGraph
<- R2.useLive' store.hyperdataGraph
-- | Computed
-- |
let
maxItemPerCluster = 4
-- | Hooks
-- |
-- For each provided Cluster (see Legend), extract the greatest nodes
extractedNodeList <- R.useMemo1 hyperdataGraph $ const $
flip A.foldMap legend
( getter _.id_
>>> GEU.takeGreatestNodeByCluster
hyperdataGraph
maxItemPerCluster
)
-- For each provided Cluster (see Legend), count the number of nodes
nodeCountList <- R.useMemo1 hyperdataGraph $ const $
flip A.foldMap legend
( getter _.id_
>>> GEU.countNodeByCluster hyperdataGraph
>>> A.singleton
)
-- | Render
-- |
pure $
H.div
{ className: "graph-sidebar__legend-tab" }
[
Legend.legend
{ items: Seq.fromFoldable legend }
{ legendSeq: Seq.fromFoldable legend
, extractedNodeList
, nodeCountList
, selectedNodeIds: store.selectedNodeIds
}
,
H.hr {}
,
......
......@@ -4,42 +4,196 @@ module Gargantext.Components.GraphExplorer.Sidebar.Legend
import Prelude hiding (map)
import Data.Array as A
import Data.Maybe (isJust, maybe)
import Data.Sequence (Seq)
import Data.Traversable (foldMap)
import Data.Set as Set
import Data.Traversable (foldMap, intercalate)
import Gargantext.Components.Bootstrap as B
import Gargantext.Components.GraphExplorer.Types as GET
import Gargantext.Hooks.Sigmax.Types as ST
import Gargantext.Utils (getter, nbsp, (?))
import Gargantext.Utils.Reactix as R2
import Reactix as R
import Reactix.DOM.HTML as H
import Gargantext.Components.GraphExplorer.Types (Legend(..), intColor)
import Gargantext.Utils.Reactix as R2
import Toestand as T
here :: R2.Here
here = R2.here "Gargantext.Components.GraphExplorer.Sidebar.Legend"
type Props = ( items :: Seq Legend )
type Props =
( legendSeq :: Seq GET.Legend
, extractedNodeList :: Array GET.Node
, nodeCountList :: Array GET.ClusterCount
, selectedNodeIds :: T.Box ST.NodeIds
)
legend :: R2.Leaf Props
legend = R2.leaf legendCpt
legendCpt :: R.Component Props
legendCpt = here.component "legend" cpt where
cpt { items } _ = pure $
H.ul
{ className: "graph-legend" }
[
flip foldMap items \(Legend { id_, label }) ->
H.li
{ className: "graph-legend__item" }
[
H.span
{ className: "graph-legend__code"
, style: { backgroundColor: intColor id_ }
cpt { legendSeq
, extractedNodeList
, nodeCountList
, selectedNodeIds
} _ = do
-- | Hooks
-- |
R.useEffectOnce' $ here.info2 "legend" extractedNodeList
-- | Render
-- |
pure $
H.ul
{ className: "graph-legend" }
[
flip foldMap legendSeq \(GET.Legend { id_, label }) ->
H.li
{ className: "graph-legend__item" }
[
H.div
{ className: "graph-legend__code"
, style: { backgroundColor: GET.intColor id_ }
}
[]
,
B.wad
[ "flex-grow-1" ]
[
B.div'
{ className: "graph-legend__title" }
label
,
selectedNodes
{ selectedNodeIds
, extractedNodeList
, clusterId: id_
, nodeCount: getClusterNodeCount nodeCountList id_
}
]
]
]
filterByCluster :: Int -> Array GET.Node -> Array GET.Node
filterByCluster id
= A.filter
( getter _.attributes
>>> getter _.clustDefault
>>> eq id
)
getClusterNodeCount :: Array GET.ClusterCount -> Int -> Int
getClusterNodeCount nodeCountList id
= nodeCountList
# A.find
( getter _.id
>>> eq id
)
>>> maybe 0
( getter _.count
)
---------------------------------------------------------
type SelectedNodesProps =
( extractedNodeList :: Array GET.Node
, selectedNodeIds :: T.Box ST.NodeIds
, clusterId :: Int
, nodeCount :: Int
)
selectedNodes :: R2.Leaf SelectedNodesProps
selectedNodes = R2.leaf selectedNodesCpt
selectedNodesCpt :: R.Component SelectedNodesProps
selectedNodesCpt = here.component "selectedNodes" cpt where
cpt { extractedNodeList
, selectedNodeIds
, clusterId
, nodeCount
} _ = do
-- | States
-- |
selectedNodeIds' <- R2.useLive' selectedNodeIds
-- | Computed
-- |
let
isSelected id
= selectedNodeIds'
# A.fromFoldable
# A.find
( eq id
)
# isJust
countValue
= extractedNodeList
# A.length
# (nodeCount - _)
-- | Behaviors
-- |
let
onBadgeClick id _ = T.write_ (Set.singleton id) selectedNodeIds
-- | Render
-- |
pure $
H.ul
{ className: "graph-legend-nodes" }
[
flip foldMap (filterByCluster clusterId extractedNodeList)
\(GET.Node { label: nodeLabel, id_: nodeId }) ->
H.li
{ className: "graph-legend-nodes__item" }
[
H.a
{ className: intercalate " "
[ "graph-legend-nodes__badge"
, (isSelected nodeId) ?
"graph-legend-nodes__badge--selected" $
""
, "badge badge-light"
]
, on: { click: onBadgeClick nodeId }
}
[ H.text nodeLabel ]
]
,
R2.when (eq countValue 0) $
H.li
{ className: intercalate " "
[ "graph-legend-nodes__item"
, "graph-legend-nodes__item--count"
]
}
[
H.text "0 node"
]
,
R2.when (not $ eq countValue 0) $
H.li
{ className: intercalate " "
[ "graph-legend-nodes__item"
, "graph-legend-nodes__item--count"
]
}
[]
,
H.span
{ className: "graph-legend__caption" }
[ H.text label ]
]
]
[
H.text "+"
,
H.text $ nbsp 1
,
H.text $ show countValue
,
H.text $ nbsp 1
,
H.text $ eq countValue 1 ? "node" $ "nodes"
]
]
......@@ -72,6 +72,12 @@ instance JSON.ReadForeign Cluster where
instance JSON.WriteForeign Cluster where
writeImpl (Cluster cl) = JSON.writeImpl $ Record.rename clustDefaultP clust_defaultP cl
newtype ClusterCount = ClusterCount
{ id :: Int
, count :: Int
}
derive instance Generic ClusterCount _
derive instance Newtype ClusterCount _
newtype Edge = Edge {
confluence :: Number
......
module Gargantext.Components.GraphExplorer.Utils where
import Data.Maybe (Maybe(..))
module Gargantext.Components.GraphExplorer.Utils
( stEdgeToGET, stNodeToGET
, normalizeNodes
, takeGreatestNodeByCluster, countNodeByCluster
) where
import Gargantext.Prelude
import Data.Array as A
import Data.Maybe (Maybe(..))
import Data.Newtype (wrap)
import Gargantext.Components.GraphExplorer.Types as GET
import Gargantext.Hooks.Sigmax.Types as ST
import Gargantext.Utils (getter)
import Gargantext.Utils.Array as GUA
stEdgeToGET :: Record ST.Edge -> GET.Edge
stEdgeToGET { _original } = _original
......@@ -24,6 +29,8 @@ stNodeToGET { id, label, x, y, _original: GET.Node { attributes, size, type_ } }
, y
}
-----------------------------------------------------------------------
normalizeNodes :: Array GET.Node -> Array GET.Node
normalizeNodes ns = map normalizeNode ns
where
......@@ -49,3 +56,37 @@ normalizeNodes ns = map normalizeNode ns
Just ydiv -> 1.0 / ydiv
normalizeNode (GET.Node n@{ x, y }) = GET.Node $ n { x = x * xdivisor
, y = y * ydivisor }
------------------------------------------------------------------------
takeGreatestNodeByCluster :: GET.HyperdataGraph -> Int -> Int -> Array GET.Node
takeGreatestNodeByCluster graphData take clusterId
= graphData
# getter _.graph
>>> getter _.nodes
>>> A.filter
( getter _.attributes
>>> getter _.clustDefault
>>> eq clusterId
)
>>> A.sortWith
( getter _.size
)
>>> A.takeEnd take
>>> A.reverse
countNodeByCluster :: GET.HyperdataGraph -> Int -> GET.ClusterCount
countNodeByCluster graphData clusterId
= graphData
# getter _.graph
>>> getter _.nodes
>>> A.filter
( getter _.attributes
>>> getter _.clustDefault
>>> eq clusterId
)
>>> A.length
>>> { id: clusterId
, count: _
}
>>> wrap
......@@ -142,8 +142,19 @@
$legend-code-height: 12px
&__item
display: flex
align-items: baseline
list-style: none
margin-bottom: space-x(0.75)
position: relative
&:not(:first-child)
margin-top: space-x(3)
&__title
color: $gray-800
font-size: 15px
font-weight: bold
margin-bottom: space-x(0.25)
&__code
width: $legend-code-width
......@@ -152,8 +163,32 @@
margin-right: space-x(2.5)
border: 1px solid $gray-500
&__caption
vertical-align: top
.graph-legend-nodes
&__item
display: inline-block
&:first-child
margin-top: space-x(0.75)
&:not(:last-child)
margin-bottom: space-x(0.5)
margin-right: space-x(0.5)
&--count
color: $gray-800
font-size: 12px
font-weight: bold
&__badge
font-size: 13px
white-space: normal
word-break: break-word
&--selected
background-color: darken($light, 10%) // from Bootstrap "_badge.scss"
.graph-documentation
......
This diff is collapsed.
......@@ -312,7 +312,7 @@
transition: transform 0s, opacity 0s;
}
@each $name, $value in $theme-colors {
@each $name, $value in $palette-semantic {
&--#{ $name }:after {
background-image:
......
......@@ -23,20 +23,6 @@ $warning :#0DE2EA;
$light :#e8e8e8;
$dark :#000000;
$theme-colors: map.merge(
$theme-colors,
(
'primary': $primary,
'secondary': $secondary,
'success': $success,
'danger': $danger,
'info': $info,
'warning': $warning,
'light': $light,
'dark': $dark,
)
);
$gray-50 : #121212; // (+)
$gray-100: #212529;
$gray-150: #2E2E2E; // (+)
......@@ -50,6 +36,49 @@ $gray-700: #DEE2E6;
$gray-800: #E9ECEF;
$gray-900: #F8F9FA;
// Palettes
$theme-colors: map.merge(
$theme-colors,
(
'primary': $primary,
'secondary': $secondary,
'success': $success,
'danger': $danger,
'info': $info,
'warning': $warning,
'light': $light,
'dark': $dark,
)
);
$palette-semantic: $theme-colors;
$palette-gray: (
'0': $white,
'50': $gray-50,
'100': $gray-100,
'150': $gray-150,
'175': $gray-175,
'200': $gray-200,
'300': $gray-300,
'400': $gray-400,
'500': $gray-500,
'600': $gray-600,
'700': $gray-700,
'800': $gray-800,
'900': $gray-900,
'1000': $black
);
$palette-pastel: (
'green': $pastel-green,
'blue': $pastel-blue,
'yellow': $pastel-yellow,
'red': $pastel-red,
'orange': $pastel-orange,
'purple': $pastel-purple
);
// Components
$annotation-text-color: $gray-600;
$annotation-field-base-alpha: 15%;
......
......@@ -13,13 +13,41 @@
$primary: #005a9a;
$secondary: $blue;
// Palettes
$theme-colors: map.merge(
$theme-colors,
(
"primary": $primary,
"secondary": $secondary,
"primary": $primary,
"secondary": $secondary,
)
);
$palette-semantic: $theme-colors;
$palette-gray: (
'0': $white,
'50': $gray-50,
'100': $gray-100,
'150': $gray-150,
'175': $gray-175,
'200': $gray-200,
'300': $gray-300,
'400': $gray-400,
'500': $gray-500,
'600': $gray-600,
'700': $gray-700,
'800': $gray-800,
'900': $gray-900,
'1000': $black
);
$palette-pastel: (
'green': $pastel-green,
'blue': $pastel-blue,
'yellow': $pastel-yellow,
'red': $pastel-red,
'orange': $pastel-orange,
'purple': $pastel-purple
);
///==========================================
......
......@@ -28,6 +28,7 @@ $warning :#6e9fa5;
$light :#eceeec;
$dark :#1e2b37;
// Palettes
$theme-colors: map.merge(
$theme-colors,
(
......@@ -41,6 +42,33 @@ $theme-colors: map.merge(
'dark': $dark,
)
);
$palette-semantic: $theme-colors;
$palette-gray: (
'0': $white,
'50': $gray-50,
'100': $gray-100,
'150': $gray-150,
'175': $gray-175,
'200': $gray-200,
'300': $gray-300,
'400': $gray-400,
'500': $gray-500,
'600': $gray-600,
'700': $gray-700,
'800': $gray-800,
'900': $gray-900,
'1000': $black
);
$palette-pastel: (
'green': $pastel-green,
'blue': $pastel-blue,
'yellow': $pastel-yellow,
'red': $pastel-red,
'orange': $pastel-orange,
'purple': $pastel-purple
);
// Misc
$enable-rounded: false;
......
......@@ -27,6 +27,7 @@ $warning :#FC3C3C;
$light :#F2F2F0;
$dark :#072247;
// Palettes
$theme-colors: map.merge(
$theme-colors,
(
......@@ -40,6 +41,33 @@ $theme-colors: map.merge(
'dark': $dark,
)
);
$palette-semantic: $theme-colors;
$palette-gray: (
'0': $white,
'50': $gray-50,
'100': $gray-100,
'150': $gray-150,
'175': $gray-175,
'200': $gray-200,
'300': $gray-300,
'400': $gray-400,
'500': $gray-500,
'600': $gray-600,
'700': $gray-700,
'800': $gray-800,
'900': $gray-900,
'1000': $black
);
$palette-pastel: (
'green': $pastel-green,
'blue': $pastel-blue,
'yellow': $pastel-yellow,
'red': $pastel-red,
'orange': $pastel-orange,
'purple': $pastel-purple
);
///==========================================
......
......@@ -28,19 +28,47 @@ $warning :#5f5f5f;
$light :#eceeec;
$dark :#111111;
// Palettes
$theme-colors: map.merge(
$theme-colors,
(
'primary': $primary,
'secondary': $secondary,
'success': $success,
'danger': $danger,
'info': $info,
'warning': $warning,
'light': $light,
'dark': $dark,
'primary': $primary,
'secondary': $secondary,
'success': $success,
'danger': $danger,
'info': $info,
'warning': $warning,
'light': $light,
'dark': $dark,
)
);
$palette-semantic: $theme-colors;
$palette-gray: (
'0': $white,
'50': $gray-50,
'100': $gray-100,
'150': $gray-150,
'175': $gray-175,
'200': $gray-200,
'300': $gray-300,
'400': $gray-400,
'500': $gray-500,
'600': $gray-600,
'700': $gray-700,
'800': $gray-800,
'900': $gray-900,
'1000': $black
);
$palette-pastel: (
'green': $pastel-green,
'blue': $pastel-blue,
'yellow': $pastel-yellow,
'red': $pastel-red,
'orange': $pastel-orange,
'purple': $pastel-purple
);
///==========================================
......
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