Skip to content

Commit 73b883c

Browse files
committed
More interesting plots, images, grids, quiver
1 parent c28c98c commit 73b883c

File tree

4 files changed

+346
-51
lines changed

4 files changed

+346
-51
lines changed

matplotlib.cabal

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ test-suite matplotlib-test
4848
, random
4949
, raw-strings-qq
5050
, split
51+
, ad
5152
ghc-options: -threaded -rtsopts -with-rtsopts=-N
5253
default-language: Haskell2010
5354

src/Graphics/Matplotlib.hs

Lines changed: 57 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -57,13 +57,14 @@
5757
-- in "ax". Plotting commands should use the current axis, never the plot
5858
-- itself; the two APIs are almost identical. When creating low-level bindings
5959
-- one must remember to call "plot.sci" to set the current image when plotting a
60-
-- graph. The current spine of the axes that's being manipulated is in "spine".
60+
-- graph. The current spine of the axes that's being manipulated is in
61+
-- "spine". The current quiver is in "q"
6162
-----------------------------------------------------------------------------
6263

6364
module Graphics.Matplotlib
6465
( module Graphics.Matplotlib
6566
-- * Creating custom plots and applying options
66-
, Matplotlib(), Option(),(@@), (%), o1, o2, (##), (#), mp, def, readData
67+
, Matplotlib(), Option(),(@@), (%), o1, o2, (##), (#), mp, def, readData, readImage
6768
, str, raw, lit, updateAxes, updateFigure, mapLinear)
6869
where
6970
import Data.List
@@ -89,6 +90,7 @@ file filename m = withMplot m (\s -> python $ pyIncludes (pyBackend "agg") ++ s
8990
-- | Plot the cross-correlation and autocorrelation of several variables. TODO Due to
9091
-- a limitation in the options mechanism this takes explicit options.
9192
xacorr xs ys opts = readData (xs, ys)
93+
% figure
9294
% addSubplot 2 1 1
9395
% xcorr xs ys @@ opts
9496
% grid True
@@ -111,6 +113,11 @@ scatter :: (ToJSON t1, ToJSON t) => t1 -> t -> Matplotlib
111113
scatter x y = readData (x, y)
112114
% mp # "plot.sci(ax.scatter(data[0], data[1]" ## "))"
113115

116+
-- | Create a bar at a position with a height
117+
bar :: (ToJSON t1, ToJSON t) => t1 -> t -> Matplotlib
118+
bar left height = readData (left, height)
119+
% mp # "ax.bar(data[0], data[1]" ## ")"
120+
114121
-- | Plot a line
115122
line :: (ToJSON t1, ToJSON t) => t1 -> t -> Matplotlib
116123
line x y = plot x y `def` [o1 "-"]
@@ -171,11 +178,29 @@ matShow :: ToJSON a => a -> Matplotlib
171178
matShow d = readData d
172179
% (mp # "plot.sci(ax.matshow(data" ## "))")
173180

181+
-- | Plot an image
182+
imshow :: MplotImage a => a -> Matplotlib
183+
imshow i = readImage i
184+
% (mp # "plot.sci(ax.imshow(img" ## "))")
185+
174186
-- | Plot a matrix
175187
pcolor :: ToJSON a => a -> Matplotlib
176188
pcolor d = readData d
177189
% (mp # "plot.sci(ax.pcolor(np.array(data)" ## "))")
178190

191+
-- | Plot a matrix
192+
pcolor3 x y z = readData (x,y,z)
193+
% (mp # "plot.sci(ax.pcolor(np.array(data[0]),np.array(data[1]),np.array(data[2])" ## "))")
194+
195+
-- | Create a non-uniform image from samples
196+
nonUniformImage x y z = readData (x,y,z)
197+
% mp # "im = mpimg.NonUniformImage(ax" ## ")"
198+
% mp # "im.set_data(data[0], data[1], data[2])"
199+
200+
-- | Create a pie chart
201+
pie l = readData l
202+
% mp # "plot.pie(" # l ## ")"
203+
179204
-- | Plot a KDE of the given functions; a good bandwith will be chosen automatically
180205
density :: [Double] -> Maybe (Double, Double) -> Matplotlib
181206
density l maybeStartEnd =
@@ -186,6 +211,9 @@ density l maybeStartEnd =
186211

187212
-- * Matplotlib configuration
188213

214+
-- | Set an rc parameter
215+
rc s = mp # "plot.rc(" # str s ## ")"
216+
189217
-- | Set an rcParams key-value
190218
setParameter k v = mp # "matplotlib.rcParams["# str k #"] = " # v
191219

@@ -207,6 +235,9 @@ dataPlot a b = mp # "p = ax.plot(data[" # a # "], data[" # b # "]" ## ")"
207235
plot :: (ToJSON t, ToJSON t1) => t1 -> t -> Matplotlib
208236
plot x y = readData (x, y) % dataPlot 0 1
209237

238+
streamplot x y u v = readData (x, y, u, v)
239+
% mp # "ax.streamplot(np.asarray(data[0]), np.asarray(data[1]), np.asarray(data[2]), np.asarray(data[3])" ## ")"
240+
210241
-- | Plot x against y where x is a date.
211242
-- xunit is something like 'weeks', yearStart, monthStart, dayStart are an offset to x.
212243
-- TODO This isn't general enough; it's missing some settings about the format. The call is also a mess.
@@ -310,11 +341,24 @@ xcorr x y = readData (x, y) % mp # "ax.xcorr(data[0], data[1]" ## ")"
310341
-- | Plot auto-correlation
311342
acorr x = readData x % mp # "ax.acorr(data" ## ")"
312343

344+
-- | A quiver plot; color is optional and can be nothing
345+
quiver x y u v Nothing = readData(x,y,u,v)
346+
% mp # "q = ax.quiver(data[0], data[1], data[2], data[3]" ## ")"
347+
quiver x y u v (Just c) = readData(x,y,u,v,c)
348+
% mp # "q = ax.quiver(data[0], data[1], data[2], data[3], data[4]" ## ")"
349+
350+
-- | A key of a given size with a label for a quiver plot
351+
quiverKey x y u label = mp # "ax.quiverkey(q, "#x#", "#y#", "#u#", "#label##")"
352+
313353
-- | Plot text at a specified location
314354
text x y s = mp # "ax.text(" # x # "," # y # "," # raw s ## ")"
315355

356+
-- | Add a text to a figure instead of a particular plot
316357
figText x y s = mp # "plot.figtext(" # x # "," # y # "," # raw s ## ")"
317358

359+
-- | Add an annotation
360+
annotate s = mp # "ax.annotate(" # str s ## ")"
361+
318362
-- * Layout, axes, and legends
319363

320364
-- | Square up the aspect ratio of a plot.
@@ -386,16 +430,16 @@ axis3DLabels xs ys zs =
386430
% mp # "ax.set_zlim3d(" # minimum2 zs # ", " # maximum2 zs # ")"
387431

388432
-- | Add a label to the x axis
389-
xLabel :: String -> Matplotlib
390-
xLabel label = mp # "ax.set_xlabel(" # raw label ## ")"
433+
xlabel :: String -> Matplotlib
434+
xlabel label = mp # "ax.set_xlabel(" # raw label ## ")"
391435

392436
-- | Add a label to the y axis
393-
yLabel :: String -> Matplotlib
394-
yLabel label = mp # "ax.set_ylabel(" # raw label ## ")"
437+
ylabel :: String -> Matplotlib
438+
ylabel label = mp # "ax.set_ylabel(" # raw label ## ")"
395439

396440
-- | Add a label to the z axis
397-
zLabel :: String -> Matplotlib
398-
zLabel label = mp # "ax.set_zlabel(" # raw label ## ")"
441+
zlabel :: String -> Matplotlib
442+
zlabel label = mp # "ax.set_zlabel(" # raw label ## ")"
399443

400444
setSizeInches w h = mp # "fig.set_size_inches(" # w # "," # h # ", forward=True)"
401445

@@ -461,9 +505,12 @@ subplots = mp # "fig, axes = plot.subplots(" ## ")"
461505
-- | Access a subplot
462506
setSubplot s = mp # "ax = axes[" # s # "]" % setAx
463507

464-
-- | Add axes to a figure
508+
-- | Add axes to a plot
465509
axes = mp # "ax = plot.axes(" ## ")" % updateAxes % setAx
466510

511+
-- | Add axes to a figure
512+
addAxes = mp # "ax = fig.add_axes(" ## ")" % updateAxes % setAx
513+
467514
-- | Creates a new figure with the given id. If the Id is already in use it
468515
-- switches to that figure.
469-
figure id = mp # "plot.figure(" # id ## ")" % updateFigure
516+
figure = mp # "plot.figure(" ## ")" % updateFigure

src/Graphics/Matplotlib/Internal.hs

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
{-# LANGUAGE FlexibleInstances, ScopedTypeVariables, FlexibleContexts, ExtendedDefaultRules #-}
1+
{-# LANGUAGE FlexibleInstances, ScopedTypeVariables, FlexibleContexts, ExtendedDefaultRules, ExistentialQuantification #-}
22
-- |
33
-- Internal representations of the Matplotlib data. These are not API-stable
44
-- and may change. You can easily extend the provided bindings without relying
@@ -33,10 +33,10 @@ data Matplotlib = Matplotlib {
3333
-- | A maplotlib command, right now we have a very shallow embedding essentially
3434
-- dealing in strings containing python code as well as the ability to load
3535
-- data. The loaded data should be a json object.
36-
data MplotCommand
37-
= LoadData B.ByteString
36+
data MplotCommand =
37+
LoadData B.ByteString
38+
| forall x. MplotImage x => LoadImage x
3839
| Exec { es :: String }
39-
deriving (Show, Eq, Ord)
4040

4141
-- | Throughout the API we need to accept options in order to expose
4242
-- matplotlib's many configuration options.
@@ -50,6 +50,7 @@ data Option =
5050
-- | Convert an 'MplotCommand' to python code, doesn't do much right now
5151
toPy :: MplotCommand -> String
5252
toPy (LoadData _) = error "withMplot needed to load data"
53+
toPy (LoadImage _) = error "withMplot needed to load images"
5354
toPy (Exec str) = str
5455

5556
-- | Resolve the pending command with no options provided.
@@ -75,6 +76,12 @@ withMplot m f = preload cs []
7576
B.hPutStr dataHandle obj
7677
hClose dataHandle
7778
preload l $ ((map Exec $ pyReadData dataFile) ++ cmds))
79+
preload ((LoadImage img):l) cmds = do
80+
withSystemTempFile "data.json" $
81+
(\dataFile dataHandle -> do
82+
hClose dataHandle
83+
obj <- saveHaskellImage img dataFile
84+
preload l $ ([Exec $ "img = " ++ (loadPythonImage img obj dataFile)] ++ cmds))
7885
preload (c:l) cmds = preload l (c:cmds)
7986

8087
-- | Create a plot that executes the string as python code
@@ -85,10 +92,14 @@ mplotString s = Matplotlib S.empty Nothing (S.singleton $ Exec s)
8592
mp :: Matplotlib
8693
mp = Matplotlib S.empty Nothing S.empty
8794

88-
-- | Load the given data into the 'data' array
95+
-- | Load the given data into the python "data" array
8996
readData :: ToJSON a => a -> Matplotlib
9097
readData d = Matplotlib (S.singleton $ LoadData $ encode d) Nothing S.empty
9198

99+
-- | Load the given image into python "img" variable
100+
readImage :: MplotImage i => i -> Matplotlib
101+
readImage i = Matplotlib (S.singleton $ LoadImage i) Nothing S.empty
102+
92103
infixl 5 %
93104
-- | Combine two matplotlib commands
94105
(%) :: Matplotlib -> Matplotlib -> Matplotlib
@@ -187,9 +198,28 @@ instance (MplotValue (x, y)) => MplotValue [(x, y)] where
187198
instance MplotValue x => MplotValue (Maybe x) where
188199
toPython Nothing = "None"
189200
toPython (Just x) = toPython x
201+
instance MplotValue [[Double]] where
202+
toPython s = "np.asarray([" ++ f s ++ "])"
203+
where f [] = ""
204+
f (x:xs) = toPython x ++ "," ++ f xs
190205

191206
default (Integer, Int, Double)
192207

208+
-- | The class of Haskell images or references to imagese which can be
209+
-- transferred to matplotlib.
210+
class MplotImage a where
211+
saveHaskellImage :: a -> FilePath -> IO String
212+
loadPythonImage :: a -> String -> FilePath -> String
213+
214+
-- | An image that is a string is a file path.
215+
instance MplotImage String where
216+
saveHaskellImage _ _ = return ""
217+
loadPythonImage s _ _ = "mpimg.imread('" ++ toPython s ++ "')"
218+
219+
instance ToJSON a => MplotImage [[a]] where
220+
saveHaskellImage d fp = (B.writeFile fp $ encode d) >> return ""
221+
loadPythonImage s _ fp = unlines $ pyReadData fp
222+
193223
-- $ Options
194224

195225
-- | Add an option to the last matplotlib command. Commands can have only one option!
@@ -285,10 +315,11 @@ pyIncludes backend = ["import matplotlib"
285315
,"import matplotlib.patches as mpatches"
286316
,"import matplotlib.pyplot as plot"
287317
,"import matplotlib.mlab as mlab"
318+
,"import matplotlib.cm as cm"
288319
,"import matplotlib.colors as mcolors"
289320
,"import matplotlib.collections as mcollections"
290321
,"import matplotlib.ticker as mticker"
291-
,"from matplotlib import cm"
322+
,"import matplotlib.image as mpimg"
292323
,"from mpl_toolkits.mplot3d import axes3d"
293324
,"import numpy as np"
294325
,"import os"
@@ -304,6 +335,10 @@ pyIncludes backend = ["import matplotlib"
304335
pyReadData :: [Char] -> [[Char]]
305336
pyReadData filename = ["data = json.loads(open('" ++ filename ++ "').read())"]
306337

338+
-- | The python command that reads an image into the img variable
339+
pyReadImage :: [Char] -> [[Char]]
340+
pyReadImage filename = ["img = mpimg.imread('" ++ filename ++ "')"]
341+
307342
-- | Detach python so we don't block (TODO This isn't working reliably)
308343
pyDetach :: [[Char]]
309344
pyDetach = ["pid = os.fork()"

0 commit comments

Comments
 (0)