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 +5/−0
- LICENSE +20/−0
- README.md +27/−0
- Setup.hs +2/−0
- asciichart.cabal +54/−0
- src/Data/Text/Chart.hs +100/−0
- test/Test.hs +148/−0
+ 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+[](https://opensource.org/licenses/MIT)+[](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).++++## 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