content: {-|
  
  Simple histograms
  
  -}
  
  module Graphics.Plotly.Histogram where
  
  import Graphics.Plotly.Base hiding (sort)
  import Data.List (sort, group)
  import Lens.Micro
  import Data.Aeson (toJSON)
  import Data.Text (Text)
  
  -- | build a histogram with a given binsize
  histogram :: Int -- ^ number of bins
            -> [Double] -- ^ the individual observations
            -> Trace
  histogram nbins pts =
    let (lo, hi) = (minimum pts, maximum pts)
        binSize = (hi - lo) / realToFrac nbins
        binToX :: Int -> Double
        binToX binN = realToFrac binN * binSize + lo
        binMap :: [(Int, Int)]
        binMap = getBinMap lo binSize pts
    in bars & x ?~ map (toJSON . binToX . fst) binMap & y ?~ map (toJSON .  snd) binMap
  
  
  histMany :: Int -> [(Text, [Double])] -> [Trace]
  histMany nbins hdata =
    let allPts = concat $ map snd hdata
        (lo, hi) = (minimum allPts, maximum allPts)
        binSize = (hi - lo) / realToFrac nbins
        binToX :: Int -> Double
        binToX binN = realToFrac binN * binSize + lo
        getTrace (nm,pts) =
          let binMap = getBinMap lo binSize pts
          in bars & x ?~ map (toJSON . binToX . fst) binMap
                  & y ?~ map (toJSON .  snd) binMap
                  & name ?~ nm
    in map getTrace hdata
  
  goFill :: [(Int,Int)] -> [(Int,Int)]
  goFill (car@(bin1,_):cdr@((bin2,_):_))
     | bin2 == bin1 + 1 =  car : goFill cdr
     | otherwise = car : goFill ((bin1+1,0):cdr)
  goFill l = l
  
  getBinMap :: Double -> Double -> [Double] -> [(Int, Int)]
  getBinMap lo binSize pts =
    let binf :: Double -> Int
        binf xv = floor $ (xv - lo) / binSize
        bins = group $ sort $ map binf pts
        binMap :: [(Int, Int)]
        binMap = goFill $ map (\is -> (head is, length is)) bins
    in binMap
  