module Graphics.Matplotlib where
import System.IO.Temp
import System.Process
import GHC.Generics
import Data.Aeson
import Control.Monad
import System.IO
import qualified Data.ByteString.Lazy as B
import Data.List
import Control.Exception
mapLinear :: (Double -> b) -> Double -> Double -> Double -> [b]
mapLinear f s e n = map (\v -> f $ s + (v * (e s) / n)) [0..n]
data Matplotlib = Matplotlib [MplotCommand]
data MplotCommand
= LoadData B.ByteString
| Exec String
deriving (Show, Eq, Ord)
data Option =
K String String
| B String
toPy (LoadData _) = error "withMplot needed to load data"
toPy (Exec str) = str
withMplot :: Matplotlib -> ([String] -> IO a) -> IO a
withMplot (Matplotlib cs) f = preload cs []
where
preload [] cmds = f $ map toPy $ reverse cmds
preload ((LoadData obj):cs) cmds =
withSystemTempFile "data.json"
(\dataFile dataHandle -> do
B.hPutStr dataHandle obj
hClose dataHandle
preload cs $ ((map Exec $ pyReadData dataFile) ++ cmds))
preload (c:cs) cmds = preload cs (c:cmds)
python codeStr =
catch (withSystemTempFile "code.py"
(\codeFile codeHandle -> do
forM_ codeStr (hPutStrLn codeHandle)
hClose codeHandle
Right <$> readProcess "/usr/bin/python3" [codeFile] ""))
(\e -> return $ Left $ show (e :: IOException))
pyIncludes = ["import matplotlib"
,"import matplotlib.path as mpath"
,"import matplotlib.patches as mpatches"
,"import matplotlib.pyplot as plot"
,"import matplotlib.mlab as mlab"
,"from matplotlib import cm"
,"from mpl_toolkits.mplot3d import axes3d"
,"import numpy as np"
,"import os"
,"import sys"
,"import json"
,"import random, datetime"
,"from matplotlib.dates import DateFormatter, WeekdayLocator"]
pyReadData filename = ["data = json.loads(open('" ++ filename ++ "').read())"]
pyDetach = ["pid = os.fork()"
,"if(pid != 0):"
," exit(0)"]
pyOnscreen = ["plot.draw()"
,"plot.show()"]
pyFigure output = ["plot.savefig('" ++ output ++ "')"]
onscreen m = withMplot m (\str -> python $ pyIncludes ++ str ++ pyDetach ++ pyOnscreen)
code m = withMplot m (\str -> return $ Right $ unlines $ pyIncludes ++ str ++ pyDetach ++ pyOnscreen)
figure filename m = withMplot m (\str -> python $ pyIncludes ++ str ++ pyFigure filename)
mplot s = Matplotlib [Exec s]
infixl 5 %
(%) :: Matplotlib -> Matplotlib -> Matplotlib
(Matplotlib a) % (Matplotlib b) = Matplotlib (a ++ b)
infixl 6 #
class MplotValue val where
(#) :: String -> val -> String
instance MplotValue String where
s # b = s ++ b
instance MplotValue [String] where
m # [] = m
m # (x:xs) = m # x # "," # xs
instance MplotValue Double where
s # b = s ++ show b
instance MplotValue Int where
s # b = s ++ show b
instance MplotValue (String, String) where
m # (n, v) = m # n # "=" # v
instance (MplotValue x) => MplotValue (x, x) where
m # (n, v) = m # n # "=" # v
instance (MplotValue (x, y)) => MplotValue [(x, y)] where
m # [] = m
m # (x:xs) = m # x # "," # xs
instance MplotValue Option where
m # (B a) = m # a
m # (K a b) = m # a # "=" # b
instance MplotValue [Option] where
m # [] = m
m # (x:xs) = m # x # "," # xs
dataPlot :: (MplotValue val, MplotValue val1) => val1 -> val -> [Option] -> Matplotlib
dataPlot a b opts =
mplot $ "p = plot.plot(data[" # a # "], data[" # b # "]" # options opts # ")"
plot :: (ToJSON t, ToJSON t1) => t1 -> t -> [Option] -> Matplotlib
plot x y opt =
readData (x, y) % dataPlot (0::Int) (1::Int) opt
showPlot :: (ToJSON t, ToJSON t1) => t1 -> t -> [Option] -> IO (Either String String)
showPlot x y opt = onscreen (plot x y opt)
readData :: ToJSON a => a -> Matplotlib
readData d = Matplotlib [LoadData $ encode d]
options :: [Option] -> String
options [] = ""
options xs = "," # xs
def o [] = [o]
def o l@((B _):_) = l
def o (x:xs) = x : def o xs
gridLines = mplot "ax.grid(True)"
dateLine :: (ToJSON t1, ToJSON t2) => t1 -> t2 -> String -> (Int, Int, Int) -> Matplotlib
dateLine x y xunit (yearStart, monthStart, dayStart) =
readData (x, y)
% mplot ("data[0] = [datetime.timedelta("#xunit#"=i) + datetime.datetime("#yearStart#","#monthStart#","#dayStart#") for i in data[0]]")
% dataPlot (0::Int) (1::Int) [B "-"]
% mplot "ax.xaxis.set_major_formatter(DateFormatter('%B'))"
% mplot "ax.xaxis.set_minor_locator(WeekdayLocator(byweekday=6))"
xLabel label = mplot $ "plot.xlabel('" # label # "')"
yLabel label = mplot $ "plot.ylabel('" # label # "')"
zLabel label = mplot $ "plot.zlabel('" # label # "')"
dataHistogram a bins opts = mplot $ "plot.hist(data[" # a # "]," # bins # options opts # ")"
histogram values bins opts = readData [values] % dataHistogram (0::Int) bins opts
showHistogram values bins opts = onscreen $ histogram values bins opts
dataScatter a b opts = dataPlot a b $ def (B "'.'") opts
scatter x y opts = plot x y $ def (B "'.'") opts
showScatter x y opts = showPlot x y $ def (B "'.'") opts
dataLine a b opts = dataPlot a b $ def (B "'-'") opts
line x y opts = plot x y $ def (B "'-'") opts
lineF f l opts = plot l (map f l) $ def (B "'-'") opts
showLine x y opts = showPlot x y $ def (B "'-'") opts
contour xs ys zs =
readData (xs, ys, zs)
% axis3DProjection
% surface (0::Int) (1::Int) (2::Int)
% contourRaw (0::Int) (1::Int) (2::Int) (maximum2 xs) (maximum2 ys) (minimum2 zs)
% axis3DLabels xs ys zs
projections xs ys zs =
readData (xs, ys, zs)
% axis3DProjection
% contourRaw (0::Int) (1::Int) (2::Int) (maximum2 xs) (maximum2 ys) (minimum2 zs)
% axis3DLabels xs ys zs
contourF f xStart xEnd yStart yEnd steps = contour xs ys zs
where xs = mapLinear (\x -> (mapLinear (\y -> x) yStart yEnd steps)) xStart xEnd steps
ys = mapLinear (\x -> (mapLinear (\y -> y) yStart yEnd steps)) xStart xEnd steps
zs = mapLinear (\x -> (mapLinear (\y -> f x y) yStart yEnd steps)) xStart xEnd steps
projectionsF f xStart xEnd yStart yEnd steps = projections xs ys zs
where xs = mapLinear (\x -> (mapLinear (\y -> x) yStart yEnd steps)) xStart xEnd steps
ys = mapLinear (\x -> (mapLinear (\y -> y) yStart yEnd steps)) xStart xEnd steps
zs = mapLinear (\x -> (mapLinear (\y -> f x y) yStart yEnd steps)) xStart xEnd steps
axis3DProjection = mplot $ "ax = plot.figure().gca(projection='3d')"
wireframe a b c = mplot $ "ax.plot_wireframe(np.array(data[" # a # "]), np.array(data[" # b # "]), np.array(data[" # c # "]), rstride=1, cstride=1)"
surface a b c = mplot $ "ax.plot_surface(np.array(data[" # a # "]), np.array(data[" # b # "]), np.array(data[" # c # "]), rstride=1, cstride=1, cmap=cm.Blues, alpha=0.3)"
contourRaw a b c maxA maxB minC =
mplot ("ax.contour(data[" # a # "], data[" # b # "], data[" # c # "], zdir='z', offset=" # minC # ")")
% mplot ("ax.contour(data[" # a # "], data[" # b # "], data[" # c # "], zdir='x', offset=-" # maxA # ")")
% mplot ("ax.contour(data[" # a # "], data[" # b # "], data[" # c # "], zdir='y', offset=" # maxB #")")
minimum2 l = minimum $ minimum l
maximum2 l = maximum $ maximum l
axis3DLabels xs ys zs =
mplot "ax.set_xlabel('X')"
% mplot ("ax.set_xlim3d(" # minimum2 xs # ", " # maximum2 xs # ")")
% mplot "ax.set_ylabel('Y')"
% mplot ("ax.set_ylim3d(" # minimum2 ys # ", " # maximum2 ys # ")")
% mplot "ax.set_zlabel('Z')"
% mplot ("ax.set_zlim3d(" # minimum2 zs # ", " # maximum2 zs # ")")
subplotDataBar a width offset opts =
mplot $ "ax.bar(np.arange(len(data[" # a # "]))+" # offset # ", data[" # a # "], " # width # options opts # ")"
addSubplot r c f opts = mplot $ "ax = plot.figure().add_subplot(" # r # c # f # options opts # ")"
mplotSubplot r c f opts = mplot $ "ax = plot.subplot(" # r # "," # c # "," # f # options opts # ")"
barDefaultWidth nr = 1.0 / (fromIntegral nr + 1)
subplotBarsLabelled valuesList labels optsList =
subplotBars valuesList optsList
% axisXTickSpacing (length $ head $ valuesList) ((1.0 barDefaultWidth (length valuesList) / 2.0) :: Double) []
% axisXTickLabels labels []
subplotBars valuesList optsList =
readData valuesList
% addSubplot (1::Int) (1::Int) (1::Int) []
% (let (width :: Double) = barDefaultWidth (length valuesList) in
foldl1 (%) (zipWith3 (\vs opts i -> subplotDataBar i width (width * i) opts) valuesList optsList [0..]))
title s opts = mplot $ "plot.title('" # s # options opts # "')"
axisXTickSpacing nr width opts = mplot $ "ax.set_xticks(np.arange(" # nr # ")+" # width # options opts # ")"
axisXTickLabels labels opts = mplot $ "ax.set_xticklabels( (" # labels # ") " # options opts # " )"
interpolate a b n =
(mplot $ "data[" # b # "] = mlab.stineman_interp(np.linspace(data[" # a # "][0],data[" # a # "][-1]," # n # "),data[" # a # "],data[" # b # "],None)")
% (mplot $ "data[" # a # "] = np.linspace(data[" # a # "][0],data[" # a # "][-1]," # n # ")")
plotInterpolated x y n opts =
readData (x, y)
% interpolate (0::Int) (1::Int) n
% (dataPlot (0::Int) (1::Int) $ def (B "-") opts)
squareAxes = mplot "plot.axes().set_aspect('equal')"
roateAxesLabels degrees = mplot "labels = plot.axes().get_xticklabels()"
% mplot "for label in labels:"
% mplot (" label.set_rotation("#degrees#")")
verticalAxes = mplot "labels = plot.axes().get_xticklabels()"
% mplot "for label in labels:"
% mplot " label.set_rotation('vertical')"
logX = mplot "plot.axes().set_xscale('log')"
logY = mplot "plot.axes().set_yscale('log')"
xlim l u = mplot $ "plot.xlim([" # l # "," # u # "])"
ylim l u = mplot $ "plot.ylim([" # l # "," # u # "])"
plotMapLinear f s e n opts = line xs ys opts
where xs = mapLinear (\x -> x) s e n
ys = mapLinear (\x -> f x) s e n
line1 y opts = line [0..length y] y opts
matShow d opts =
readData d
% (mplot $ "plot.matshow(data" # options opts # ")")
densityBandwidth l h maybeStartEnd =
plotMapLinear f (case maybeStartEnd of
Nothing -> minimum l
(Just (start, _)) -> start)
(case maybeStartEnd of
Nothing -> maximum l
(Just (_, end)) -> end)
100
[]
where f x = sum (map (\xi -> gaussianPdf x xi h) l) / ((fromIntegral $ length l) * h)
gaussianPdf x mu sigma = exp ( sqr (x mu) / (2 * sigma)) / sqrt (2 * pi * sigma)
sqr x = x * x
density l maybeStartEnd =
densityBandwidth l (((4 * (variance ** 5)) / (fromIntegral $ 3 * length l)) ** (1 / 5) / 3) maybeStartEnd
where mean = foldl' (+) 0 l / (fromIntegral $ length l)
variance = foldl' (+) 0 (map (\x -> sqr (x mean)) l) / (fromIntegral $ length l)
sqr x = x * x