Permalink
Find file
Fetching contributors…
Cannot retrieve contributors at this time
373 lines (317 sloc) 16.5 KB
{-# LANGUAGE ParallelListComp, ScopedTypeVariables, BangPatterns, Rank2Types, TupleSections #-}
module Tools.TimePlot.Plots (
initGen
) where
import qualified Control.Monad.Trans.State.Strict as St
import Control.Arrow
import Control.Applicative
import Data.List (foldl', sort)
import Data.Maybe
import qualified Data.Map as M
import qualified Data.Set as Set
import qualified Data.ByteString.Char8 as S
import Data.Time
import Graphics.Rendering.Chart
import Graphics.Rendering.Chart.Event
import Data.Colour
import Data.Colour.Names
import Tools.TimePlot.Types
import qualified Tools.TimePlot.Incremental as I
type PlotGen = String -> LocalTime -> LocalTime -> I.StreamSummary (LocalTime, InEvent) PlotData
initGen :: ChartKind LocalTime -> PlotGen
initGen (KindACount bs) = genActivity (\sns n -> n) bs
initGen (KindCount bs) = genActivity (\sns n -> n*toSeconds bs (undefined::LocalTime)) bs
initGen (KindAPercent bs b) = genActivity (\sns n -> 100*n/b) bs
initGen (KindAFreq bs) = genActivity (\sns n -> if n == 0 then 0 else (n / sum (M.elems sns))) bs
initGen (KindFreq bs k) = genAtoms atoms2freqs bs k
where atoms2freqs as m = let s = sum [c | (a,c) <- M.toList m]
in if s==0 then [0] else 0:[fromIntegral (M.findWithDefault 0 a m)/fromIntegral s | a <- as]
initGen (KindHistogram bs k) = genAtoms atoms2hist bs k
where atoms2hist as m = 0:[fromIntegral (M.findWithDefault 0 a m) | a <- as]
initGen KindEvent = genEvent
initGen (KindQuantile bs vs) = genQuantile bs vs
initGen (KindBinFreq bs vs) = genBinFreqs bs vs
initGen (KindBinHist bs vs) = genBinHist bs vs
initGen KindLines = genLines
initGen (KindDots alpha) = genDots alpha
initGen (KindSum bs ss) = genSum bs ss
initGen (KindCumSum bs ss) = genCumSum bs ss
initGen (KindDuration sk dropSubtrack) = genDuration sk dropSubtrack
initGen (KindWithin _ _) = \name -> error $
"KindWithin should not be plotted (this is a bug): track " ++ show name
initGen KindNone = \name -> error $
"KindNone should not be plotted (this is a bug): track " ++ show name
initGen KindUnspecified = \name -> error $
"Kind not specified for track " ++ show name ++ " (have you misspelled -dk or any of -k arguments?)"
-- Auxiliary functions for two common plot varieties
plotTrackBars :: [(LocalTime,[Double])] -> [String] -> String -> [Colour Double] -> PlotData
plotTrackBars vals titles name colors = PlotBarsData {
plotName = name,
barsStyle = BarsStacked,
barsValues = vals,
barsStyles = [ (solidFillStyle c, Nothing)
| c <- transparent:map opaque colors
| _ <- "":titles],
barsTitles = "":titles
}
plotLines :: String -> [(S.ByteString, [(LocalTime,Double)])] -> PlotData
plotLines name vss = PlotLinesData {
plotName = name,
linesData = [vs | (_, vs) <- vss],
linesStyles = [solidLine 1 color | _ <- vss | color <- map opaque colors],
linesTitles = [S.unpack subtrack | (subtrack, _) <- vss]
}
-------------------------------------------------------------
-- Plot generators
-------------------------------------------------------------
-- Wrappers for I.filterMap
values (t,InValue s v) = Just (t,s,v)
values _ = Nothing
valuesDropTrack (t, InValue s v) = Just (t,v)
valuesDropTrack _ = Nothing
atomsDropTrack (t, InAtom s a) = Just (t,a)
atomsDropTrack _ = Nothing
edges (t,InEdge s e) = Just (t,s,e)
edges _ = Nothing
------------------- Lines ----------------------
genLines :: PlotGen
genLines name t0 t1 = I.filterMap values $ (data2plot . groupByTrack) <$> I.collect
where
data2plot vss = PlotLinesData {
plotName = name,
linesData = [vs | (_,vs) <- vss],
linesStyles = [solidLine 1 color | _ <- vss | color <- map opaque colors],
linesTitles = [S.unpack subtrack | (subtrack, _) <- vss]
}
------------------- Dots ----------------------
genDots :: Double -> PlotGen
genDots alpha name t0 t1 = I.filterMap values $ (data2plot . groupByTrack) <$> I.collect
where
data2plot vss = PlotDotsData {
plotName = name,
dotsData = [vs | (_,vs) <- vss],
dotsTitles = [S.unpack subtrack | (subtrack, _) <- vss],
dotsColors = if alpha == 1 then map opaque colors else map (`withOpacity` alpha) colors
}
------------------- Binned graphs ----------------------
summaryByFixedTimeBins t0 binSize = I.byTimeBins (iterate (add binSize) t0)
-- Arguments of f will be: value bin boundaries, values in the current time bin
genByBins :: ([Double] -> [Double] -> [Double]) -> NominalDiffTime -> [Double] -> PlotGen
genByBins f timeBinSize valueBinBounds name t0 t1 = I.filterMap valuesDropTrack $
summaryByFixedTimeBins t0 timeBinSize $
I.mapInput (\(t,xs) -> (t, 0:f valueBinBounds xs)) $
(\tfs -> plotTrackBars tfs binTitles name colors) <$>
I.collect
where
binTitles = [low]++[showDt v1++".."++showDt v2
| v1 <- valueBinBounds
| v2 <- tail valueBinBounds]++
[high]
where
low = "<"++showDt (head valueBinBounds)
high = ">"++showDt (last valueBinBounds)
-- 0.1s but 90ms, etc.
showDt t | t < 0.0000001 = show (t*1000000000) ++ "ns"
| t < 0.0001 = show (t*1000000) ++ "us"
| t < 0.1 = show (t*1000) ++ "ms"
| True = show t ++ "s"
genBinHist :: NominalDiffTime -> [Double] -> PlotGen
genBinFreqs :: NominalDiffTime -> [Double] -> PlotGen
(genBinHist,genBinFreqs) = (genByBins values2binHist, genByBins values2binFreqs)
where
values2binHist bins = values2binHist' bins . sort
values2binHist' [] xs = [fromIntegral (length xs)]
values2binHist' (a:as) xs = fromIntegral (length xs0) : values2binHist' as xs'
where (xs0,xs') = span (<a) xs
values2binFreqs bins xs = map toFreq $ values2binHist bins xs
where
n = length xs
toFreq k = if n==0 then 0 else (k/fromIntegral n)
genQuantile :: NominalDiffTime -> [Double] -> PlotGen
genQuantile binSize qs name t0 t1 = I.filterMap valuesDropTrack $
summaryByFixedTimeBins t0 binSize $
I.mapInput (second (diffs . getQuantiles qs)) $
fmap (\tqs -> plotTrackBars tqs quantileTitles name colors) $
I.collect
where
quantileTitles = [show p1++".."++show p2++"%" | p1 <- percents | p2 <- tail percents]
percents = map (floor . (*100.0)) $ [0.0] ++ qs ++ [1.0]
diffs xs = zipWith (-) xs (0:xs)
getQuantiles :: (Ord a) => [Double] -> [a] -> [a]
getQuantiles qs = quantiles' . sort
where
qs' = sort qs
quantiles' [] = []
quantiles' xs = index (0:ns++[n-1]) 0 xs
where
n = length xs
ns = map (floor . (*(fromIntegral n-1))) qs'
index _ _ [] = []
index [] _ _ = []
index [i] j (x:xs)
| i<j = []
| i==j = [x]
| True = index [i] (j+1) xs
index (i:i':is) j (x:xs)
| i<j = index (i':is) j (x:xs)
| i>j = index (i:i':is) (j+1) xs
| i==i' = x:index (i':is) j (x:xs)
| True = x:index (i':is) (j+1) xs
genAtoms :: ([S.ByteString] -> M.Map S.ByteString Int -> [Double]) ->
NominalDiffTime -> PlotBarsStyle -> PlotGen
genAtoms f binSize k name t0 t1 = I.filterMap atomsDropTrack (h <$> unique (\(t,atom) -> atom) <*> fInBins)
where
fInBins :: I.StreamSummary (LocalTime, S.ByteString) [(LocalTime, M.Map S.ByteString Int)]
fInBins = summaryByFixedTimeBins t0 binSize $ I.mapInput (second counts) I.collect
counts = foldl' insert M.empty
where
insert m a = case M.lookup a m of
Nothing -> M.insert a 1 m
Just !n -> M.insert a (n+1) m
h :: [S.ByteString] -> [(LocalTime, M.Map S.ByteString Int)] -> PlotData
h as tfs = (plotTrackBars (map (second (f as)) tfs) (map show as) name colors) { barsStyle = k }
unique :: (Ord a) => (x -> a) -> I.StreamSummary x [a]
unique f = I.stateful M.empty (\a -> M.insert (f a) ()) M.keys
uniqueSubtracks :: I.StreamSummary (LocalTime,S.ByteString,a) [S.ByteString]
uniqueSubtracks = unique (\(t,s,a) -> s)
genSum :: NominalDiffTime -> SumSubtrackStyle -> PlotGen
genSum binSize ss name t0 t1 = I.filterMap values (h <$> uniqueSubtracks <*> sumsInBins t0 binSize)
where
h :: [S.ByteString] -> [(LocalTime, M.Map S.ByteString Double)] -> PlotData
h tracks binSums = plotLines name rows
where
rowsT' = case ss of
SumOverlayed -> map (second M.toList) binSums
SumStacked -> map (second stack) binSums
stack :: M.Map S.ByteString Double -> [(S.ByteString, Double)]
stack ss = zip tracks (scanl1 (+) (map (\x -> M.findWithDefault 0 x ss) tracks))
rows :: [(S.ByteString, [(LocalTime, Double)])]
rows = M.toList $ fmap sort $ M.fromListWith (++) $
[(track, [(t,sum)]) | (t, m) <- rowsT', (track, sum) <- m]
sumsInBins :: LocalTime -> NominalDiffTime -> I.StreamSummary (LocalTime,S.ByteString,Double) [(LocalTime, M.Map S.ByteString Double)]
sumsInBins t0 bs = I.mapInput (\(t,s,v) -> (t,(s,v))) $
summaryByFixedTimeBins t0 bs $
I.mapInput (second (fromListWith' (+))) $
I.collect
genCumSum :: NominalDiffTime -> SumSubtrackStyle -> PlotGen
genCumSum bs ss name t0 t1 = I.filterMap values (accumulate <$> uniqueSubtracks <*> sumsInBins t0 bs)
where
accumulate :: [S.ByteString] -> [(LocalTime, M.Map S.ByteString Double)] -> PlotData
accumulate tracks tss = plotLines name [(track, [(t, ss M.! track) | (t,ss) <- cumsums]) | track <- tracks]
where
cumsums = scanl' f (t0, M.fromList $ zip tracks (repeat 0)) (map normalize tss)
normalize (t,binSums) = (t, M.fromList [ (track, M.findWithDefault 0 track binSums) | track <- tracks ])
f (_,bases) (t,binSums) = (t,) $ M.fromList $ zip tracks $ zipWith (+) trackBases $ case ss of
SumOverlayed -> trackSums
SumStacked -> trackAccSums
where
trackSums = [ binSums M.! track | track <- tracks ]
trackBases = [ bases M.! track | track <- tracks ]
trackAccSums = scanl1' (+) trackSums
scanl1' f (x:xs) = scanl' f x xs
scanl' f !x0 [] = [x0]
scanl' f !x0 (x:xs) = x0:scanl' f (f x0 x) xs
genActivity :: (M.Map S.ByteString Double -> Double -> Double) -> NominalDiffTime -> PlotGen
genActivity f bs name t0 t1 = I.filterMap edges (h <$> uniqueSubtracks <*> binAreas)
where
binAreas :: I.StreamSummary (LocalTime,S.ByteString,Edge) [(LocalTime, M.Map S.ByteString Double)]
binAreas = fmap (map (\((t1,t2),m) -> (t1,m))) $ edges2binsSummary bs t0 t1
h tracks binAreas = (plotTrackBars barsData (map S.unpack tracks) name colors) { barsStyle = BarsStacked }
where
barsData = [(t, 0:map (f m . flip (M.findWithDefault 0) m) tracks) | (t,m) <- binAreas]
edges2binsSummary :: (Ord t,HasDelta t,Show t) =>
Delta t -> t -> t ->
I.StreamSummary (t,S.ByteString,Edge) [((t,t), M.Map S.ByteString Double)]
edges2binsSummary binSize tMin tMax = I.stateful (M.empty, iterate (add binSize) tMin, []) step flush
where
-- State: (m, ts, r) where:
-- * m = subtrack => state of current bin:
-- (area, starting time, level = rise-fall, num pulse events)
-- * ts = infinite list of time bin boundaries
-- * r = reversed list of results per bins
modState s t (!m, ts,r) f = (m', ts, r)
where
m' = M.insertWith' (\new !old -> f old) s (f (0,t,0,0)) m
flushBin st@(m,t1:t2:ts,!r) = (m', t2:ts, r')
where
states = M.toList m
binSizeSec = deltaToSeconds t2 t1
binValue (area,start,nopen,npulse) =
(fromIntegral npulse + area + deltaToSeconds t2 start*nopen) / binSizeSec
!r' = ((t1,t2), M.fromList [(s, binValue bin) | (s, bin) <- states]) : r
!m' = fmap (\(_,_,nopen,_) -> (0,t2,nopen,0)) m
step ev@(t, s, e) st@(m, t1:t2:ts, r)
| t < t1 = error $ "Times are not in ascending order, first violating is " ++ show t
| t >= t2 = step ev (flushBin st)
| True = step'' ev st
step'' ev@(t,s,e) st@(m, t1:t2:ts, r) = if (t < t1 || t >= t2) then error "Outside bin" else step' ev st
step' (t, s, SetTo _) st = st
step' (t, s, Pulse _) st = modState s t st $
\(!area, !start, !nopen, !npulse) -> (area, t, nopen, npulse+1)
step' (t, s, Rise) st = modState s t st $
\(!area, !start, !nopen, !npulse) -> (area+deltaToSeconds t start*nopen, t, nopen+1, npulse)
step' (t, s, Fall) st = modState s t st $
\(!area, !start, !nopen, !npulse) -> (area+deltaToSeconds t start*nopen, t, nopen-1, npulse)
flush st@(m, t1:t2:ts, r)
| t2 <= tMax = flush (flushBin st)
| True = reverse r
type StreamTransformer a b = forall r . I.StreamSummary b r -> I.StreamSummary a r
edges2eventsSummary :: forall t . (Ord t) =>
t -> t -> StreamTransformer (t,S.ByteString,Edge) (S.ByteString, Event t Status)
edges2eventsSummary t0 t1 s = I.stateful (M.empty,s) step flush
where
-- State: (m, sum) where
-- * m = subtrack => (event start, level = rise-fall, status)
-- * sum = summary of accumulated events so far
tellSummary e (ts,!sum) = (ts,I.insert sum e)
getTrack s (!ts,sum) = M.findWithDefault (t0, 0, emptyStatus) s ts
putTrack s t (!ts,sum) = (M.insert s t ts, sum)
killTrack s (!ts,sum) = (M.delete s ts, sum)
trackCase s whenZero withNonzero st
| numActive == 0 = whenZero
| True = withNonzero t0 numActive status
where (t0, numActive, status) = getTrack s st
emptyStatus = Status "" ""
step (t,s,Pulse st) state = tellSummary (s, PulseEvent t st) state
step (t,s,SetTo st) state = trackCase s
(putTrack s (t, 1, st) state)
(\t0 !n st0 -> putTrack s (t,n,st) $ tellSummary (s, LongEvent (t0,True) (t,True) st0) state)
state
step (t,s,Rise) state = trackCase s
(putTrack s (t, 1, emptyStatus) state)
(\t0 !n st -> putTrack s (t, n+1, st) state)
state
step (t,s,Fall) state
| numActive == 1 = killTrack s $ tellSummary (s, LongEvent (t0,True) (t,True) st) state
| True = putTrack s (t0, max 0 (numActive-1), st) state
where
(t0, numActive, st) = getTrack s state
flush (ts,sum) = I.finalize $ foldl' addEvent sum $ M.toList ts
where
addEvent sum (s,(t0,_,st)) = I.insert sum (s, LongEvent (t0,True) (t1,False) st)
edges2durationsSummary :: forall t . (Ord t, HasDelta t) =>
t -> t -> Maybe String -> StreamTransformer (t,S.ByteString,Edge) (t,InEvent)
edges2durationsSummary t0 t1 commonTrack = edges2eventsSummary t0 t1 . I.filterMap genDurations
where
genDurations (track, e) = case e of
LongEvent (t1,True) (t2,True) _ -> Just (t2, InValue (case commonTrack of
Nothing -> track
_ -> commonTrackBS)
(deltaToSeconds t2 t1))
_ -> Nothing
commonTrackBS = S.pack (fromJust commonTrack)
genEvent :: PlotGen
genEvent name t0 t1 = I.filterMap edges $
fmap (\evs -> PlotEventData { plotName = name, eventData = map snd evs }) $
edges2eventsSummary t0 t1 I.collect
-- TODO Multiple tracks
genDuration :: ChartKind LocalTime -> Bool -> PlotGen
genDuration sk dropSubtrack name t0 t1 = I.filterMap edges $
edges2durationsSummary t0 t1 (if dropSubtrack then Just name else Nothing) (initGen sk name t0 t1)
fromListWith' f kvs = foldl' insert M.empty kvs
where
insert m (k,v) = case M.lookup k m of
Nothing -> M.insert k v m
Just !v' -> M.insert k (f v' v) m
colors = cycle [green,blue,red,brown,yellow,orange,grey,purple,violet,lightblue]
groupByTrack xs = M.toList $ sort `fmap` M.fromListWith (++) [(s, [(t,v)]) | (t,s,v) <- xs]