packages feed

asciichart (empty) → 1.0.0

raw patch · 7 files changed

+356/−0 lines, 7 filesdep +arraydep +asciichartdep +basesetup-changed

Dependencies added: array, asciichart, base, hspec, random, raw-strings-qq, silently

Files

+ ChangeLog.md view
@@ -0,0 +1,5 @@+# Revision history for asciichart++## 1.0.0 -- YYYY-mm-dd++* First version. Released on an unsuspecting world.
+ LICENSE view
@@ -0,0 +1,20 @@+Copyright (c) 2018 Fabian Beuke++Permission is hereby granted, free of charge, to any person obtaining+a copy of this software and associated documentation files (the+"Software"), to deal in the Software without restriction, including+without limitation the rights to use, copy, modify, merge, publish,+distribute, sublicense, and/or sell copies of the Software, and to+permit persons to whom the Software is furnished to do so, subject to+the following conditions:++The above copyright notice and this permission notice shall be included+in all copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ README.md view
@@ -0,0 +1,27 @@+# asciichart+[![License: MIT](https://img.shields.io/badge/License-MIT-brightgreen.svg)](https://opensource.org/licenses/MIT)+[![Build Status](https://travis-ci.org/madnight/asciichart.svg?branch=master)](https://travis-ci.org/madnight/asciichart)++ASCII line charts in terminal ╭┈╯. Console line charts in pure Haskell.  +This is a Haskell port of the Javascript library [kroitor/asciichart](https://github.com/kroitor/asciichart). Free for any usage (MIT License).++![](example.png)++## Usage+```bash+cabal install asciichart+```++```haskell+import Data.Text.Chart (plot)++main :: IO ()+main = plot [1..20]+```++For more examples e.g. sinus wave see [examples folder](https://github.com/madnight/asciichart/tree/master/examples).++## References++Full credits to [kroitor](https://github.com/kroitor/) the inventor of asciichart for the terminal.  +This is only a simple port for the Haskell community.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ asciichart.cabal view
@@ -0,0 +1,54 @@+name:                asciichart+version:             1.0.0+synopsis:            Line charts in terminal+license:             MIT+license-file:        LICENSE+author:              Fabian Beuke+maintainer:          mail@beuke.org+homepage:            https://github.com/madnight/asciichart+bug-reports:         https://github.com/madnight/asciichart/issues+copyright:           (c) 2018-Present Fabian Beuke+category:            Text+build-type:          Simple+extra-source-files:  ChangeLog.md, README.md+cabal-version:       >=1.10+tested-with:         GHC == 7.8+                   , GHC == 7.10+                   , GHC == 8.0.1+                   , GHC == 8.2.2+                   , GHC == 8.4.1+description:+  Line charts in terminal ╭┈╯. Haskell port of kroitor/asciichart.+  .+  @+    import Data.Text.Chart (plot)+    .+    main :: IO ()+    main = plot [1..20]+  @+  .++source-repository head+  type: git+  location: git://github.com/madnight/asciichart.git++library+  exposed-modules:     Data.Text.Chart+  build-depends:       base >=3.0 && <5+                     , array >=0.5 && <0.6+  hs-source-dirs:      src+  ghc-options:         -Wall+  default-language:    Haskell2010++test-suite test+    type:              exitcode-stdio-1.0+    main-is:           Test.hs+    hs-source-dirs:    test+    ghc-options:       -Wall+    default-language:  Haskell2010+    build-depends:     base+                     , asciichart+                     , random+                     , silently+                     , raw-strings-qq+                     , hspec == 2.*
+ src/Data/Text/Chart.hs view
@@ -0,0 +1,100 @@+{-# LANGUAGE CPP #-}++module Data.Text.Chart+    ( plot+    , plotWith+    , options+    , height+    ) where++import Control.Monad (forM_)+import Data.Array.IO (newArray, IOArray, getElems, writeArray)+import Data.Char     (isSpace)+import Data.List     (unfoldr, dropWhileEnd)+import Text.Printf   (printf)++#if !MIN_VERSION_base(4,8,0)+import Control.Applicative ((<$>))+#endif++data Options =+  Options { height :: Int }++-- default options+options :: Options+options =+  Options { height = 14 }++newArray2D :: Integer -> Integer -> IO (IOArray (Integer, Integer) String)+newArray2D dimX dimY = newArray ((0,0), (dimX, dimY)) " "++splitEvery :: Int -> [a] -> [[a]]+splitEvery n = takeWhile (not . null) . unfoldr (Just . splitAt n)++plot :: [Integer] -> IO ()+plot = plotWith options++pad :: Integral a => [a] -> Int+pad series =+  let floats = fromIntegral <$> series+      toStr :: [Float] -> [String]+      toStr = fmap (printf "%0.2f")+  in  maximum $ length <$> toStr floats++plotWith :: Options -> [Integer] -> IO ()+plotWith options series = do++    -- variables and functions+    let min' = minimum series+    let max' = maximum series+    let range = abs $ max' - min'+    let offset' = 3+    let height' = height options+    let ratio = fromIntegral height' / fromIntegral range :: Float+    let min2 = fromIntegral min' * ratio+    let max2 = fromIntegral max' * ratio+    let rows = round $ abs $ max2 - min2+    let width = length series + 3+    let pad' = pad series++    -- array creation+    arr <- newArray2D rows (toInteger width)+    let write arr [x] [y] = writeArray arr (x, y)+    let result = write arr++    -- axis and labels+    forM_ [min2..max2] $ \y -> do+            let label = fromInteger max' - (y - min2)+                      * fromInteger range / fromIntegral rows+            result [round(y - min2)] [maximum [offset' - 5, 0]] $+                   printf ("%"++ show pad' ++".2f") label+            result [round(y - min2)] [offset' - 1] $+                   if y == 0 then "┼" else "┤"++    -- initial value+    let first = fromInteger (head series) * ratio - min2+    result [round(fromInteger rows - first)] [offset' - 1] "┼"++    -- plot the line+    forM_ [0..(length series - 2)] $ \x' -> do+        let x = toInteger x'+        let offset'' = x + offset'+        let y0 = round (fromInteger (series !! (x' + 0)) * ratio) - round min2+        let y1 = round (fromInteger (series !! (x' + 1)) * ratio) - round min2+        if y0 == y1 then+            result [rows - y0] [offset''] "─"+        else do+            result [rows - y1] [offset''] $+                   if y0 > y1 then "╰" else "╭"+            result [rows - y0] [offset''] $+                   if y0 > y1 then "╮" else "╯"+            let start = minimum [y0, y1] + 1+            let end = maximum [y0, y1]++            forM_ [start..(end - 1)] $ \y ->+                result [rows - y] [offset''] "│"++    -- print the results+    elements <- getElems arr+    let result = splitEvery (width + 1) elements+    forM_ result $ putStrLn . dropWhileEnd isSpace . concat
+ test/Test.hs view
@@ -0,0 +1,148 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE QuasiQuotes #-}++import Control.Monad       (forM_, replicateM)+import Data.Text.Chart     (plot, plotWith, options, height)+import System.IO.Silently+import System.Random       (randomRIO)+import Test.Hspec+import Text.RawString.QQ++#if !MIN_VERSION_base(4,8,0)+import Control.Applicative ((<$>))+import Data.Monoid         (mempty)+#endif++randomList :: Int -> IO [Integer]+randomList = ($ randomRIO (-99999, 99999)) . replicateM++test0 :: IO ()+test0 = plotWith options {height = 5} ([1..6] ++ [6,5..1])++test0Output :: String+test0Output = tail [r|+6.00 ┤    ╭─╮+5.00 ┤   ╭╯ ╰╮+4.00 ┤  ╭╯   ╰╮+3.00 ┤ ╭╯     ╰╮+2.00 ┤╭╯       ╰╮+1.00 ┼╯         ╰+|]++test1 :: IO ()+test1 = plotWith options {height = 8} wave+  where wave = round . (5 *) . sin . (/ 120) . (pi *) . (4 *) <$> [0..60]++test1Output :: String+test1Output = tail [r|+ 5.00 ┤          ╭────────╮+ 3.75 ┤       ╭──╯        ╰──╮+ 2.50 ┤  ╭────╯              ╰────╮+ 1.25 ┤╭─╯                        ╰─╮+ 0.00 ┼╯                            ╰╮                            ╭+-1.25 ┤                              ╰─╮                        ╭─╯+-2.50 ┤                                ╰────╮              ╭────╯+-3.75 ┤                                     ╰──╮        ╭──╯+-5.00 ┤                                        ╰────────╯+|]++test2 :: IO ()+test2 = plotWith options {height = 2} wave+  where wave = round . (5 *) . cos . (/ 30) . (pi *) . (4 *) <$> [0..30]++test2Output :: String+test2Output = tail [r|+ 5.00 ┼──╮         ╭────╮         ╭──+ 0.00 ┼  ╰──╮    ╭─╯    ╰──╮   ╭──╯+-5.00 ┤     ╰────╯         ╰───╯+|]++test3 :: IO ()+test3 = plot $ take 51 $ cycle [-8975789655001, 6755678990773]++test3Output :: String+test3Output = tail [r|+ 6755679000000.00 ┤╭╮╭╮╭╮╭╮╭╮╭╮╭╮╭╮╭╮╭╮╭╮╭╮╭╮╭╮╭╮╭╮╭╮╭╮╭╮╭╮╭╮╭╮╭╮╭╮╭╮+ 5632002400000.00 ┤││││││││││││││││││││││││││││││││││││││││││││││││││+ 4508326300000.00 ┤││││││││││││││││││││││││││││││││││││││││││││││││││+ 3384649700000.00 ┤││││││││││││││││││││││││││││││││││││││││││││││││││+ 2260973600000.00 ┤││││││││││││││││││││││││││││││││││││││││││││││││││+ 1137297000000.00 ┤││││││││││││││││││││││││││││││││││││││││││││││││││+   13620478000.00 ┤││││││││││││││││││││││││││││││││││││││││││││││││││+-1110055600000.00 ┤││││││││││││││││││││││││││││││││││││││││││││││││││+-2233731600000.00 ┤││││││││││││││││││││││││││││││││││││││││││││││││││+-3357408800000.00 ┤││││││││││││││││││││││││││││││││││││││││││││││││││+-4481085000000.00 ┤││││││││││││││││││││││││││││││││││││││││││││││││││+-5604761000000.00 ┤││││││││││││││││││││││││││││││││││││││││││││││││││+-6728438000000.00 ┤││││││││││││││││││││││││││││││││││││││││││││││││││+-7852114000000.00 ┤││││││││││││││││││││││││││││││││││││││││││││││││││+-8975790000000.00 ┼╯╰╯╰╯╰╯╰╯╰╯╰╯╰╯╰╯╰╯╰╯╰╯╰╯╰╯╰╯╰╯╰╯╰╯╰╯╰╯╰╯╰╯╰╯╰╯╰╯╰+|]++btcChart :: [Float]+btcChart =+    [2591.22,2720.08,2723.58,3255,3445.28,4382.74,4159.46,4137.67           ] +++    [4387.46,4630.73,4631.69,4638.1,4188.84,3686.9,3897,3777.29,4208.56     ] +++    [4394.64,4322.75,4772.97,5640.13,5595.23,6013.23,5733.9,6140.53,7030    ] +++    [6958.21,6570.31,6598.77,7776.94,8230.69,9326.59,9916.54,11616.85       ] +++    [16057.14,17178.1,19343.04,16454.72,13975.44,14428.76,13412.44,16937.17 ] +++    [14439.47,14188.78,11141.25,11522.86,11137.24,11158.39,8827.63,7700.39  ] +++    [8556.61,9477.84,10396.63,9830.43,10313.08,11019.52,10709.53,8787.16    ] +++    [8196.9,8196.02,8712.89,8138.34,6844.32,7417.89,6896.28,6939.55,8357.04 ] +++    [8273.74,8938.3,8978.33                                                 ]++test4 :: IO ()+test4 = plot $ round <$> btcChart++test4Output :: String+test4Output = tail [r|+19343.00 ┤                                   ╭╮+18146.43 ┤                                   ││+16949.86 ┤                                  ╭╯╰╮  ╭╮+15753.28 ┤                                 ╭╯  │  ││+14556.72 ┤                                 │   ╰─╮│╰─╮+13360.14 ┤                                 │     ╰╯  │+12163.57 ┤                                ╭╯         │╭╮+10967.00 ┤                                │          ╰╯╰─╮   ╭╮╭──╮+ 9770.43 ┤                              ╭─╯              │  ╭╯╰╯  │           ╭+ 8573.86 ┤                             ╭╯                ╰╮╭╯     ╰────╮   ╭──╯+ 7377.29 ┤                        ╭─╮╭─╯                  ╰╯           ╰───╯+ 6180.71 ┤                   ╭────╯ ╰╯+ 4984.14 ┤    ╭╮ ╭────╮  ╭───╯+ 3787.57 ┤  ╭─╯╰─╯    ╰──╯+ 2591.00 ┼──╯+|]++check :: IO a -> String -> IO ()+check test out = do+    (output, _) <- capture test+    output `shouldBe` out++main :: IO ()+main = hspec $ do++    describe "Test 0" $+      it "{height = 5} ([1..6] ++ [6,5..1])" $+        check test0 test0Output++    describe "Test 1" $+      it "{height = 8} sin wave" $+        check test1 test1Output++    describe "Test 2" $+      it "{height = 2} mini cos wave" $+        check test2 test2Output++    describe "Test 3" $+      it "very large numbers" $+        check test3 test3Output++    describe "Test 4" $+      it "bitcoin chart" $+        check test4 test4Output++    describe "Test 5" $+      it "500 random charts" $+      forM_ [1..500] . const $ do+        randLen <- randomRIO (2, 140)+        plot =<< randomList randLen