Scale<>Track<>Legend demo browser start

Scale<>Track<>Legend demo browser start

module Genetics.Browser.Track.Backend where

import Prelude

import Color (Color, black, textColor)
import Color.Scheme.Clrs (aqua, blue, fuchsia, green, lime, maroon, navy, olive, orange, purple, red, teal, yellow)
import Control.Alt ((<|>))
import Control.Monad.Aff (Aff)
import Control.Monad.Eff.Exception (error)
import Control.Monad.Error.Class (throwError)
import Control.Monad.Reader (class MonadReader, ask)
import Data.Argonaut (Json, _Array, _Number, _Object, _String)
import Data.Array ((:))
import Data.Array as Array
import Data.Bifunctor (bimap)
import Data.BigInt (BigInt)
import Data.BigInt as BigInt
import Data.Filterable (class Filterable, filterMap)
import Data.Foldable (class Foldable, fold, foldMap, foldl, length, maximum, sum)
import Data.FunctorWithIndex (mapWithIndex)
import Data.Int as Int
import Data.Lens (united, (^?), (^.))
import Data.Lens as Lens
import Data.Lens.Index (ix)
import Data.List (List)
import Data.List as List
import Data.Map (Map)
import Data.Map as Map
import Data.Maybe (Maybe(Just, Nothing), fromMaybe)
import Data.Monoid (class Monoid, mempty)
import Data.Monoid.Additive (Additive(..))
import Data.Newtype (class Newtype, ala, alaF, unwrap)
import Data.Ord.Max (Max(..))
import Data.Ratio (Ratio(..), denominator, numerator, (%))
import Data.Ratio as Ratio
import Data.Record as Record
import Data.Symbol (SProxy(..))
import Data.Traversable (scanl, traverse)
import Data.Tuple (Tuple(..), fst, snd, uncurry)
import Data.Variant (class Contractable, Variant)
import Data.Variant as Variant
import Genetics.Browser.Types (Bp(Bp), ChrId(ChrId), Point)
import Genetics.Browser.Types.Coordinates (BrowserPoint, CoordInterval, CoordSys(..), Interval, Normalized(..), _BrowserIntervals, _Index, _Interval, intervalToScreen, nPointToFrame, normPoint, viewIntervals)
import Genetics.Browser.View (Pixels)
import Graphics.Drawing (Drawing, circle, fillColor, filled, lineWidth, outlineColor, outlined, rectangle, scale, translate)
import Graphics.Drawing as Drawing
import Graphics.Drawing.Font (font, sansSerif)
import Network.HTTP.Affjax as Affjax
import Type.Prelude (class IsSymbol, class RowLacks)
import Unsafe.Coerce (unsafeCoerce)

intersection :: forall k a b.
                Ord k
             => Map k a
             -> Map k b
             -> Map k a
intersection a b = Map.filterKeys (flip Map.member b) a

zipMaps :: forall k a b.
           Ord k
        => Map k a
        -> Map k b
        -> Map k (Tuple a b)
zipMaps a b =
  let kas = Array.unzip $ Map.toAscUnfoldable $ a `intersection` b
      kbs = Array.unzip $ Map.toAscUnfoldable $ b `intersection` a
      kabs = uncurry Array.zip $ map (Array.zip (snd kas)) kbs
  in Map.fromFoldable kabs

zipMapsWith :: forall k a b c.
               Ord k
            => (a -> b -> c)
            -> Map k a
            -> Map k b
            -> Map k c
zipMapsWith f a b = uncurry f <$> zipMaps a b

type ChrCtx r = Map ChrId (Record r)

-- A renderer draws a single value to a normalized "canvas",
-- i.e. the whole chromosome is located in x = (0.0, 1.0),
-- and the whole track height in y = (0.0, 1.0).
-- It can fail if the given feature for some reason cannot be rendered,
-- either because the feature lacks information, or because the context lacks something.

type PureRenderer a = a -> Maybe { drawing :: Drawing
                                 , point   :: Normalized Point
                                 }

-- TODO Instead of tag & combine, we can just use Variant.match & Record.Builder!
-- tagRenderer :: forall a sym r1 r2.
-- combineRenderers :: forall r1 r2 r3.

type GWASFeature r = { score :: Number
                     , pos :: Bp
                     , chrId :: ChrId | r }

mkGwasRenderer :: forall m rf rctx.
                  MonadReader (ChrCtx (size :: Bp, color :: Color | rctx)) m
               => {min :: Number, max :: Number, sig :: Number}
               -> m (PureRenderer (GWASFeature rf))
mkGwasRenderer {min, max} = do
  ctx <- ask
  let renderer f = do
        size <- _.size <$> Map.lookup f.chrId ctx
        color <- _.color <$> Map.lookup f.chrId ctx
        let r = 2.2
            x = unwrap $ f.pos / size
            y = (f.score - min) / (max - min)
            c = circle x y r
            out = outlined (outlineColor color) c
            fill = filled (fillColor color) c
            drawing = out <> fill

        point <- normPoint {x, y}
        pure { drawing, point }
  pure renderer

mkAnnotRenderer :: forall m rf rctx.
                   MonadReader (ChrCtx (size :: Bp | rctx)) m
                => m (PureRenderer (Annot rf))
mkAnnotRenderer = do
  ctx <- ask
  let font' = font sansSerif 12 mempty
  let renderer f = do
        size <- _.size <$> Map.lookup f.chrId ctx

        let rad = 5.0
            x = unwrap $ f.pos / size
            y = 0.0
            c = circle x y rad
            out  = outlined (outlineColor maroon <> lineWidth 3.0) c
            fill = filled   (fillColor red) c
            -- the canvas is flipped so y-axis increases upward, need to flip the text too
            text' = scale 1.0 (-1.0)
                    $ Drawing.text font' (x+7.5) (y+2.5) (fillColor black) f.name
            drawing = out <> fill <> text'

        point <- normPoint {x, y}
        pure { drawing, point }

  pure renderer

pureRender :: forall f a.
              Filterable f
           => PureRenderer a
           -> f a
           -> f { drawing :: Drawing, point :: Normalized Point }
pureRender r as = filterMap r as

drawPureInterval :: forall f r .
                    Foldable f
                 => { height :: Pixels | r }
                 -> { width :: Pixels, offset :: Pixels }
                 -> f { drawing :: Drawing, point :: Normalized Point }
                 -> Drawing
drawPureInterval {height} {width, offset} = foldMap render'
  where render' {drawing, point} =
          let {x,y} = nPointToFrame width height $ point
          in translate (x + offset) y drawing

drawData :: forall i c f a.
            Ord i
         => Foldable f
         => Filterable f
         => CoordSys i BrowserPoint
         -> { width :: Pixels, height :: Pixels, yOffset :: Pixels }
         -> PureRenderer a
         -> Map i (f a)
         -> Interval BrowserPoint
         -> Drawing
drawData cs@(CoordSys s) canvasBox renderer dat =
  let drawings :: Map i (Array { drawing :: _, point :: _ })
      drawings = map (Array.fromFoldable <<< pureRender renderer) dat

      f :: Interval _  -> CoordInterval i _ -> { width :: _, offset :: _ }
      f iv c = (intervalToScreen canvasBox iv) (c^._Interval)

      g :: CoordInterval i _ -> Array { drawing :: _, point :: _ }
      g c = fromMaybe [] $ Map.lookup (c^._Index) drawings

      toDraw :: Interval BrowserPoint -> Array (Tuple _ (Array _))
      toDraw iv = (\x -> Tuple (f iv x) (g x)) <$> (viewIntervals cs iv)

      drawIvs :: Array (Tuple _ (Array _)) -> Drawing
      drawIvs = foldMap (uncurry $ drawPureInterval canvasBox)

  in \bView -> translate 0.0 (canvasBox.height - canvasBox.yOffset)
                $ scale 1.0 (-1.0)
                $ drawIvs (toDraw bView)

shiftRendererMinY :: forall rf.
                      RowLacks "minY" rf
                   => Number
                   -> { min :: Number, max :: Number, sig :: Number }
                   -> PureRenderer (Record rf)
                   -> PureRenderer (Record (minY :: Number | rf))
shiftRendererMinY dist s render f = do
  {drawing, point} <- render (Record.delete (SProxy :: SProxy "minY") f)
  let {x,y} = unwrap point
      normY = (f.minY - s.min) / (s.max - s.min)
      y' = min (normY + dist) 0.95

  point' <- normPoint {x, y: y'}
  pure {drawing, point: point'}

groupToChrs :: forall a f rData.
               Monoid (f {chrId :: ChrId | rData})
            => Foldable f
            => Applicative f
            => f { chrId :: ChrId | rData }
            -> Map ChrId (f { chrId :: ChrId | rData })
groupToChrs = foldl (\chrs r@{chrId} -> Map.alter (add r) chrId chrs ) mempty
  where add x Nothing   = Just $ pure x
        add x (Just xs) = Just $ pure x <> xs

fetchJSON :: forall a.
             (Json -> Maybe a)
          -> String
          -> Aff _ (Array a)
fetchJSON p url = do
  json <- _.response <$> Affjax.get url

  case json ^? _Array of
    Nothing -> throwError $ error "Parse error: JSON is not an array"
    Just as -> case traverse p as of
      Nothing -> throwError $ error "Failed to parse JSON features"
      Just fs -> pure $ fs

gemmaJSONParse :: Json -> Maybe (GWASFeature ())
gemmaJSONParse j = do
  obj <- j ^? _Object
  chrId <- ChrId <$> obj ^? ix "chr" <<< _String
  pos   <- Bp    <$> obj ^? ix "ps"  <<< _Number
  score <-           obj ^? ix "af"  <<< _Number
  pure {pos, score, chrId}

fetchGemmaJSON :: String -> Aff _ (Array (GWASFeature ()))
fetchGemmaJSON = fetchJSON gemmaJSONParse

type GeneRow r = ( geneID :: String
                 , desc :: String
                 , start :: Bp
                 , end :: Bp
                 , name :: String
                 , chrId :: ChrId | r )

type Gene r = Record (GeneRow r)

geneJSONParse :: Json -> Maybe (Gene ())
geneJSONParse j = do
  obj    <- j ^? _Object
  geneID <- obj ^? ix "Gene stable ID" <<< _String
  desc   <- obj ^? ix "Gene description" <<< _String
  name   <- obj ^? ix "Gene name" <<< _String
  start  <- Bp    <$> obj ^? ix "Gene start (bp)"  <<< _Number
  end    <- Bp    <$> obj ^? ix "Gene end (bp)"  <<< _Number
  chrId  <- ChrId <$> obj ^? ix "ChrId" <<< _String
  pure {geneID, desc, name, start, end, chrId}

fetchGeneJSON :: String -> Aff _ (Array (Gene ()))
fetchGeneJSON = fetchJSON geneJSONParse

type AnnotRow r = ( geneID :: String
                  , desc :: String
                  , pos :: Bp
                  , name :: String
                  , chrId :: ChrId | r )

type Annot r = Record (AnnotRow r)

fetchAnnotJSON :: String -> Aff _ (Array (Annot ()))
fetchAnnotJSON str = map toAnnot <$> fetchJSON geneJSONParse str
  where toAnnot :: Gene () -> Annot ()
        toAnnot gene = { geneID: gene.geneID
                       , desc: gene.desc
                       , pos: gene.start
                       , name: gene.name
                       , chrId: gene.chrId }

bumpAnnots :: forall rAnnot rGWAS.
             RowLacks "minY" (AnnotRow rAnnot)
          => Bp
          -> Map ChrId (List (GWASFeature rGWAS))
          -> Map ChrId (List (Annot rAnnot))
          -> Map ChrId (List (Annot (minY :: Number | rAnnot)))
bumpAnnots radius = zipMapsWith (map <<< bumpAnnot)
  where maxScoreIn :: Bp -> Bp -> List (GWASFeature rGWAS) -> Number
        maxScoreIn l r gwas = fromMaybe 0.5 $ maximum
                              $ map (\g -> if g.pos >= l && g.pos <= r
                                           then g.score else 0.0) gwas
        bumpAnnot :: List (GWASFeature rGWAS) -> Annot rAnnot -> Annot (minY :: Number | rAnnot)
        bumpAnnot l g = let minY = maxScoreIn (g.pos - radius) (g.pos + radius) l
                       in Record.insert (SProxy :: SProxy "minY") minY g

getDataDemo :: { gwas :: String
               , annots :: String }
            -> Aff _ { gwas  :: Map ChrId (List (GWASFeature ()       ))
                     , annots :: Map ChrId (List (Annot (minY :: Number)))
                     }
getDataDemo urls = do
  gwas <- fetchGemmaJSON urls.gwas
  annots' <- fetchAnnotJSON urls.annots
  -- Divide data by chromosomes
  let gwasChr :: Map ChrId (List (GWASFeature ()))
      gwasChr = List.fromFoldable <$> groupToChrs gwas
  let annotsChr :: Map ChrId (List (Annot ()))
      annotsChr = List.fromFoldable <$> groupToChrs annots'
  let bumpedAnnots :: Map ChrId (List (Annot (minY :: Number)))
      bumpedAnnots = bumpAnnots (Bp 1000000.0) gwasChr annotsChr

  pure { gwas: gwasChr, annots: bumpedAnnots }

renderersDemo :: forall r.
                 { min :: Number, max :: Number, sig :: Number }
              -> { gwas  :: PureRenderer (GWASFeature ()       )
                 , annots :: PureRenderer (Annot (minY :: Number)) }
renderersDemo s = { gwas, annots }
  where colorsCtx = zipMapsWith (\r {color} -> Record.insert (SProxy :: SProxy "color") color r)
                      mouseChrCtx mouseColors
        gwas :: PureRenderer (GWASFeature ())
        gwas = mkGwasRenderer s colorsCtx

        annots :: PureRenderer (Annot (minY :: Number))
        annots = shiftRendererMinY 0.06 s $ mkAnnotRenderer mouseChrCtx

ruler :: forall r r1.
         { min :: Number, max :: Number, sig :: Number }
      -> Color
      -> { width :: Pixels, height :: Pixels, yOffset :: Pixels | r}
      -> Drawing
ruler {min, max, sig} color f = outlined outline rulerDrawing
  where normY = (sig - min) / (max - min)
        y = f.height - (normY * f.height + f.yOffset)
        outline = outlineColor color
        rulerDrawing = Drawing.path [{x: 0.0, y}, {x: f.width, y}]

drawDemo :: forall f r.
            Foldable f
         => Filterable f
         => CoordSys _ _
         -> { min :: Number, max :: Number, sig :: Number }
         -> { width :: Pixels, height :: Pixels, yOffset :: Pixels }
         -> { gwas :: Map ChrId (f (GWASFeature ()))
            , annots :: Map ChrId (f (Annot (minY :: Number))) }
         -> Interval BrowserPoint
         -> Drawing
drawDemo cs s canvasBox {gwas, annots} =
  let renderers = renderersDemo s
      ruler' = ruler s red
      drawGwas   = drawData cs canvasBox renderers.gwas gwas
      drawAnnots = drawData cs canvasBox renderers.annots annots
  in \view -> (drawGwas view) <> (drawAnnots view) <> ruler' canvasBox

drawVScale :: forall r.
              Number
           -> Number
           -> { min :: Number, max :: Number, sig :: Number }
           -> Color
           -> Drawing
drawVScale width height vs col =
  -- should have some padding here too; hardcode for now
  let hPad = width  / 10.0
      vPad = height / 10.0
      x = width / 2.0
      y1 = vPad
      y2 = height - vPad
      p = Drawing.path [{x, y:y1}, {x, y:y2}]

  in outlined (outlineColor col <> lineWidth 3.0) p

drawLegendItem :: Number
               -> { text :: String, icon :: Drawing }
               -> Drawing
drawLegendItem w {text, icon} =
  let ft = font sansSerif 12 mempty
      t = scale 1.0 (-1.0) $ Drawing.text ft 32.0 0.0 (fillColor black) text
  in icon <> t

drawLegend :: Number
           -> Number
           -> Array { text :: String, icon :: Drawing }
           -> Drawing
drawLegend width height icons =
  let hPad = width  / 10.0
      vPad = height / 10.0
      n :: Int
      n = length icons
      x = hPad
      f :: Number -> { text :: String, icon :: Drawing } -> Drawing
      f y ic = translate x y $ drawLegendItem (width - 2.0*hPad) ic
      d = (height - 2.0*vPad) / (length icons)
      ds = mapWithIndex (\i ic -> f (vPad+(vPad*(Int.toNumber i))) ic) icons
  in fold ds

mkIcon :: Color -> String -> { text :: String, icon :: Drawing }
mkIcon c text =
  let sh = circle (-5.0) (-5.0) 10.0
      icon = outlined (outlineColor c <> lineWidth 2.0) sh <>
             filled (fillColor c) sh
  in {text, icon}

demoLegend :: Array {text :: String, icon :: Drawing}
demoLegend =
  [ mkIcon red "Red thing"
  , mkIcon blue "blue thing"
  , mkIcon purple "boop"
  ]

              -- the first 2 are used by all parts of the browser
demoBrowser :: forall f.
               Foldable f
            => Filterable f
            => CoordSys _ _
            -> { width :: Pixels, height :: Pixels }
            -- this by the vertical scale and the tracks
            -> { min :: Number, max :: Number, sig :: Number }
            -- these define the width of the non-track parts of the browser;
            -- the browser takes up the remaining space.
            -> { vScaleWidth :: Pixels
               , legendWidth :: Pixels }
            -- vScale only
            -> Color
            -- to be drawn in the legend on the RHS
            -> Array { text :: String, icon :: Drawing }
            -- finally, used by the main track
            -> { gwas :: Map ChrId (f (GWASFeature ()))
               , annots :: Map ChrId (f (Annot (minY :: Number))) }
            -> Interval BrowserPoint
            -- the drawing produced contains all 3 parts.
            -> Drawing
demoBrowser cs canvas vscale sizes vscaleColor legend {gwas, annots} =
  let track v = drawDemo cs vscale trackCanvas {gwas, annots} v
      trackCanvas = { width: canvas.width - sizes.vScaleWidth - sizes.legendWidth
                    , height: canvas.height, yOffset: 0.0 }

      vScale :: _ -> Drawing
      vScale _ = drawVScale sizes.vScaleWidth canvas.height vscale vscaleColor

      legendD :: _ -> Drawing
      legendD _ = drawLegend sizes.legendWidth canvas.height legend

  in \view -> vScale view <> track view <> legendD view

mouseChrIds :: Array ChrId
mouseChrIds =
            [ (ChrId "1")
            , (ChrId "2")
            , (ChrId "3")
            , (ChrId "4")
            , (ChrId "5")
            , (ChrId "6")
            , (ChrId "7")
            , (ChrId "8")
            , (ChrId "9")
            , (ChrId "10")
            , (ChrId "11")
            , (ChrId "12")
            , (ChrId "13")
            , (ChrId "14")
            , (ChrId "15")
            , (ChrId "16")
            , (ChrId "17")
            , (ChrId "18")
            , (ChrId "19")
            , (ChrId "X")
            , (ChrId "Y")
            ]

mouseChrs :: Map ChrId Bp
mouseChrs = Map.fromFoldable $ Array.zip mouseChrIds $
            [ (Bp 195471971.0)
            , (Bp 182113224.0)
            , (Bp 160039680.0)
            , (Bp 156508116.0)
            , (Bp 151834684.0)
            , (Bp 149736546.0)
            , (Bp 145441459.0)
            , (Bp 129401213.0)
            , (Bp 124595110.0)
            , (Bp 130694993.0)
            , (Bp 122082543.0)
            , (Bp 120129022.0)
            , (Bp 120421639.0)
            , (Bp 124902244.0)
            , (Bp 104043685.0)
            , (Bp 98207768.0)
            , (Bp 94987271.0)
            , (Bp 90702639.0)
            , (Bp 61431566.0)
            , (Bp 17103129.0)
            , (Bp 9174469.0)
            ]

mouseColors :: ChrCtx (color :: Color)
mouseColors = Map.fromFoldable $ Array.zip mouseChrIds $ map (\x -> {color: x})
              [ navy, blue, aqua, teal, olive
              , green, lime, yellow, orange, red
              , maroon, fuchsia, purple, navy, blue
              , aqua, teal, olive, green, lime
              , yellow ]

mouseChrCtx :: ChrCtx (size :: Bp)
mouseChrCtx = map (\size -> { size }) mouseChrs

module Genetics.Browser.Track.Backend where

import Prelude

import Color (Color, black, textColor)
import Color.Scheme.Clrs (aqua, blue, fuchsia, green, lime, maroon, navy, olive, orange, purple, red, teal, yellow)
import Control.Alt ((<|>))
import Control.Monad.Aff (Aff)
import Control.Monad.Eff.Exception (error)
import Control.Monad.Error.Class (throwError)
import Control.Monad.Reader (class MonadReader, ask)
import Data.Argonaut (Json, _Array, _Number, _Object, _String)
import Data.Array ((:))
import Data.Array as Array
import Data.Bifunctor (bimap)
import Data.BigInt (BigInt)
import Data.BigInt as BigInt
import Data.Filterable (class Filterable, filterMap)
import Data.Foldable (class Foldable, fold, foldMap, foldl, length, maximum, sum)
import Data.FunctorWithIndex (mapWithIndex)
import Data.Int as Int
import Data.Lens (united, (^?), (^.))
import Data.Lens as Lens
import Data.Lens.Index (ix)
import Data.List (List)
import Data.List as List
import Data.Map (Map)
import Data.Map as Map
import Data.Maybe (Maybe(Just, Nothing), fromMaybe)
import Data.Monoid (class Monoid, mempty)
import Data.Monoid.Additive (Additive(..))
import Data.Newtype (class Newtype, ala, alaF, unwrap)
import Data.Ord.Max (Max(..))
import Data.Ratio (Ratio(..), denominator, numerator, (%))
import Data.Ratio as Ratio
import Data.Record as Record
import Data.Symbol (SProxy(..))
import Data.Traversable (scanl, traverse)
import Data.Tuple (Tuple(..), fst, snd, uncurry)
import Data.Variant (class Contractable, Variant)
import Data.Variant as Variant
import Genetics.Browser.Types (Bp(Bp), ChrId(ChrId), Point)
import Genetics.Browser.Types.Coordinates (BrowserPoint, CoordInterval, CoordSys(..), Interval, Normalized(..), _BrowserIntervals, _Index, _Interval, intervalToScreen, nPointToFrame, normPoint, viewIntervals)
import Genetics.Browser.View (Pixels)
import Graphics.Drawing (Drawing, circle, fillColor, filled, lineWidth, outlineColor, outlined, rectangle, scale, translate)
import Graphics.Drawing as Drawing
import Graphics.Drawing.Font (font, sansSerif)
import Network.HTTP.Affjax as Affjax
import Type.Prelude (class IsSymbol, class RowLacks)
import Unsafe.Coerce (unsafeCoerce)

intersection :: forall k a b.
                Ord k
             => Map k a
             -> Map k b
             -> Map k a
intersection a b = Map.filterKeys (flip Map.member b) a

zipMaps :: forall k a b.
           Ord k
        => Map k a
        -> Map k b
        -> Map k (Tuple a b)
zipMaps a b =
  let kas = Array.unzip $ Map.toAscUnfoldable $ a `intersection` b
      kbs = Array.unzip $ Map.toAscUnfoldable $ b `intersection` a
      kabs = uncurry Array.zip $ map (Array.zip (snd kas)) kbs
  in Map.fromFoldable kabs

zipMapsWith :: forall k a b c.
               Ord k
            => (a -> b -> c)
            -> Map k a
            -> Map k b
            -> Map k c
zipMapsWith f a b = uncurry f <$> zipMaps a b

type ChrCtx r = Map ChrId (Record r)

-- A renderer draws a single value to a normalized "canvas",
-- i.e. the whole chromosome is located in x = (0.0, 1.0),
-- and the whole track height in y = (0.0, 1.0).
-- It can fail if the given feature for some reason cannot be rendered,
-- either because the feature lacks information, or because the context lacks something.

type PureRenderer a = a -> Maybe { drawing :: Drawing
                                 , point   :: Normalized Point
                                 }

-- TODO Instead of tag & combine, we can just use Variant.match & Record.Builder!
-- tagRenderer :: forall a sym r1 r2.
-- combineRenderers :: forall r1 r2 r3.

type GWASFeature r = { score :: Number
                     , pos :: Bp
                     , chrId :: ChrId | r }

mkGwasRenderer :: forall m rf rctx.
                  MonadReader (ChrCtx (size :: Bp, color :: Color | rctx)) m
               => {min :: Number, max :: Number, sig :: Number}
               -> m (PureRenderer (GWASFeature rf))
mkGwasRenderer {min, max} = do
  ctx <- ask
  let renderer f = do
        size <- _.size <$> Map.lookup f.chrId ctx
        color <- _.color <$> Map.lookup f.chrId ctx
        let r = 2.2
            x = unwrap $ f.pos / size
            y = (f.score - min) / (max - min)
            c = circle x y r
            out = outlined (outlineColor color) c
            fill = filled (fillColor color) c
            drawing = out <> fill

        point <- normPoint {x, y}
        pure { drawing, point }
  pure renderer

mkAnnotRenderer :: forall m rf rctx.
                   MonadReader (ChrCtx (size :: Bp | rctx)) m
                => m (PureRenderer (Annot rf))
mkAnnotRenderer = do
  ctx <- ask
  let font' = font sansSerif 12 mempty
  let renderer f = do
        size <- _.size <$> Map.lookup f.chrId ctx

        let rad = 5.0
            x = unwrap $ f.pos / size
            y = 0.0
            c = circle x y rad
            out  = outlined (outlineColor maroon <> lineWidth 3.0) c
            fill = filled   (fillColor red) c
            -- the canvas is flipped so y-axis increases upward, need to flip the text too
            text' = scale 1.0 (-1.0)
                    $ Drawing.text font' (x+7.5) (y+2.5) (fillColor black) f.name
            drawing = out <> fill <> text'

        point <- normPoint {x, y}
        pure { drawing, point }

  pure renderer

pureRender :: forall f a.
              Filterable f
           => PureRenderer a
           -> f a
           -> f { drawing :: Drawing, point :: Normalized Point }
pureRender r as = filterMap r as

drawPureInterval :: forall f r .
                    Foldable f
                 => { height :: Pixels | r }
                 -> { width :: Pixels, offset :: Pixels }
                 -> f { drawing :: Drawing, point :: Normalized Point }
                 -> Drawing
drawPureInterval {height} {width, offset} = foldMap render'
  where render' {drawing, point} =
          let {x,y} = nPointToFrame width height $ point
          in translate (x + offset) y drawing

drawData :: forall i c f a.
            Ord i
         => Foldable f
         => Filterable f
         => CoordSys i BrowserPoint
         -> { width :: Pixels, height :: Pixels, yOffset :: Pixels }
         -> PureRenderer a
         -> Map i (f a)
         -> Interval BrowserPoint
         -> Drawing
drawData cs@(CoordSys s) canvasBox renderer dat =
  let drawings :: Map i (Array { drawing :: _, point :: _ })
      drawings = map (Array.fromFoldable <<< pureRender renderer) dat

      f :: Interval _  -> CoordInterval i _ -> { width :: _, offset :: _ }
      f iv c = (intervalToScreen canvasBox iv) (c^._Interval)

      g :: CoordInterval i _ -> Array { drawing :: _, point :: _ }
      g c = fromMaybe [] $ Map.lookup (c^._Index) drawings

      toDraw :: Interval BrowserPoint -> Array (Tuple _ (Array _))
      toDraw iv = (\x -> Tuple (f iv x) (g x)) <$> (viewIntervals cs iv)

      drawIvs :: Array (Tuple _ (Array _)) -> Drawing
      drawIvs = foldMap (uncurry $ drawPureInterval canvasBox)

  in \bView -> translate 0.0 (canvasBox.height - canvasBox.yOffset)
                $ scale 1.0 (-1.0)
                $ drawIvs (toDraw bView)

shiftRendererMinY :: forall rf.
                      RowLacks "minY" rf
                   => Number
                   -> { min :: Number, max :: Number, sig :: Number }
                   -> PureRenderer (Record rf)
                   -> PureRenderer (Record (minY :: Number | rf))
shiftRendererMinY dist s render f = do
  {drawing, point} <- render (Record.delete (SProxy :: SProxy "minY") f)
  let {x,y} = unwrap point
      normY = (f.minY - s.min) / (s.max - s.min)
      y' = min (normY + dist) 0.95

  point' <- normPoint {x, y: y'}
  pure {drawing, point: point'}

groupToChrs :: forall a f rData.
               Monoid (f {chrId :: ChrId | rData})
            => Foldable f
            => Applicative f
            => f { chrId :: ChrId | rData }
            -> Map ChrId (f { chrId :: ChrId | rData })
groupToChrs = foldl (\chrs r@{chrId} -> Map.alter (add r) chrId chrs ) mempty
  where add x Nothing   = Just $ pure x
        add x (Just xs) = Just $ pure x <> xs

fetchJSON :: forall a.
             (Json -> Maybe a)
          -> String
          -> Aff _ (Array a)
fetchJSON p url = do
  json <- _.response <$> Affjax.get url

  case json ^? _Array of
    Nothing -> throwError $ error "Parse error: JSON is not an array"
    Just as -> case traverse p as of
      Nothing -> throwError $ error "Failed to parse JSON features"
      Just fs -> pure $ fs

gemmaJSONParse :: Json -> Maybe (GWASFeature ())
gemmaJSONParse j = do
  obj <- j ^? _Object
  chrId <- ChrId <$> obj ^? ix "chr" <<< _String
  pos   <- Bp    <$> obj ^? ix "ps"  <<< _Number
  score <-           obj ^? ix "af"  <<< _Number
  pure {pos, score, chrId}

fetchGemmaJSON :: String -> Aff _ (Array (GWASFeature ()))
fetchGemmaJSON = fetchJSON gemmaJSONParse

type GeneRow r = ( geneID :: String
                 , desc :: String
                 , start :: Bp
                 , end :: Bp
                 , name :: String
                 , chrId :: ChrId | r )

type Gene r = Record (GeneRow r)

geneJSONParse :: Json -> Maybe (Gene ())
geneJSONParse j = do
  obj    <- j ^? _Object
  geneID <- obj ^? ix "Gene stable ID" <<< _String
  desc   <- obj ^? ix "Gene description" <<< _String
  name   <- obj ^? ix "Gene name" <<< _String
  start  <- Bp    <$> obj ^? ix "Gene start (bp)"  <<< _Number
  end    <- Bp    <$> obj ^? ix "Gene end (bp)"  <<< _Number
  chrId  <- ChrId <$> obj ^? ix "ChrId" <<< _String
  pure {geneID, desc, name, start, end, chrId}

fetchGeneJSON :: String -> Aff _ (Array (Gene ()))
fetchGeneJSON = fetchJSON geneJSONParse

type AnnotRow r = ( geneID :: String
                  , desc :: String
                  , pos :: Bp
                  , name :: String
                  , chrId :: ChrId | r )

type Annot r = Record (AnnotRow r)

fetchAnnotJSON :: String -> Aff _ (Array (Annot ()))
fetchAnnotJSON str = map toAnnot <$> fetchJSON geneJSONParse str
  where toAnnot :: Gene () -> Annot ()
        toAnnot gene = { geneID: gene.geneID
                       , desc: gene.desc
                       , pos: gene.start
                       , name: gene.name
                       , chrId: gene.chrId }

bumpAnnots :: forall rAnnot rGWAS.
             RowLacks "minY" (AnnotRow rAnnot)
          => Bp
          -> Map ChrId (List (GWASFeature rGWAS))
          -> Map ChrId (List (Annot rAnnot))
          -> Map ChrId (List (Annot (minY :: Number | rAnnot)))
bumpAnnots radius = zipMapsWith (map <<< bumpAnnot)
  where maxScoreIn :: Bp -> Bp -> List (GWASFeature rGWAS) -> Number
        maxScoreIn l r gwas = fromMaybe 0.5 $ maximum
                              $ map (\g -> if g.pos >= l && g.pos <= r
                                           then g.score else 0.0) gwas
        bumpAnnot :: List (GWASFeature rGWAS) -> Annot rAnnot -> Annot (minY :: Number | rAnnot)
        bumpAnnot l g = let minY = maxScoreIn (g.pos - radius) (g.pos + radius) l
                       in Record.insert (SProxy :: SProxy "minY") minY g

getDataDemo :: { gwas :: String
               , annots :: String }
            -> Aff _ { gwas  :: Map ChrId (List (GWASFeature ()       ))
                     , annots :: Map ChrId (List (Annot (minY :: Number)))
                     }
getDataDemo urls = do
  gwas <- fetchGemmaJSON urls.gwas
  annots' <- fetchAnnotJSON urls.annots
  -- Divide data by chromosomes
  let gwasChr :: Map ChrId (List (GWASFeature ()))
      gwasChr = List.fromFoldable <$> groupToChrs gwas
  let annotsChr :: Map ChrId (List (Annot ()))
      annotsChr = List.fromFoldable <$> groupToChrs annots'
  let bumpedAnnots :: Map ChrId (List (Annot (minY :: Number)))
      bumpedAnnots = bumpAnnots (Bp 1000000.0) gwasChr annotsChr

  pure { gwas: gwasChr, annots: bumpedAnnots }

renderersDemo :: forall r.
                 { min :: Number, max :: Number, sig :: Number }
              -> { gwas  :: PureRenderer (GWASFeature ()       )
                 , annots :: PureRenderer (Annot (minY :: Number)) }
renderersDemo s = { gwas, annots }
  where colorsCtx = zipMapsWith (\r {color} -> Record.insert (SProxy :: SProxy "color") color r)
                      mouseChrCtx mouseColors
        gwas :: PureRenderer (GWASFeature ())
        gwas = mkGwasRenderer s colorsCtx

        annots :: PureRenderer (Annot (minY :: Number))
        annots = shiftRendererMinY 0.06 s $ mkAnnotRenderer mouseChrCtx

ruler :: forall r r1.
         { min :: Number, max :: Number, sig :: Number }
      -> Color
      -> { width :: Pixels, height :: Pixels, yOffset :: Pixels | r}
      -> Drawing
ruler {min, max, sig} color f = outlined outline rulerDrawing
  where normY = (sig - min) / (max - min)
        y = f.height - (normY * f.height + f.yOffset)
        outline = outlineColor color
        rulerDrawing = Drawing.path [{x: 0.0, y}, {x: f.width, y}]

drawDemo :: forall f r.
            Foldable f
         => Filterable f
         => CoordSys _ _
         -> { min :: Number, max :: Number, sig :: Number }
         -> { width :: Pixels, height :: Pixels, yOffset :: Pixels }
         -> { gwas :: Map ChrId (f (GWASFeature ()))
            , annots :: Map ChrId (f (Annot (minY :: Number))) }
         -> Interval BrowserPoint
         -> Drawing
drawDemo cs s canvasBox {gwas, annots} =
  let renderers = renderersDemo s
      ruler' = ruler s red
      drawGwas   = drawData cs canvasBox renderers.gwas gwas
      drawAnnots = drawData cs canvasBox renderers.annots annots
  in \view -> (drawGwas view) <> (drawAnnots view) <> ruler' canvasBox

drawVScale :: forall r.
              Number
           -> Number
           -> { min :: Number, max :: Number, sig :: Number }
           -> Color
           -> Drawing
drawVScale width height vs col =
  -- should have some padding here too; hardcode for now
  let hPad = width  / 10.0
      vPad = height / 10.0
      x = width / 2.0
      y1 = vPad
      y2 = height - vPad
      p = Drawing.path [{x, y:y1}, {x, y:y2}]

  in outlined (outlineColor col <> lineWidth 3.0) p

drawLegendItem :: Number
               -> { text :: String, icon :: Drawing }
               -> Drawing
drawLegendItem w {text, icon} =
  let ft = font sansSerif 12 mempty
      t = scale 1.0 (-1.0) $ Drawing.text ft 32.0 0.0 (fillColor black) text
  in icon <> t

drawLegend :: Number
           -> Number
           -> Array { text :: String, icon :: Drawing }
           -> Drawing
drawLegend width height icons =
  let hPad = width  / 10.0
      vPad = height / 10.0
      n :: Int
      n = length icons
      x = hPad
      f :: Number -> { text :: String, icon :: Drawing } -> Drawing
      f y ic = translate x y $ drawLegendItem (width - 2.0*hPad) ic
      d = (height - 2.0*vPad) / (length icons)
      ds = mapWithIndex (\i ic -> f (vPad+(vPad*(Int.toNumber i))) ic) icons
  in fold ds

mouseChrIds :: Array ChrId
mouseChrIds =
            [ (ChrId "1")
            , (ChrId "2")
            , (ChrId "3")
            , (ChrId "4")
            , (ChrId "5")
            , (ChrId "6")
            , (ChrId "7")
            , (ChrId "8")
            , (ChrId "9")
            , (ChrId "10")
            , (ChrId "11")
            , (ChrId "12")
            , (ChrId "13")
            , (ChrId "14")
            , (ChrId "15")
            , (ChrId "16")
            , (ChrId "17")
            , (ChrId "18")
            , (ChrId "19")
            , (ChrId "X")
            , (ChrId "Y")
            ]

mouseChrs :: Map ChrId Bp
mouseChrs = Map.fromFoldable $ Array.zip mouseChrIds $
            [ (Bp 195471971.0)
            , (Bp 182113224.0)
            , (Bp 160039680.0)
            , (Bp 156508116.0)
            , (Bp 151834684.0)
            , (Bp 149736546.0)
            , (Bp 145441459.0)
            , (Bp 129401213.0)
            , (Bp 124595110.0)
            , (Bp 130694993.0)
            , (Bp 122082543.0)
            , (Bp 120129022.0)
            , (Bp 120421639.0)
            , (Bp 124902244.0)
            , (Bp 104043685.0)
            , (Bp 98207768.0)
            , (Bp 94987271.0)
            , (Bp 90702639.0)
            , (Bp 61431566.0)
            , (Bp 17103129.0)
            , (Bp 9174469.0)
            ]

mouseColors :: ChrCtx (color :: Color)
mouseColors = Map.fromFoldable $ Array.zip mouseChrIds $ map (\x -> {color: x})
              [ navy, blue, aqua, teal, olive
              , green, lime, yellow, orange, red
              , maroon, fuchsia, purple, navy, blue
              , aqua, teal, olive, green, lime
              , yellow ]

mouseChrCtx :: ChrCtx (size :: Bp)
mouseChrCtx = map (\size -> { size }) mouseChrs


Providing skeleton PureScript file

Providing skeleton PureScript file

module Main where

main = Debug.Trace.trace "hello"


produce a force layout from the package set

generating colors based on the base of the repo (ie paf31, purescript, purescript-contrib, slamdata)

module PackageData where

import Prelude
import D3.ForceSimulation (ForceType(Links), GroupedForceLayout)
import Data.Argonaut (gDecodeJson, Json, class DecodeJson, decodeJson, (.?))
import Data.Array (zip, concatMap, (!!), (:), (..), nub, length)
import Data.Foldable (foldr)
import Data.Generic (class Generic)
import Data.Int (toNumber)
import Data.Map (fromFoldable, Map, lookup)
import Data.Maybe (fromMaybe)
import Data.String (split, stripPrefix)
import Network.HTTP.Affjax (Affjax, get, URL)

--  read in the file
--  convert to desired format
--  make available to mainline for visualisation
--  stretch goal - load in various files and return array of data so that transforms can happen

-- | Ultra-simplistic types for now
type PackageName   = String
type VersionString = String

-- foreign import packagesJSON :: Json
-- foreign import packagesSmol :: Json
--
-- data PackageData = PackageData  { dependencies :: Array PackageName
--                                 , repo    :: URL
--                                 , version :: VersionString }
--
-- data PackageSet = StrMap PackageData
--
-- derive instance genericPackageData :: Generic PackageData
-- derive instance genericPackageSet  :: Generic PackageSet
--
-- instance decodeJsonPackageData :: DecodeJson PackageData where
--   decodeJson json = do
--     obj <- decodeJson json
--     dependencies <- obj .? "dependencies"
--     repo         <- obj .? "repo"
--     version      <- obj .? "version"
--     pure $ PackageData { dependencies, repo, version }
--
-- instance decodeJsonPackageSet :: DecodeJson PackageSet where
--   decodeJson json = do
--     obj <- gDecodeJson json
--     pure $ obj

-- Data defs for ForceLayout
type Node = { id :: String, group :: Number }
type Link = { source :: String, target :: String, value :: Number }

-- Data def (almost) for packages.json
type PackageSet = Array PackageData
type PackageData =  { name :: String
                    , dependencies :: Array PackageName
                    , repo         :: URL
                    , version      :: VersionString }

type RepoName = String

getRepoName :: URL -> RepoName
getRepoName url = fromMaybe "parse error" repo
  where
    subs = split "/" url
    repo = subs !! 3    -- drops the "http:" "" "github.com" parts and the sub-dirs

mydata :: GroupedForceLayout
mydata = convert dummyData (getGroups dummyData)

-- | Conversion function
convert :: PackageSet -> Map RepoName Number -> GroupedForceLayout
convert ps groups = { nodes: map getNode ps
                    , links: concatMap getLinks ps }
  where
    getLinks :: PackageData -> Array Link
    getLinks { name: name, dependencies: deps } = map (createLink name) deps
      where
        createLink s t = { source: s, target: t, value: 1.0 }
    getNode :: PackageData -> Node
    getNode { name: name, repo: repo } = { id: name, group: groupID } where
      key   = getRepoName repo
      groupID = fromMaybe 0.0 (lookup key groups)

getGroups :: PackageSet -> Map RepoName Number
getGroups ps = fromFoldable $ zip uniqueRepos indices
  where
    uniqueRepos = nub $ foldr (\p acc -> (getRepoName p.repo):acc ) [] ps
    indices     = map toNumber (1..length uniqueRepos)

dummyData :: PackageSet
dummyData = [
  { name: "aff",
    "dependencies": [
      "arrays",
      "bifunctors",
      "console",
      "const",
      "contravariant",
      "control",
      "distributive",
      "eff",
      "either",
      "exceptions",
      "foldable-traversable",
      "functions",
      "functors",
      "identity",
      "invariant",
      "lazy",
      "maybe",
      "monoid",
      "newtype",
      "nonempty",
      "parallel",
      "partial",
      "prelude",
      "refs",
      "st",
      "tailrec",
      "transformers",
      "tuples",
      "unfoldable",
      "unsafe-coerce"
    ],
    "repo": "https://github.com/slamdata/purescript-aff.git",
    "version": "v2.0.0"
  },
  { name: "argonaut",
    "dependencies": [
      "argonaut-codecs",
      "argonaut-core",
      "argonaut-traversals",
      "arrays",
      "bifunctors",
      "const",
      "contravariant",
      "control",
      "distributive",
      "eff",
      "either",
      "enums",
      "foldable-traversable",
      "functions",
      "functors",
      "generics",
      "identity",
      "integers",
      "invariant",
      "lazy",
      "lists",
      "maps",
      "math",
      "maybe",
      "monoid",
      "newtype",
      "nonempty",
      "partial",
      "prelude",
      "profunctor",
      "profunctor-lenses",
      "proxy",
      "sets",
      "st",
      "strings",
      "tailrec",
      "transformers",
      "tuples",
      "unfoldable",
      "unsafe-coerce"
    ],
    "repo": "https://github.com/purescript-contrib/purescript-argonaut.git",
    "version": "v2.0.0"
  },
  { name: "argonaut-codecs",
    "dependencies": [
      "argonaut-core",
      "arrays",
      "bifunctors",
      "control",
      "eff",
      "either",
      "enums",
      "foldable-traversable",
      "functions",
      "generics",
      "identity",
      "integers",
      "invariant",
      "lazy",
      "lists",
      "maps",
      "math",
      "maybe",
      "monoid",
      "newtype",
      "nonempty",
      "partial",
      "prelude",
      "proxy",
      "st",
      "strings",
      "tailrec",
      "tuples",
      "unfoldable"
    ],
    "repo": "https://github.com/purescript-contrib/purescript-argonaut-codecs.git",
    "version": "v2.0.0"
  },
  { name: "argonaut-core",
    "dependencies": [
      "arrays",
      "bifunctors",
      "control",
      "eff",
      "either",
      "enums",
      "foldable-traversable",
      "functions",
      "generics",
      "identity",
      "invariant",
      "lazy",
      "lists",
      "maps",
      "maybe",
      "monoid",
      "newtype",
      "nonempty",
      "partial",
      "prelude",
      "proxy",
      "st",
      "strings",
      "tailrec",
      "tuples",
      "unfoldable"
    ],
    "repo": "https://github.com/purescript-contrib/purescript-argonaut-core.git",
    "version": "v2.0.0"
  },
  { name: "argonaut-traversals",
    "dependencies": [
      "argonaut-codecs",
      "argonaut-core",
      "arrays",
      "bifunctors",
      "const",
      "contravariant",
      "control",
      "distributive",
      "eff",
      "either",
      "enums",
      "foldable-traversable",
      "functions",
      "functors",
      "generics",
      "identity",
      "integers",
      "invariant",
      "lazy",
      "lists",
      "maps",
      "math",
      "maybe",
      "monoid",
      "newtype",
      "nonempty",
      "partial",
      "prelude",
      "profunctor",
      "profunctor-lenses",
      "proxy",
      "sets",
      "st",
      "strings",
      "tailrec",
      "transformers",
      "tuples",
      "unfoldable",
      "unsafe-coerce"
    ],
    "repo": "https://github.com/purescript-contrib/purescript-argonaut-traversals.git",
    "version": "v2.0.0"
  },
  { name: "arraybuffer-types",
    "dependencies": [],
    "repo": "https://github.com/purescript-contrib/purescript-arraybuffer-types.git",
    "version": "v0.2.0"
  },
  { name: "arrays",
    "dependencies": [
      "bifunctors",
      "console",
      "control",
      "eff",
      "either",
      "foldable-traversable",
      "identity",
      "invariant",
      "maybe",
      "monoid",
      "newtype",
      "nonempty",
      "partial",
      "prelude",
      "st",
      "tailrec",
      "tuples",
      "unfoldable"
    ],
    "repo": "https://github.com/purescript/purescript-arrays.git",
    "version": "v3.0.0"
  },
  { name: "assert",
    "dependencies": [
      "console",
      "eff",
      "prelude"
    ],
    "repo": "https://github.com/purescript/purescript-assert.git",
    "version": "v1.0.0"
  },
  { name: "bifunctors",
    "dependencies": [
      "control",
      "newtype",
      "prelude"
    ],
    "repo": "https://github.com/purescript/purescript-bifunctors.git",
    "version": "v2.0.0"
  },
  { name: "catenable-lists",
    "dependencies": [
      "arrays",
      "assert",
      "bifunctors",
      "control",
      "eff",
      "either",
      "foldable-traversable",
      "generics",
      "identity",
      "invariant",
      "lazy",
      "monoid",
      "newtype",
      "nonempty",
      "lists",
      "maybe",
      "partial",
      "prelude",
      "proxy",
      "st",
      "strings",
      "tailrec",
      "tuples",
      "unfoldable"
    ],
    "repo": "https://github.com/purescript/purescript-catenable-lists.git",
    "version": "v3.0.0"
  },
  { name: "console",
    "dependencies": [
      "prelude",
      "eff"
    ],
    "repo": "https://github.com/purescript/purescript-console.git",
    "version": "v2.0.0"
  },
  { name: "const",
    "dependencies": [
      "bifunctors",
      "contravariant",
      "control",
      "either",
      "foldable-traversable",
      "invariant",
      "maybe",
      "monoid",
      "newtype",
      "prelude",
      "tuples"
    ],
    "repo": "https://github.com/purescript/purescript-const.git",
    "version": "v2.0.0"
  },
  { name: "contravariant",
    "dependencies": [
      "bifunctors",
      "control",
      "either",
      "foldable-traversable",
      "invariant",
      "maybe",
      "monoid",
      "newtype",
      "prelude",
      "tuples"
    ],
    "repo": "https://github.com/purescript/purescript-contravariant.git",
    "version": "v2.0.0"
  },
  { name: "control",
    "dependencies": [
      "prelude"
    ],
    "repo": "https://github.com/purescript/purescript-control.git",
    "version": "v2.0.0"
  },
  { name: "coroutines",
    "dependencies": [
      "arrays",
      "bifunctors",
      "console",
      "const",
      "contravariant",
      "control",
      "distributive",
      "eff",
      "either",
      "exists",
      "foldable-traversable",
      "freet",
      "functors",
      "identity",
      "invariant",
      "lazy",
      "maybe",
      "monoid",
      "newtype",
      "nonempty",
      "parallel",
      "partial",
      "prelude",
      "profunctor",
      "refs",
      "st",
      "tailrec",
      "transformers",
      "tuples",
      "unfoldable",
      "unsafe-coerce"
    ],
    "repo": "https://github.com/purescript-contrib/purescript-coroutines.git",
    "version": "v3.0.0"
  },
  { name: "datetime",
    "dependencies": [
      "arrays",
      "assert",
      "bifunctors",
      "console",
      "control",
      "eff",
      "either",
      "enums",
      "foldable-traversable",
      "functions",
      "generics",
      "identity",
      "integers",
      "invariant",
      "math",
      "maybe",
      "monoid",
      "newtype",
      "nonempty",
      "partial",
      "prelude",
      "proxy",
      "st",
      "strings",
      "tailrec",
      "tuples",
      "unfoldable"
    ],
    "repo": "https://github.com/purescript/purescript-datetime.git",
    "version": "v2.0.0"
  },
  { name: "day",
    "dependencies": [
      "arrays",
      "bifunctors",
      "catenable-lists",
      "console",
      "const",
      "contravariant",
      "control",
      "distributive",
      "eff",
      "either",
      "exists",
      "foldable-traversable",
      "free",
      "functors",
      "generics",
      "identity",
      "inject",
      "invariant",
      "lazy",
      "lists",
      "maybe",
      "monoid",
      "newtype",
      "nonempty",
      "pairing",
      "partial",
      "prelude",
      "proxy",
      "st",
      "strings",
      "tailrec",
      "transformers",
      "tuples",
      "unfoldable",
      "unsafe-coerce"
    ],
    "repo": "https://github.com/paf31/purescript-day.git",
    "version": "v7.0.0"
  },
  { name: "distributions",
    "dependencies": [
      "arrays",
      "bifunctors",
      "assert",
      "console",
      "control",
      "eff",
      "either",
      "foldable-traversable",
      "generics",
      "identity",
      "invariant",
      "lazy",
      "lists",
      "math",
      "maybe",
      "monoid",
      "newtype",
      "nonempty",
      "partial",
      "prelude",
      "proxy",
      "st",
      "strings",
      "tailrec",
      "tuples",
      "unfoldable"
    ],
    "repo": "https://github.com/paf31/purescript-distributions.git",
    "version": "v2.0.0"
  },
  { name: "distributive",
    "dependencies": [
      "bifunctors",
      "control",
      "foldable-traversable",
      "identity",
      "invariant",
      "maybe",
      "monoid",
      "newtype",
      "prelude"
    ],
    "repo": "https://github.com/purescript/purescript-distributive.git",
    "version": "v2.0.0"
  },
  { name: "dom",
    "dependencies": [
      "arrays",
      "bifunctors",
      "control",
      "datetime",
      "distributive",
      "eff",
      "either",
      "enums",
      "exceptions",
      "foldable-traversable",
      "foreign",
      "functions",
      "generics",
      "identity",
      "integers",
      "invariant",
      "js-date",
      "lazy",
      "lists",
      "math",
      "maybe",
      "media-types",
      "monoid",
      "newtype",
      "nonempty",
      "nullable",
      "partial",
      "prelude",
      "proxy",
      "st",
      "strings",
      "tailrec",
      "transformers",
      "tuples",
      "unfoldable",
      "unsafe-coerce"
    ],
    "repo": "https://github.com/purescript-contrib/purescript-dom.git",
    "version": "v3.0.0"
  },
  { name: "eff",
    "dependencies": [
      "prelude"
    ],
    "repo": "https://github.com/purescript/purescript-eff.git",
    "version": "v2.0.0"
  },
  { name: "either",
    "dependencies": [
      "bifunctors",
      "control",
      "foldable-traversable",
      "invariant",
      "maybe",
      "monoid",
      "newtype",
      "prelude"
    ],
    "repo": "https://github.com/purescript/purescript-either.git",
    "version": "v2.0.0"
  },
  { name: "enums",
    "dependencies": [
      "bifunctors",
      "control",
      "either",
      "foldable-traversable",
      "invariant",
      "maybe",
      "monoid",
      "newtype",
      "partial",
      "prelude",
      "strings",
      "tuples",
      "unfoldable"
    ],
    "repo": "https://github.com/purescript/purescript-enums.git",
    "version": "v2.0.0"
  },
  { name: "exceptions",
    "dependencies": [
      "bifunctors",
      "control",
      "eff",
      "either",
      "foldable-traversable",
      "invariant",
      "maybe",
      "monoid",
      "newtype",
      "prelude"
    ],
    "repo": "https://github.com/purescript/purescript-exceptions.git",
    "version": "v2.0.0"
  },
  { name: "exists",
    "dependencies": [
      "unsafe-coerce"
    ],
    "repo": "https://github.com/purescript/purescript-exists.git",
    "version": "v2.0.0"
  },
  { name: "foldable-traversable",
    "dependencies": [
      "bifunctors",
      "control",
      "invariant",
      "maybe",
      "monoid",
      "newtype",
      "prelude"
    ],
    "repo": "https://github.com/purescript/purescript-foldable-traversable.git",
    "version": "v2.0.0"
  },
  { name: "folds",
    "dependencies": [
      "bifunctors",
      "control",
      "distributive",
      "either",
      "foldable-traversable",
      "identity",
      "invariant",
      "maybe",
      "monoid",
      "newtype",
      "prelude",
      "profunctor",
      "tuples"
    ],
    "repo": "https://github.com/paf31/purescript-folds.git",
    "version": "v2.0.0"
  },
  { name: "foreign",
    "dependencies": [
      "arrays",
      "console",
      "bifunctors",
      "control",
      "distributive",
      "eff",
      "either",
      "foldable-traversable",
      "functions",
      "generics",
      "identity",
      "integers",
      "invariant",
      "lazy",
      "lists",
      "math",
      "maybe",
      "monoid",
      "newtype",
      "nonempty",
      "partial",
      "prelude",
      "proxy",
      "st",
      "strings",
      "unfoldable",
      "tailrec",
      "transformers",
      "tuples"
    ],
    "repo": "https://github.com/purescript/purescript-foreign.git",
    "version": "v3.0.0"
  },
  { name: "foreign-generic",
    "dependencies": [
      "arrays",
      "assert",
      "bifunctors",
      "console",
      "control",
      "distributive",
      "eff",
      "either",
      "exceptions",
      "foldable-traversable",
      "foreign",
      "functions",
      "generics",
      "globals",
      "identity",
      "integers",
      "invariant",
      "lazy",
      "lists",
      "maps",
      "math",
      "maybe",
      "monoid",
      "newtype",
      "nonempty",
      "nullable",
      "partial",
      "prelude",
      "proxy",
      "st",
      "strings",
      "tailrec",
      "transformers",
      "tuples",
      "unfoldable"
    ],
    "repo": "https://github.com/paf31/purescript-foreign-generic.git",
    "version": "v2.0.0"
  },
  { name: "foreign-lens",
    "dependencies": [
      "arrays",
      "bifunctors",
      "console",
      "const",
      "contravariant",
      "control",
      "distributive",
      "eff",
      "either",
      "foldable-traversable",
      "foreign",
      "functions",
      "functors",
      "generics",
      "identity",
      "integers",
      "invariant",
      "lazy",
      "lists",
      "maps",
      "math",
      "maybe",
      "monoid",
      "newtype",
      "nonempty",
      "partial",
      "prelude",
      "profunctor",
      "profunctor-lenses",
      "proxy",
      "sets",
      "st",
      "strings",
      "tailrec",
      "transformers",
      "tuples",
      "unfoldable",
      "unsafe-coerce"
    ],
    "repo": "https://github.com/paf31/purescript-foreign-lens.git",
    "version": "v2.0.0"
  },
  { name: "free",
    "dependencies": [
      "arrays",
      "bifunctors",
      "console",
      "const",
      "contravariant",
      "catenable-lists",
      "distributive",
      "control",
      "eff",
      "either",
      "exists",
      "foldable-traversable",
      "functors",
      "generics",
      "inject",
      "identity",
      "invariant",
      "lazy",
      "lists",
      "maybe",
      "monoid",
      "newtype",
      "nonempty",
      "partial",
      "prelude",
      "proxy",
      "st",
      "strings",
      "tailrec",
      "tuples",
      "transformers",
      "unfoldable",
      "unsafe-coerce"
    ],
    "repo": "https://github.com/purescript/purescript-free.git",
    "version": "v3.0.0"
  },
  { name: "freet",
    "dependencies": [
      "arrays",
      "bifunctors",
      "console",
      "control",
      "distributive",
      "eff",
      "either",
      "exists",
      "foldable-traversable",
      "identity",
      "invariant",
      "lazy",
      "maybe",
      "monoid",
      "newtype",
      "nonempty",
      "partial",
      "prelude",
      "st",
      "tailrec",
      "transformers",
      "tuples",
      "unfoldable",
      "unsafe-coerce"
    ],
    "repo": "https://github.com/purescript-contrib/purescript-freet.git",
    "version": "v2.0.0"
  },
  { name: "functions",
    "dependencies": [
      "prelude"
    ],
    "repo": "https://github.com/purescript/purescript-functions.git",
    "version": "v2.0.0"
  },
  { name: "functors",
    "dependencies": [
      "bifunctors",
      "control",
      "const",
      "contravariant",
      "either",
      "foldable-traversable",
      "invariant",
      "maybe",
      "monoid",
      "newtype",
      "prelude",
      "tuples"
    ],
    "repo": "https://github.com/purescript/purescript-functors.git",
    "version": "v1.0.0"
  },
  { name: "generics",
    "dependencies": [
      "arrays",
      "assert",
      "bifunctors",
      "console",
      "control",
      "eff",
      "either",
      "foldable-traversable",
      "identity",
      "invariant",
      "maybe",
      "monoid",
      "newtype",
      "nonempty",
      "partial",
      "prelude",
      "proxy",
      "st",
      "strings",
      "tailrec",
      "tuples",
      "unfoldable"
    ],
    "repo": "https://github.com/purescript/purescript-generics.git",
    "version": "v3.1.0"
  },
  { name: "generics-rep",
    "dependencies": [
      "control",
      "invariant",
      "monoid",
      "newtype",
      "prelude"
    ],
    "repo": "https://github.com/purescript/purescript-generics-rep.git",
    "version": "v3.0.0"
  },
  { name: "globals",
    "dependencies": [],
    "repo": "https://github.com/purescript/purescript-globals.git",
    "version": "v2.0.0"
  },
  { name: "graphs",
    "dependencies": [
      "arrays",
      "bifunctors",
      "catenable-lists",
      "control",
      "eff",
      "either",
      "foldable-traversable",
      "functions",
      "generics",
      "identity",
      "invariant",
      "lazy",
      "lists",
      "maps",
      "maybe",
      "monoid",
      "newtype",
      "nonempty",
      "partial",
      "prelude",
      "proxy",
      "sets",
      "st",
      "strings",
      "tailrec",
      "tuples",
      "unfoldable"
    ],
    "repo": "https://github.com/purescript/purescript-graphs.git",
    "version": "v2.0.0"
  },
  { name: "hoist",
    "dependencies": [
      "bifunctors",
      "const",
      "contravariant",
      "control",
      "either",
      "foldable-traversable",
      "functors",
      "invariant",
      "maybe",
      "monoid",
      "newtype",
      "prelude",
      "tuples"
    ],
    "repo": "https://github.com/paf31/purescript-hoist.git",
    "version": "v3.0.0"
  },
  { name: "http-methods",
    "dependencies": [
      "arrays",
      "bifunctors",
      "control",
      "eff",
      "either",
      "foldable-traversable",
      "generics",
      "identity",
      "invariant",
      "maybe",
      "monoid",
      "newtype",
      "nonempty",
      "partial",
      "prelude",
      "proxy",
      "st",
      "strings",
      "tailrec",
      "tuples",
      "unfoldable"
    ],
    "repo": "https://github.com/purescript-contrib/purescript-http-methods.git",
    "version": "v2.0.0"
  },
  { name: "identity",
    "dependencies": [
      "bifunctors",
      "control",
      "foldable-traversable",
      "invariant",
      "maybe",
      "monoid",
      "newtype",
      "prelude"
    ],
    "repo": "https://github.com/purescript/purescript-identity.git",
    "version": "v2.0.0"
  },
  { name: "inject",
    "dependencies": [
      "bifunctors",
      "control",
      "const",
      "contravariant",
      "either",
      "foldable-traversable",
      "functors",
      "invariant",
      "maybe",
      "monoid",
      "newtype",
      "prelude",
      "tuples"
    ],
    "repo": "https://github.com/purescript/purescript-inject.git",
    "version": "v3.0.0"
  },
  { name: "integers",
    "dependencies": [
      "control",
      "invariant",
      "math",
      "maybe",
      "monoid",
      "newtype",
      "partial",
      "prelude"
    ],
    "repo": "https://github.com/purescript/purescript-integers.git",
    "version": "v2.0.0"
  },
  { name: "invariant",
    "dependencies": [
      "prelude"
    ],
    "repo": "https://github.com/purescript/purescript-invariant.git",
    "version": "v2.0.0"
  },
  { name: "isomorphisms",
    "dependencies": [
      "bifunctors",
      "contravariant",
      "control",
      "distributive",
      "either",
      "foldable-traversable",
      "identity",
      "invariant",
      "maybe",
      "monoid",
      "newtype",
      "prelude",
      "profunctor",
      "tuples"
    ],
    "repo": "https://github.com/paf31/purescript-isomorphisms.git",
    "version": "v3.0.0"
  },
  { name: "js-date",
    "dependencies": [
      "arrays",
      "bifunctors",
      "control",
      "datetime",
      "distributive",
      "eff",
      "either",
      "enums",
      "exceptions",
      "foldable-traversable",
      "foreign",
      "functions",
      "generics",
      "identity",
      "integers",
      "invariant",
      "lazy",
      "lists",
      "math",
      "maybe",
      "monoid",
      "newtype",
      "nonempty",
      "partial",
      "prelude",
      "proxy",
      "st",
      "strings",
      "tailrec",
      "transformers",
      "tuples",
      "unfoldable"
    ],
    "repo": "https://github.com/purescript-contrib/purescript-js-date.git",
    "version": "v2.0.0"
  },
  { name: "js-timers",
    "dependencies": [
      "eff",
      "prelude"
    ],
    "repo": "https://github.com/purescript-contrib/purescript-js-timers.git",
    "version": "v2.0.0"
  },
  { name: "lazy",
    "dependencies": [
      "control",
      "invariant",
      "monoid",
      "newtype",
      "prelude"
    ],
    "repo": "https://github.com/purescript/purescript-lazy.git",
    "version": "v2.0.0"
  },
  { name: "leibniz",
    "dependencies": [
      "prelude",
      "unsafe-coerce"
    ],
    "repo": "https://github.com/paf31/purescript-leibniz.git",
    "version": "v2.0.0"
  },
  { name: "lens-simple",
    "dependencies": [
      "bifunctors",
      "control",
      "foldable-traversable",
      "invariant",
      "maybe",
      "monoid",
      "newtype",
      "prelude",
      "tuples"
    ],
    "repo": "https://github.com/paf31/purescript-lens-simple.git",
    "version": "v2.0.0"
  },
  { name: "lists",
    "dependencies": [
      "arrays",
      "bifunctors",
      "assert",
      "console",
      "control",
      "eff",
      "either",
      "foldable-traversable",
      "generics",
      "identity",
      "invariant",
      "lazy",
      "math",
      "maybe",
      "monoid",
      "newtype",
      "nonempty",
      "partial",
      "prelude",
      "proxy",
      "st",
      "strings",
      "tailrec",
      "tuples",
      "unfoldable"
    ],
    "repo": "https://github.com/purescript/purescript-lists.git",
    "version": "v3.0.0"
  },
  { name: "machines",
    "dependencies": [
      "arrays",
      "bifunctors",
      "control",
      "distributive",
      "eff",
      "either",
      "foldable-traversable",
      "generics",
      "identity",
      "invariant",
      "lazy",
      "lists",
      "maybe",
      "monoid",
      "newtype",
      "nonempty",
      "partial",
      "prelude",
      "profunctor",
      "proxy",
      "st",
      "strings",
      "tailrec",
      "tuples",
      "unfoldable"
    ],
    "repo": "https://github.com/purescript-contrib/purescript-machines.git",
    "version": "v2.0.0"
  },
  { name: "maps",
    "dependencies": [
      "arrays",
      "bifunctors",
      "control",
      "eff",
      "either",
      "foldable-traversable",
      "functions",
      "generics",
      "identity",
      "invariant",
      "lazy",
      "lists",
      "maybe",
      "monoid",
      "newtype",
      "nonempty",
      "partial",
      "prelude",
      "proxy",
      "st",
      "strings",
      "tailrec",
      "tuples",
      "unfoldable"
    ],
    "repo": "https://github.com/purescript/purescript-maps.git",
    "version": "v2.0.0"
  },
  { name: "math",
    "dependencies": [],
    "repo": "https://github.com/purescript/purescript-math.git",
    "version": "v2.0.0"
  },
  { name: "maybe",
    "dependencies": [
      "control",
      "invariant",
      "monoid",
      "newtype",
      "prelude"
    ],
    "repo": "https://github.com/purescript/purescript-maybe.git",
    "version": "v2.0.0"
  },
  { name: "media-types",
    "dependencies": [
      "arrays",
      "bifunctors",
      "control",
      "eff",
      "either",
      "foldable-traversable",
      "generics",
      "identity",
      "invariant",
      "maybe",
      "monoid",
      "newtype",
      "nonempty",
      "partial",
      "prelude",
      "proxy",
      "st",
      "strings",
      "tailrec",
      "tuples",
      "unfoldable"
    ],
    "repo": "https://github.com/purescript-contrib/purescript-media-types.git",
    "version": "v2.0.0"
  },
  { name: "memoize",
    "dependencies": [
      "arrays",
      "assert",
      "bifunctors",
      "console",
      "control",
      "eff",
      "either",
      "foldable-traversable",
      "generics",
      "generics-rep",
      "identity",
      "integers",
      "invariant",
      "lazy",
      "lists",
      "math",
      "maybe",
      "monoid",
      "newtype",
      "nonempty",
      "partial",
      "prelude",
      "proxy",
      "st",
      "strings",
      "tailrec",
      "tuples",
      "unfoldable"
    ],
    "repo": "https://github.com/paf31/purescript-memoize.git",
    "version": "v3.0.0"
  },
  { name: "monoid",
    "dependencies": [
      "control",
      "invariant",
      "newtype",
      "prelude"
    ],
    "repo": "https://github.com/purescript/purescript-monoid.git",
    "version": "v2.0.0"
  },
  { name: "newtype",
    "dependencies": [
      "prelude"
    ],
    "repo": "https://github.com/purescript/purescript-newtype.git",
    "version": "v1.0.0"
  },
  { name: "node-buffer",
    "dependencies": [
      "control",
      "eff",
      "invariant",
      "maybe",
      "monoid",
      "newtype",
      "prelude"
    ],
    "repo": "https://github.com/purescript-node/purescript-node-buffer.git",
    "version": "v2.0.0"
  },
  { name: "node-child-process",
    "dependencies": [
      "arrays",
      "bifunctors",
      "control",
      "datetime",
      "distributive",
      "eff",
      "either",
      "enums",
      "exceptions",
      "foldable-traversable",
      "foreign",
      "functions",
      "generics",
      "globals",
      "identity",
      "integers",
      "invariant",
      "js-date",
      "lazy",
      "lists",
      "maps",
      "math",
      "maybe",
      "monoid",
      "newtype",
      "node-buffer",
      "node-fs",
      "node-path",
      "node-streams",
      "nonempty",
      "nullable",
      "partial",
      "posix-types",
      "prelude",
      "proxy",
      "st",
      "strings",
      "tailrec",
      "transformers",
      "tuples",
      "unfoldable",
      "unsafe-coerce"
    ],
    "repo": "https://github.com/purescript-node/purescript-node-child-process.git",
    "version": "v3.0.0"
  },
  { name: "node-fs",
    "dependencies": [
      "arrays",
      "bifunctors",
      "control",
      "datetime",
      "distributive",
      "eff",
      "either",
      "enums",
      "exceptions",
      "foldable-traversable",
      "foreign",
      "functions",
      "generics",
      "globals",
      "identity",
      "integers",
      "invariant",
      "js-date",
      "lazy",
      "lists",
      "math",
      "maybe",
      "monoid",
      "newtype",
      "node-buffer",
      "node-path",
      "node-streams",
      "nonempty",
      "nullable",
      "partial",
      "prelude",
      "proxy",
      "st",
      "strings",
      "tailrec",
      "transformers",
      "tuples",
      "unfoldable",
      "unsafe-coerce"
    ],
    "repo": "https://github.com/purescript-node/purescript-node-fs.git",
    "version": "v3.0.0"
  },
  { name: "node-fs-aff",
    "dependencies": [
      "aff",
      "arrays",
      "bifunctors",
      "console",
      "const",
      "contravariant",
      "control",
      "datetime",
      "distributive",
      "eff",
      "either",
      "enums",
      "exceptions",
      "foldable-traversable",
      "foreign",
      "functions",
      "functors",
      "generics",
      "globals",
      "identity",
      "integers",
      "invariant",
      "js-date",
      "lazy",
      "lists",
      "math",
      "maybe",
      "monoid",
      "newtype",
      "node-buffer",
      "node-fs",
      "node-path",
      "node-streams",
      "nonempty",
      "nullable",
      "parallel",
      "partial",
      "prelude",
      "proxy",
      "refs",
      "st",
      "strings",
      "tailrec",
      "transformers",
      "tuples",
      "unfoldable",
      "unsafe-coerce"
    ],
    "repo": "https://github.com/purescript-node/purescript-node-fs-aff.git",
    "version": "v3.0.0"
  },
  { name: "node-http",
    "dependencies": [
      "arrays",
      "bifunctors",
      "contravariant",
      "control",
      "distributive",
      "eff",
      "either",
      "exceptions",
      "foldable-traversable",
      "foreign",
      "functions",
      "generics",
      "identity",
      "integers",
      "invariant",
      "lazy",
      "lists",
      "maps",
      "math",
      "maybe",
      "monoid",
      "newtype",
      "node-buffer",
      "node-streams",
      "node-url",
      "nonempty",
      "nullable",
      "options",
      "partial",
      "prelude",
      "proxy",
      "st",
      "strings",
      "tailrec",
      "transformers",
      "tuples",
      "unfoldable",
      "unsafe-coerce"
    ],
    "repo": "https://github.com/purescript-node/purescript-node-http.git",
    "version": "v2.0.0"
  },
  { name: "node-path",
    "dependencies": [],
    "repo": "https://github.com/purescript-node/purescript-node-path.git",
    "version": "v1.0.0"
  },
  { name: "node-process",
    "dependencies": [
      "arrays",
      "bifunctors",
      "console",
      "control",
      "datetime",
      "distributive",
      "eff",
      "either",
      "enums",
      "exceptions",
      "foldable-traversable",
      "foreign",
      "functions",
      "generics",
      "globals",
      "identity",
      "integers",
      "invariant",
      "js-date",
      "lazy",
      "lists",
      "maps",
      "math",
      "maybe",
      "monoid",
      "newtype",
      "node-buffer",
      "node-fs",
      "node-path",
      "node-streams",
      "nonempty",
      "nullable",
      "partial",
      "posix-types",
      "prelude",
      "proxy",
      "st",
      "strings",
      "tailrec",
      "transformers",
      "tuples",
      "unfoldable",
      "unsafe-coerce"
    ],
    "repo": "https://github.com/purescript-node/purescript-node-process.git",
    "version": "v3.0.0"
  },
  { name: "node-readline",
    "dependencies": [
      "arrays",
      "bifunctors",
      "console",
      "contravariant",
      "control",
      "datetime",
      "distributive",
      "eff",
      "either",
      "enums",
      "exceptions",
      "foldable-traversable",
      "foreign",
      "functions",
      "generics",
      "globals",
      "identity",
      "integers",
      "invariant",
      "js-date",
      "lazy",
      "lists",
      "maps",
      "math",
      "maybe",
      "monoid",
      "newtype",
      "node-buffer",
      "node-fs",
      "node-path",
      "node-process",
      "node-streams",
      "nonempty",
      "nullable",
      "options",
      "partial",
      "posix-types",
      "prelude",
      "proxy",
      "st",
      "strings",
      "tailrec",
      "transformers",
      "tuples",
      "unfoldable",
      "unsafe-coerce"
    ],
    "repo": "https://github.com/purescript-node/purescript-node-readline.git",
    "version": "v2.0.0"
  },
  { name: "node-streams",
    "dependencies": [
      "bifunctors",
      "control",
      "eff",
      "either",
      "exceptions",
      "foldable-traversable",
      "invariant",
      "maybe",
      "monoid",
      "newtype",
      "node-buffer",
      "prelude"
    ],
    "repo": "https://github.com/purescript-node/purescript-node-streams.git",
    "version": "v2.0.0"
  },
  { name: "node-url",
    "dependencies": [
      "control",
      "functions",
      "invariant",
      "maybe",
      "monoid",
      "newtype",
      "nullable",
      "prelude"
    ],
    "repo": "https://github.com/purescript-node/purescript-node-url.git",
    "version": "v2.0.0"
  },
  { name: "nonempty",
    "dependencies": [
      "bifunctors",
      "control",
      "foldable-traversable",
      "invariant",
      "maybe",
      "monoid",
      "newtype",
      "prelude"
    ],
    "repo": "https://github.com/purescript/purescript-nonempty.git",
    "version": "v3.0.0"
  },
  { name: "now",
    "dependencies": [
      "arrays",
      "bifunctors",
      "control",
      "datetime",
      "eff",
      "either",
      "enums",
      "foldable-traversable",
      "functions",
      "generics",
      "identity",
      "integers",
      "invariant",
      "math",
      "maybe",
      "monoid",
      "newtype",
      "nonempty",
      "partial",
      "prelude",
      "proxy",
      "st",
      "strings",
      "tailrec",
      "tuples",
      "unfoldable"
    ],
    "repo": "https://github.com/purescript-contrib/purescript-now.git",
    "version": "v2.0.0"
  },
  { name: "nullable",
    "dependencies": [
      "control",
      "functions",
      "invariant",
      "maybe",
      "monoid",
      "newtype",
      "prelude"
    ],
    "repo": "https://github.com/purescript-contrib/purescript-nullable.git",
    "version": "v2.0.0"
  },
  { name: "options",
    "dependencies": [
      "arrays",
      "bifunctors",
      "contravariant",
      "control",
      "distributive",
      "eff",
      "either",
      "foldable-traversable",
      "foreign",
      "functions",
      "generics",
      "identity",
      "integers",
      "invariant",
      "lazy",
      "lists",
      "maps",
      "math",
      "maybe",
      "monoid",
      "newtype",
      "nonempty",
      "partial",
      "prelude",
      "proxy",
      "st",
      "strings",
      "tailrec",
      "transformers",
      "tuples",
      "unfoldable"
    ],
    "repo": "https://github.com/purescript-contrib/purescript-options.git",
    "version": "v2.0.0"
  },
  { name: "orders",
    "dependencies": [
      "control",
      "invariant",
      "monoid",
      "newtype",
      "prelude"
    ],
    "repo": "https://github.com/purescript/purescript-orders.git",
    "version": "v2.0.0"
  },
  { name: "pairing",
    "dependencies": [
      "arrays",
      "bifunctors",
      "catenable-lists",
      "const",
      "contravariant",
      "control",
      "distributive",
      "eff",
      "either",
      "exists",
      "foldable-traversable",
      "free",
      "functors",
      "generics",
      "identity",
      "inject",
      "invariant",
      "lazy",
      "lists",
      "maybe",
      "monoid",
      "newtype",
      "nonempty",
      "partial",
      "prelude",
      "proxy",
      "st",
      "strings",
      "tailrec",
      "transformers",
      "tuples",
      "unfoldable",
      "unsafe-coerce"
    ],
    "repo": "https://github.com/paf31/purescript-pairing.git",
    "version": "v2.0.0"
  },
  { name: "parallel",
    "dependencies": [
      "arrays",
      "bifunctors",
      "const",
      "contravariant",
      "control",
      "distributive",
      "eff",
      "either",
      "foldable-traversable",
      "functors",
      "identity",
      "invariant",
      "lazy",
      "maybe",
      "monoid",
      "newtype",
      "nonempty",
      "partial",
      "prelude",
      "refs",
      "st",
      "tailrec",
      "transformers",
      "tuples",
      "unfoldable"
    ],
    "repo": "https://github.com/purescript/purescript-parallel.git",
    "version": "v2.0.0"
  },
  { name: "partial",
    "dependencies": [],
    "repo": "https://github.com/purescript/purescript-partial.git",
    "version": "v1.1.2"
  },
  { name: "posix-types",
    "dependencies": [
      "control",
      "invariant",
      "maybe",
      "monoid",
      "newtype",
      "prelude"
    ],
    "repo": "https://github.com/purescript-node/purescript-posix-types.git",
    "version": "v2.0.0"
  },
  { name: "pprint",
    "dependencies": [
      "arrays",
      "bifunctors",
      "console",
      "control",
      "eff",
      "either",
      "foldable-traversable",
      "identity",
      "invariant",
      "maybe",
      "monoid",
      "newtype",
      "nonempty",
      "partial",
      "prelude",
      "st",
      "strings",
      "tailrec",
      "tuples",
      "unfoldable"
    ],
    "repo": "https://github.com/paf31/purescript-pprint.git",
    "version": "v2.0.0"
  },
  { name: "preface",
    "dependencies": [],
    "repo": "https://github.com/paf31/purescript-preface.git",
    "version": "v1.1.0"
  },
  { name: "prelude",
    "dependencies": [],
    "repo": "https://github.com/purescript/purescript-prelude.git",
    "version": "v2.1.0"
  },
  { name: "profunctor",
    "dependencies": [
      "bifunctors",
      "control",
      "distributive",
      "either",
      "foldable-traversable",
      "identity",
      "invariant",
      "maybe",
      "prelude",
      "monoid",
      "newtype",
      "tuples"
    ],
    "repo": "https://github.com/purescript/purescript-profunctor.git",
    "version": "v2.0.0"
  },
  { name: "profunctor-lenses",
    "dependencies": [
      "arrays",
      "bifunctors",
      "const",
      "contravariant",
      "control",
      "distributive",
      "eff",
      "either",
      "foldable-traversable",
      "functions",
      "functors",
      "generics",
      "identity",
      "invariant",
      "lazy",
      "lists",
      "maps",
      "maybe",
      "monoid",
      "newtype",
      "nonempty",
      "partial",
      "prelude",
      "profunctor",
      "proxy",
      "sets",
      "st",
      "strings",
      "tailrec",
      "transformers",
      "tuples",
      "unfoldable",
      "unsafe-coerce"
    ],
    "repo": "https://github.com/purescript-contrib/purescript-profunctor-lenses.git",
    "version": "v2.0.0"
  },
  { name: "proxy",
    "dependencies": [],
    "repo": "https://github.com/purescript/purescript-proxy.git",
    "version": "v1.0.0"
  },
  { name: "psci-support",
    "dependencies": [
      "console",
      "eff",
      "prelude"
    ],
    "repo": "https://github.com/purescript/purescript-psci-support.git",
    "version": "v2.0.0"
  },
  { name: "quickcheck",
    "dependencies": [
      "arrays",
      "bifunctors",
      "console",
      "control",
      "distributive",
      "eff",
      "either",
      "exceptions",
      "exists",
      "foldable-traversable",
      "generics",
      "identity",
      "integers",
      "invariant",
      "lazy",
      "lists",
      "math",
      "maybe",
      "monoid",
      "newtype",
      "nonempty",
      "partial",
      "prelude",
      "profunctor",
      "proxy",
      "psci-support",
      "random",
      "st",
      "strings",
      "tailrec",
      "transformers",
      "tuples",
      "unfoldable",
      "unsafe-coerce"
    ],
    "repo": "https://github.com/purescript/purescript-quickcheck.git",
    "version": "v3.0.0"
  },
  { name: "random",
    "dependencies": [
      "control",
      "eff",
      "integers",
      "invariant",
      "math",
      "maybe",
      "monoid",
      "newtype",
      "partial",
      "prelude"
    ],
    "repo": "https://github.com/purescript/purescript-random.git",
    "version": "v2.0.0"
  },
  { name: "reflection",
    "dependencies": [
      "control",
      "invariant",
      "monoid",
      "newtype",
      "prelude",
      "proxy",
      "unsafe-coerce"
    ],
    "repo": "https://github.com/paf31/purescript-reflection.git",
    "version": "v2.0.0"
  },
  { name: "refs",
    "dependencies": [
      "eff",
      "prelude"
    ],
    "repo": "https://github.com/purescript/purescript-refs.git",
    "version": "v2.0.0"
  },
  { name: "safely",
    "dependencies": [
      "arrays",
      "assert",
      "bifunctors",
      "console",
      "control",
      "distributive",
      "eff",
      "either",
      "exists",
      "foldable-traversable",
      "freet",
      "generics",
      "identity",
      "invariant",
      "lazy",
      "lists",
      "math",
      "maybe",
      "monoid",
      "newtype",
      "nonempty",
      "partial",
      "prelude",
      "proxy",
      "st",
      "strings",
      "tailrec",
      "transformers",
      "tuples",
      "unfoldable",
      "unsafe-coerce"
    ],
    "repo": "https://github.com/paf31/purescript-safely.git",
    "version": "v2.0.0"
  },
  { name: "semirings",
    "dependencies": [
      "arrays",
      "bifunctors",
      "control",
      "either",
      "eff",
      "foldable-traversable",
      "generics",
      "identity",
      "invariant",
      "lazy",
      "lists",
      "maybe",
      "monoid",
      "newtype",
      "nonempty",
      "partial",
      "proxy",
      "strings",
      "prelude",
      "st",
      "tailrec",
      "tuples",
      "unfoldable"
    ],
    "repo": "https://github.com/purescript/purescript-semirings.git",
    "version": "v3.0.0"
  },
  { name: "sets",
    "dependencies": [
      "arrays",
      "bifunctors",
      "control",
      "eff",
      "either",
      "foldable-traversable",
      "functions",
      "generics",
      "identity",
      "invariant",
      "lazy",
      "lists",
      "maps",
      "maybe",
      "monoid",
      "newtype",
      "nonempty",
      "partial",
      "prelude",
      "proxy",
      "st",
      "strings",
      "tailrec",
      "tuples",
      "unfoldable"
    ],
    "repo": "https://github.com/purescript/purescript-sets.git",
    "version": "v2.0.0"
  },
  { name: "st",
    "dependencies": [
      "eff",
      "prelude"
    ],
    "repo": "https://github.com/purescript/purescript-st.git",
    "version": "v2.0.0"
  },
  { name: "strings",
    "dependencies": [
      "bifunctors",
      "control",
      "either",
      "foldable-traversable",
      "invariant",
      "maybe",
      "monoid",
      "newtype",
      "prelude"
    ],
    "repo": "https://github.com/purescript/purescript-strings.git",
    "version": "v2.0.0"
  },
  { name: "strongcheck",
    "dependencies": [
      "arrays",
      "bifunctors",
      "catenable-lists",
      "console",
      "const",
      "contravariant",
      "control",
      "distributive",
      "eff",
      "either",
      "enums",
      "exceptions",
      "exists",
      "foldable-traversable",
      "free",
      "functors",
      "generics",
      "identity",
      "inject",
      "integers",
      "invariant",
      "lazy",
      "lists",
      "machines",
      "math",
      "maybe",
      "monoid",
      "newtype",
      "nonempty",
      "partial",
      "prelude",
      "profunctor",
      "proxy",
      "random",
      "st",
      "strings",
      "tailrec",
      "transformers",
      "tuples",
      "unfoldable",
      "unsafe-coerce"
    ],
    "repo": "https://github.com/purescript-contrib/purescript-strongcheck.git",
    "version": "v2.0.0"
  },
  { name: "symbols",
    "dependencies": [
      "unsafe-coerce"
    ],
    "repo": "https://github.com/purescript/purescript-symbols.git",
    "version": "v1.0.1"
  },
  { name: "tailrec",
    "dependencies": [
      "bifunctors",
      "console",
      "control",
      "eff",
      "either",
      "foldable-traversable",
      "identity",
      "invariant",
      "maybe",
      "monoid",
      "newtype",
      "partial",
      "prelude",
      "st"
    ],
    "repo": "https://github.com/purescript/purescript-tailrec.git",
    "version": "v2.0.0"
  },
  { name: "taylor",
    "dependencies": [
      "arrays",
      "assert",
      "bifunctors",
      "console",
      "control",
      "eff",
      "either",
      "foldable-traversable",
      "generics",
      "identity",
      "invariant",
      "lazy",
      "lists",
      "math",
      "maybe",
      "monoid",
      "newtype",
      "nonempty",
      "partial",
      "prelude",
      "proxy",
      "st",
      "strings",
      "tailrec",
      "tuples",
      "unfoldable"
    ],
    "repo": "https://github.com/paf31/purescript-taylor.git",
    "version": "v2.0.0"
  },
  { name: "these",
    "dependencies": [
      "arrays",
      "bifunctors",
      "control",
      "eff",
      "either",
      "foldable-traversable",
      "generics",
      "identity",
      "invariant",
      "maybe",
      "monoid",
      "newtype",
      "nonempty",
      "partial",
      "prelude",
      "proxy",
      "st",
      "strings",
      "tailrec",
      "tuples",
      "unfoldable"
    ],
    "repo": "https://github.com/purescript-contrib/purescript-these.git",
    "version": "v2.0.0"
  },
  { name: "transformers",
    "dependencies": [
      "arrays",
      "bifunctors",
      "console",
      "control",
      "eff",
      "distributive",
      "either",
      "foldable-traversable",
      "identity",
      "invariant",
      "lazy",
      "maybe",
      "monoid",
      "newtype",
      "nonempty",
      "partial",
      "prelude",
      "st",
      "tailrec",
      "tuples",
      "unfoldable"
    ],
    "repo": "https://github.com/purescript/purescript-transformers.git",
    "version": "v2.0.1"
  },
  { name: "tropical",
    "dependencies": [
      "globals",
      "math",
      "newtype",
      "prelude"
    ],
    "repo": "https://github.com/paf31/purescript-tropical.git",
    "version": "v2.0.0"
  },
  { name: "tuples",
    "dependencies": [
      "bifunctors",
      "control",
      "foldable-traversable",
      "invariant",
      "maybe",
      "monoid",
      "newtype",
      "prelude"
    ],
    "repo": "https://github.com/purescript/purescript-tuples.git",
    "version": "v2.0.0"
  },
  { name: "type-equality",
    "dependencies": [],
    "repo": "https://github.com/purescript/purescript-type-equality.git",
    "version": "v1.0.0"
  },
  { name: "unfoldable",
    "dependencies": [
      "bifunctors",
      "control",
      "foldable-traversable",
      "invariant",
      "maybe",
      "monoid",
      "newtype",
      "partial",
      "prelude",
      "tuples"
    ],
    "repo": "https://github.com/purescript/purescript-unfoldable.git",
    "version": "v2.0.0"
  },
  { name: "unsafe-coerce",
    "dependencies": [],
    "repo": "https://github.com/purescript/purescript-unsafe-coerce.git",
    "version": "v1.0.0"
  },
  { name: "validation",
    "dependencies": [
      "bifunctors",
      "control",
      "newtype",
      "prelude"
    ],
    "repo": "https://github.com/purescript/purescript-validation.git",
    "version": "v2.0.0"
  }
]

module PackageData where

import Prelude
import D3.ForceSimulation (ForceType(Links), GroupedForceLayout)
import Data.Argonaut (gDecodeJson, Json, class DecodeJson, decodeJson, (.?))
import Data.Array (concatMap)
import Data.Generic (class Generic)
import Network.HTTP.Affjax (Affjax, get, URL)

--  read in the file
--  convert to desired format
--  make available to mainline for visualisation
--  stretch goal - load in various files and return array of data so that transforms can happen

-- | Ultra-simplistic types for now
type PackageName   = String
type VersionString = String

-- foreign import packagesJSON :: Json
-- foreign import packagesSmol :: Json
--
-- data PackageData = PackageData  { dependencies :: Array PackageName
--                                 , repo    :: URL
--                                 , version :: VersionString }
--
-- data PackageSet = StrMap PackageData
--
-- derive instance genericPackageData :: Generic PackageData
-- derive instance genericPackageSet  :: Generic PackageSet
--
-- instance decodeJsonPackageData :: DecodeJson PackageData where
--   decodeJson json = do
--     obj <- decodeJson json
--     dependencies <- obj .? "dependencies"
--     repo         <- obj .? "repo"
--     version      <- obj .? "version"
--     pure $ PackageData { dependencies, repo, version }
--
-- instance decodeJsonPackageSet :: DecodeJson PackageSet where
--   decodeJson json = do
--     obj <- gDecodeJson json
--     pure $ obj

-- Data defs for ForceLayout
type Node = { id :: String, group :: Number }
type Link = { source :: String, target :: String, value :: Number }

-- Data def (almost) for packages.json
type PackageSet = Array PackageData
type PackageData =  { name :: String
                    , data :: PackageDetails }

type PackageDetails = { dependencies :: Array PackageName
                      , repo         :: URL
                      , version      :: VersionString }

mydata = convert dummyData

-- | Conversion function
convert :: PackageSet -> GroupedForceLayout
convert ps = { nodes: map getNode ps
             , links: concatMap getLinks ps }
  where
    getLinks :: PackageData -> Array Link
    getLinks { name: name, data: { dependencies: deps } } = map (createLink name) deps
      where
        createLink s t = { source: s, target: t, value: 1.0 }
    getNode :: PackageData -> Node
    getNode { name: name } = { id: name, group: 0.0 }

dummyData :: PackageSet
dummyData = [ { name: "awn"
              , data: {
                  dependencies: []
                , repo: "some/path/"
                , version: "v1.0.0" } },
              { name: "bel"
              , data: {
                  dependencies: []
                , repo: "some/path/"
                , version: "v1.0.0" } },
              { name: "cep"
              , data: {
                  dependencies: []
                , repo: "some/path/"
                , version: "v1.0.0" } },
              { name: "dof"
              , data: {
                  dependencies: []
                , repo: "some/path/"
                , version: "v1.0.0" } },
              { name: "erg"
              , data: {
                  dependencies: []
                , repo: "some/path/"
                , version: "v1.0.0" } },
              { name: "fid"
              , data: {
                  dependencies: []
                , repo: "some/path/"
                , version: "v1.0.0" } },
              { name: "gid"
              , data: {
                  dependencies: []
                , repo: "some/path/"
                , version: "v1.0.0" } },
              { name: "hoy"
              , data: {
                  dependencies: []
                , repo: "some/path/"
                , version: "v1.0.0" } }
            ]


Export `debounce` and `debounceWith` (#29)

Yep, more negligence on my part. This commit adds `debounce` and
`debounceWith` to the exports within the `FRP.Event.Time` module.

module FRP.Event.Time
  ( interval
  , animationFrame
  , withTime
  , debounce
  , debounceWith
  ) where

import Data.Maybe (Maybe, maybe)
import Data.Unit (Unit)
import FRP.Event (Event)
import FRP.Event.Class (fix, gateBy)
import Prelude ((+), (<), map)

-- | Create an event which fires every specified number of milliseconds.
foreign import interval :: Int -> Event Int

-- | Create an event which fires every frame (using `requestAnimationFrame`).
foreign import animationFrame :: Event Unit

-- | Create an event which reports the current time in milliseconds since the epoch.
foreign import withTime :: forall a. Event a -> Event { value :: a, time :: Number }

-- | On each event, ignore subsequent events for a given number of milliseconds.
debounce :: forall a. Number -> Event a -> Event a
debounce period = debounceWith (map { period, value: _ })

-- | Provided an input event and transformation, block the input event for the
-- | duration of the specified period on each output.
debounceWith
  :: forall a b.
     (Event a -> Event { period :: Number, value :: b })
  -> Event a
  -> Event b
debounceWith process event
  = fix \allowed ->
      let
        processed :: Event { period :: Number, value :: b }
        processed = process allowed

        expiries :: Event Number
        expiries =
          map (\{ time, value } -> time + value)
              (withTime (map _.period processed))

        comparison :: forall r. Maybe Number -> { time :: Number | r } -> Boolean
        comparison a b = maybe true (_ < b.time) a

        unblocked :: Event { time :: Number, value :: a }
        unblocked = gateBy comparison expiries stamped
      in
        { input:  map _.value unblocked
        , output: map _.value processed
        }
  where
    stamped :: Event { time :: Number, value :: a }
    stamped = withTime event

module FRP.Event.Time
  ( interval
  , animationFrame
  , withTime
  ) where

import Data.Maybe (Maybe, maybe)
import Data.Unit (Unit)
import FRP.Event (Event)
import FRP.Event.Class (fix, gateBy)
import Prelude ((+), (<), map)

-- | Create an event which fires every specified number of milliseconds.
foreign import interval :: Int -> Event Int

-- | Create an event which fires every frame (using `requestAnimationFrame`).
foreign import animationFrame :: Event Unit

-- | Create an event which reports the current time in milliseconds since the epoch.
foreign import withTime :: forall a. Event a -> Event { value :: a, time :: Number }

-- | On each event, ignore subsequent events for a given number of milliseconds.
debounce :: forall a. Number -> Event a -> Event a
debounce period = debounceWith (map { period, value: _ })

-- | Provided an input event and transformation, block the input event for the
-- | duration of the specified period on each output.
debounceWith
  :: forall a b.
     (Event a -> Event { period :: Number, value :: b })
  -> Event a
  -> Event b
debounceWith process event
  = fix \allowed ->
      let
        processed :: Event { period :: Number, value :: b }
        processed = process allowed

        expiries :: Event Number
        expiries =
          map (\{ time, value } -> time + value)
              (withTime (map _.period processed))

        comparison :: forall r. Maybe Number -> { time :: Number | r } -> Boolean
        comparison a b = maybe true (_ < b.time) a

        unblocked :: Event { time :: Number, value :: a }
        unblocked = gateBy comparison expiries stamped
      in
        { input:  map _.value unblocked
        , output: map _.value processed
        }
  where
    stamped :: Event { time :: Number, value :: a }
    stamped = withTime event