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) ...@@ -27,9 +27,10 @@ import Gargantext.Components.GraphExplorer.Sidebar.DocList (docListWrapper)
import Gargantext.Components.GraphExplorer.Sidebar.Legend as Legend import Gargantext.Components.GraphExplorer.Sidebar.Legend as Legend
import Gargantext.Components.GraphExplorer.Store as GraphStore import Gargantext.Components.GraphExplorer.Store as GraphStore
import Gargantext.Components.GraphExplorer.Types as GET import Gargantext.Components.GraphExplorer.Types as GET
import Gargantext.Components.GraphExplorer.Utils as GEU
import Gargantext.Components.Lang (Lang(..)) import Gargantext.Components.Lang (Lang(..))
import Gargantext.Core.NgramsTable.Functions as NTC
import Gargantext.Config.REST (AffRESTError) import Gargantext.Config.REST (AffRESTError)
import Gargantext.Core.NgramsTable.Functions as NTC
import Gargantext.Core.NgramsTable.Types as CNT import Gargantext.Core.NgramsTable.Types as CNT
import Gargantext.Data.Array (mapMaybe) import Gargantext.Data.Array (mapMaybe)
import Gargantext.Ends (Frontends) import Gargantext.Ends (Frontends)
...@@ -37,7 +38,7 @@ import Gargantext.Hooks.FirstEffect (useFirstEffect') ...@@ -37,7 +38,7 @@ import Gargantext.Hooks.FirstEffect (useFirstEffect')
import Gargantext.Hooks.Sigmax.Types as SigmaxT import Gargantext.Hooks.Sigmax.Types as SigmaxT
import Gargantext.Sessions (Session) import Gargantext.Sessions (Session)
import Gargantext.Types (CTabNgramType, FrontendError(..), NodeID, TabSubType(..), TabType(..), TermList(..), modeTabType) 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.Reactix as R2
import Gargantext.Utils.Toestand as T2 import Gargantext.Utils.Toestand as T2
import Math as Math import Math as Math
...@@ -101,15 +102,53 @@ sideTabLegend :: R2.Leaf Props ...@@ -101,15 +102,53 @@ sideTabLegend :: R2.Leaf Props
sideTabLegend = R2.leaf sideTabLegendCpt sideTabLegend = R2.leaf sideTabLegendCpt
sideTabLegendCpt :: R.Component Props sideTabLegendCpt :: R.Component Props
sideTabLegendCpt = here.component "sideTabLegend" cpt sideTabLegendCpt = here.component "sideTabLegend" cpt where
where cpt { metaData: GET.MetaData { legend } } _ = do
cpt { metaData: GET.MetaData { legend } } _ = pure $ -- | 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 H.div
{ className: "graph-sidebar__legend-tab" } { className: "graph-sidebar__legend-tab" }
[ [
Legend.legend Legend.legend
{ items: Seq.fromFoldable legend } { legendSeq: Seq.fromFoldable legend
, extractedNodeList
, nodeCountList
, selectedNodeIds: store.selectedNodeIds
}
, ,
H.hr {} H.hr {}
, ,
......
...@@ -4,42 +4,196 @@ module Gargantext.Components.GraphExplorer.Sidebar.Legend ...@@ -4,42 +4,196 @@ module Gargantext.Components.GraphExplorer.Sidebar.Legend
import Prelude hiding (map) import Prelude hiding (map)
import Data.Array as A
import Data.Maybe (isJust, maybe)
import Data.Sequence (Seq) 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 as R
import Reactix.DOM.HTML as H import Reactix.DOM.HTML as H
import Toestand as T
import Gargantext.Components.GraphExplorer.Types (Legend(..), intColor)
import Gargantext.Utils.Reactix as R2
here :: R2.Here here :: R2.Here
here = R2.here "Gargantext.Components.GraphExplorer.Sidebar.Legend" 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 Props
legend = R2.leaf legendCpt legend = R2.leaf legendCpt
legendCpt :: R.Component Props legendCpt :: R.Component Props
legendCpt = here.component "legend" cpt where legendCpt = here.component "legend" cpt where
cpt { items } _ = pure $ cpt { legendSeq
, extractedNodeList
H.ul , nodeCountList
{ className: "graph-legend" } , selectedNodeIds
[ } _ = do
flip foldMap items \(Legend { id_, label }) -> -- | Hooks
-- |
H.li R.useEffectOnce' $ here.info2 "legend" extractedNodeList
{ className: "graph-legend__item" } -- | Render
[ -- |
H.span pure $
{ className: "graph-legend__code"
, style: { backgroundColor: intColor id_ } 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.text "+"
H.span ,
{ className: "graph-legend__caption" } H.text $ nbsp 1
[ H.text label ] ,
] H.text $ show countValue
] ,
H.text $ nbsp 1
,
H.text $ eq countValue 1 ? "node" $ "nodes"
]
]
...@@ -72,6 +72,12 @@ instance JSON.ReadForeign Cluster where ...@@ -72,6 +72,12 @@ instance JSON.ReadForeign Cluster where
instance JSON.WriteForeign Cluster where instance JSON.WriteForeign Cluster where
writeImpl (Cluster cl) = JSON.writeImpl $ Record.rename clustDefaultP clust_defaultP cl 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 { newtype Edge = Edge {
confluence :: Number confluence :: Number
......
module Gargantext.Components.GraphExplorer.Utils where module Gargantext.Components.GraphExplorer.Utils
( stEdgeToGET, stNodeToGET
import Data.Maybe (Maybe(..)) , normalizeNodes
, takeGreatestNodeByCluster, countNodeByCluster
) where
import Gargantext.Prelude 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.Components.GraphExplorer.Types as GET
import Gargantext.Hooks.Sigmax.Types as ST import Gargantext.Hooks.Sigmax.Types as ST
import Gargantext.Utils (getter)
import Gargantext.Utils.Array as GUA import Gargantext.Utils.Array as GUA
stEdgeToGET :: Record ST.Edge -> GET.Edge stEdgeToGET :: Record ST.Edge -> GET.Edge
stEdgeToGET { _original } = _original stEdgeToGET { _original } = _original
...@@ -24,6 +29,8 @@ stNodeToGET { id, label, x, y, _original: GET.Node { attributes, size, type_ } } ...@@ -24,6 +29,8 @@ stNodeToGET { id, label, x, y, _original: GET.Node { attributes, size, type_ } }
, y , y
} }
-----------------------------------------------------------------------
normalizeNodes :: Array GET.Node -> Array GET.Node normalizeNodes :: Array GET.Node -> Array GET.Node
normalizeNodes ns = map normalizeNode ns normalizeNodes ns = map normalizeNode ns
where where
...@@ -49,3 +56,37 @@ normalizeNodes ns = map normalizeNode ns ...@@ -49,3 +56,37 @@ normalizeNodes ns = map normalizeNode ns
Just ydiv -> 1.0 / ydiv Just ydiv -> 1.0 / ydiv
normalizeNode (GET.Node n@{ x, y }) = GET.Node $ n { x = x * xdivisor normalizeNode (GET.Node n@{ x, y }) = GET.Node $ n { x = x * xdivisor
, y = y * ydivisor } , 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 @@ ...@@ -142,8 +142,19 @@
$legend-code-height: 12px $legend-code-height: 12px
&__item &__item
display: flex
align-items: baseline
list-style: none 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 &__code
width: $legend-code-width width: $legend-code-width
...@@ -152,8 +163,32 @@ ...@@ -152,8 +163,32 @@
margin-right: space-x(2.5) margin-right: space-x(2.5)
border: 1px solid $gray-500 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 .graph-documentation
......
This diff is collapsed.
...@@ -312,7 +312,7 @@ ...@@ -312,7 +312,7 @@
transition: transform 0s, opacity 0s; transition: transform 0s, opacity 0s;
} }
@each $name, $value in $theme-colors { @each $name, $value in $palette-semantic {
&--#{ $name }:after { &--#{ $name }:after {
background-image: background-image:
......
...@@ -23,20 +23,6 @@ $warning :#0DE2EA; ...@@ -23,20 +23,6 @@ $warning :#0DE2EA;
$light :#e8e8e8; $light :#e8e8e8;
$dark :#000000; $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-50 : #121212; // (+)
$gray-100: #212529; $gray-100: #212529;
$gray-150: #2E2E2E; // (+) $gray-150: #2E2E2E; // (+)
...@@ -50,6 +36,49 @@ $gray-700: #DEE2E6; ...@@ -50,6 +36,49 @@ $gray-700: #DEE2E6;
$gray-800: #E9ECEF; $gray-800: #E9ECEF;
$gray-900: #F8F9FA; $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-text-color: $gray-600;
$annotation-field-base-alpha: 15%; $annotation-field-base-alpha: 15%;
......
...@@ -13,13 +13,41 @@ ...@@ -13,13 +13,41 @@
$primary: #005a9a; $primary: #005a9a;
$secondary: $blue; $secondary: $blue;
// Palettes
$theme-colors: map.merge( $theme-colors: map.merge(
$theme-colors, $theme-colors,
( (
"primary": $primary, "primary": $primary,
"secondary": $secondary, "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; ...@@ -28,6 +28,7 @@ $warning :#6e9fa5;
$light :#eceeec; $light :#eceeec;
$dark :#1e2b37; $dark :#1e2b37;
// Palettes
$theme-colors: map.merge( $theme-colors: map.merge(
$theme-colors, $theme-colors,
( (
...@@ -41,6 +42,33 @@ $theme-colors: map.merge( ...@@ -41,6 +42,33 @@ $theme-colors: map.merge(
'dark': $dark, '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 // Misc
$enable-rounded: false; $enable-rounded: false;
......
...@@ -27,6 +27,7 @@ $warning :#FC3C3C; ...@@ -27,6 +27,7 @@ $warning :#FC3C3C;
$light :#F2F2F0; $light :#F2F2F0;
$dark :#072247; $dark :#072247;
// Palettes
$theme-colors: map.merge( $theme-colors: map.merge(
$theme-colors, $theme-colors,
( (
...@@ -40,6 +41,33 @@ $theme-colors: map.merge( ...@@ -40,6 +41,33 @@ $theme-colors: map.merge(
'dark': $dark, '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; ...@@ -28,19 +28,47 @@ $warning :#5f5f5f;
$light :#eceeec; $light :#eceeec;
$dark :#111111; $dark :#111111;
// Palettes
$theme-colors: map.merge( $theme-colors: map.merge(
$theme-colors, $theme-colors,
( (
'primary': $primary, 'primary': $primary,
'secondary': $secondary, 'secondary': $secondary,
'success': $success, 'success': $success,
'danger': $danger, 'danger': $danger,
'info': $info, 'info': $info,
'warning': $warning, 'warning': $warning,
'light': $light, 'light': $light,
'dark': $dark, '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