ige-0.1.0.0: src/IGE/Layout.hs
module IGE.Layout
( layoutGr )
where
import Protolude hiding (force)
import IGE.Types
import Data.Graph.Inductive.Graph
import Data.Graph.Inductive.PatriciaTree
import qualified Data.Map.Strict as Map
import Data.Array
import Data.Array.MArray
import Data.Array.ST
import Control.Monad.ST
import Data.STRef
import Lens.Micro.Platform
import System.Random.MWC
data PEdge = Spring Double Double | Spacer Double Double
force :: PEdge -> ℂ -> ℂ -> ℂ
force (Spring k l) p1 p2 = ((k * (magnitude z - l)) :+ 0) * signum z
where z = p2 - p1
force (Spacer k l) p1 p2 = ((max 0 (k * (magnitude z - l))) :+ 0) * signum z
where z = p2 - p1
type Edges = Array (Int, Int) PEdge
type STNodes s = STArray s Int ℂ
type Nodes = Array Int ℂ
fdStep :: Double -> Edges -> STNodes s -> ST s Double
fdStep stepSize edges nodes = do
let stepSizeC = stepSize :+ 0
(0, n) <- getBounds nodes
maxForceRef <- newSTRef 0
forM_ [0..n] $ \i -> do
fRef <- newSTRef $ 0 :+ 0
p1 <- readArray nodes i
forM_ [0..n] $ \j -> do
p2 <- readArray nodes j
let edge = edges ! (i, j)
f <- readSTRef fRef
writeSTRef fRef $ f + (force edge p1 p2)
f <- readSTRef fRef
let dp = stepSizeC * f
writeArray nodes i $ p1 + dp
maxF <- readSTRef maxForceRef
if magnitude f > maxF
then writeSTRef maxForceRef $ magnitude f
else return ()
maxF <- readSTRef maxForceRef
return maxF
runFd :: Double -> Double -> Edges -> STNodes s -> ST s ()
runFd tolerance stepSize edges nodes = perturb >> loop
where
perturb = do
gen <- create
(0, n) <- getBounds nodes
forM_ [0..n] $ \i -> do
p <- readArray nodes i
cθ <- uniformR (0, 2 * pi) gen
cr <- uniformR (0, 0.2) gen
writeArray nodes i (p + mkPolar cr cθ)
loop = do
f <- fdStep stepSize edges nodes
if f > tolerance
then loop
else return ()
defaultSpring :: PEdge
defaultSpring = Spring 0.1 0.8
defaultSpacer :: PEdge
defaultSpacer = Spacer 0 0.5
defaultStepSize :: Double
defaultStepSize = 0.01
defaultTolerance :: Double
defaultTolerance = 0.001
initNodes :: Gr a b -> (Map.Map Node Int, Nodes)
initNodes g = ( Map.fromList $ zip (nodes g) [0..]
, listArray (0, n - 1) $ (\k -> fromIntegral (k `rem` d) :+ fromIntegral (k `quot` d)) <$> [0..n-1])
where
n = length $ nodes g
d = ceiling $ sqrt $ fromIntegral $ order g
initEdges :: Gr a b -> Map.Map Node Int -> Edges
initEdges g m = runSTArray $ do
edgeMat <- newArray ((0, 0), (n - 1, n - 1)) defaultSpacer
forM_ (over both (m Map.!) <$> edges g) $ \(n1, n2) -> do
writeArray edgeMat (n1, n2) defaultSpring
writeArray edgeMat (n2, n1) defaultSpring
return edgeMat
where
n = length $ nodes g
layoutGr :: Gr n e -> Map.Map Node ℂ
layoutGr g = Map.map (nodeVec !) nodeMap
where
(nodeMap, frozenNodes) = initNodes g
nodeVec = runSTArray $ do
let edgeMat = initEdges g nodeMap
nodeVec' <- thaw frozenNodes
runFd defaultTolerance defaultStepSize edgeMat nodeVec'
return nodeVec'