diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -7,17 +7,17 @@
 
 ![matplotlib contour plot](https://github.com/abarbu/matplotlib-haskell/raw/master/imgs/contour.png)
 
-More info and docs forthcoming. For now see the tests for some examples.
-
-Note that the API will undergo one large change in the next few days to make
-optional arguments more pervasive and less of a hassle and to eliminate some
-necessary type annotations. After this it will stabilize.
+[Documentation is available on Hackage](https://hackage.haskell.org/package/matplotlib). For
+more examples see the tests.
 
 ```haskell
+{-# LANGUAGE ExtendedDefaultRules #-}
 import Matplotlib
 
 onscreen $ contourF (\a b -> sin (degreesRadians a) + cos (degreesRadians b)) (-100) 100 (-200) 200 10
 ```
+
+We need `-XExtendedDefaultRules` to avoid having to manually having to specify certain types.
 
 ### Installation
 
diff --git a/matplotlib.cabal b/matplotlib.cabal
--- a/matplotlib.cabal
+++ b/matplotlib.cabal
@@ -1,5 +1,5 @@
 name:                matplotlib
-version:             0.1.2.1
+version:             0.2.0
 synopsis:            Bindings to Matplotlib; a Python plotting library
 description:         Matplotlib is probably the most full featured plotting library out there. These bindings provide a quick, easy, and extensible way to use it in Haskell. Type-safety is largely non-existent and sacrificed for maximum extensibility.
 homepage:            https://github.com/abarbu/matplotlib-haskell
@@ -15,12 +15,14 @@
 
 library
   hs-source-dirs:      src
-  exposed-modules:     Graphics.Matplotlib
+  exposed-modules:     Graphics.Matplotlib.Internal
+                     , Graphics.Matplotlib
   build-depends:       base >= 4.7 && < 5
                      , process
                      , bytestring
                      , aeson
                      , temporary
+                     , containers
   default-language:    Haskell2010
 
 test-suite matplotlib-test
diff --git a/src/Graphics/Matplotlib.hs b/src/Graphics/Matplotlib.hs
--- a/src/Graphics/Matplotlib.hs
+++ b/src/Graphics/Matplotlib.hs
@@ -1,5 +1,6 @@
-{-# LANGUAGE DeriveGeneric, TypeSynonymInstances, FlexibleInstances, ScopedTypeVariables, FlexibleContexts, OverlappingInstances #-}
-
+{-# LANGUAGE ExtendedDefaultRules #-}
+-----------------------------------------------------------------------------
+-- |
 -- Matplotlib bindings and an interface to easily bind to new portions of the
 -- API. The most essential parts of Matplotlib are wrapped and exposed to
 -- Haskell through an interface that allows extenisbility. Code is generated on
@@ -14,194 +15,76 @@
 -- into a data variable that is a python array. Data is transffered via
 -- json. This data variable is indexed by various rendering commands.
 --
--- Many functions take an optional argument list.
---
 -- Functions which start with the word data operate on the data array, arguments
 -- are python code that should access that array. Most other functions take
 -- haskell objects and load them into python.
+--
+-- This module should expose enough tools so that you can bind any part of the
+-- matplotlib API. A binding with options, such as that of 'plot', looks like:
+--
+-- @
+--   readData (x, y)
+--   % mp # "p = plot.plot(data[" # a # "], data[" # b # "]" ## ")"
+--   % mp # "plot.xlabel('" # label # "')"
+-- @
+--
+-- Where important functions are:
+--
+--   [@'readData'@] Load the given data into the python data array by serializing it to JSON.
+--   [@'%'@] Sequence two plots
+--   [@'mp'@] Create an empty plot
+--   [@'#'@] Append python code to the last command in a plot
+--   [@'##'@] Just like '#' but also adds in a placeholder for an options list
+--
+-- You can call this plot with
+--
+-- > plot [1,2,3,4,5,6] [1,3,2,5,2] @@ [o1 "'go-'", o2 "linewidth" "2"]
+--
+-- where '@@' applies an options list replacing the last '##'
+--
+--  [@'o1'@] A single positional option
+--  [@'o2'@] A keyword option
+--
+-- Right now there's no easy way to bind to an option other than the last one
+-- unless you want to pass options in as parameters.
+-----------------------------------------------------------------------------
 
-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
+module Graphics.Matplotlib
+  ( module Graphics.Matplotlib
+    -- * Creating custom plots and applying options
+  , Matplotlib(), Option(),(@@), (%), o1, o2, (##), (#), mp, def, readData)
+where
 import Data.List
-import Control.Exception
-
--- | A handle internal function
-mapLinear :: (Double -> b) -> Double -> Double -> Double -> [b]
-mapLinear f s e n = map (\v -> f $ s + (v * (e - s) / n)) [0..n]
-
--- | The wrapper type for a matplotlib computation. This is opaque.
-data Matplotlib = Matplotlib [MplotCommand]
-
--- | A maplotlib command, right now we have a very shallow embedding essentially
--- dealing in opaque strings containing python code as well as the ability to
--- load data. The loaded should be a json object.
-data MplotCommand
-  = LoadData B.ByteString
-  | Exec String
-  deriving (Show, Eq, Ord)
-
--- | Throughout the API we need to accept options in order to expose
--- matplotlib's many configuration options.
-data Option =
-  -- | results in a=b
-  K String String
-  -- | just inserts the option verbatim as an argument at the end of the function
-  | B String
-
--- | Convert an 'MplotCommand' to python code, doesn't do much right now
-toPy (LoadData _) = error "withMplot needed to load data"
-toPy (Exec str)   = str
-
--- | The io action is given a list of python commands to execute (note that
--- these are commands in the sense of lines of python code; each inidivudal line
--- may not be parseable on its own
-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 operations
-
--- | Run python given a code string.
-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))
-
--- | The standard python includes of every plot
-pyIncludes = ["import matplotlib"
-             -- TODO Provide a way to set the render backend
-             -- ,"matplotlib.use('GtkAgg')"
-             ,"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"]
-
--- | The python command that reads external data into the python data array
-pyReadData filename = ["data = json.loads(open('" ++ filename ++ "').read())"]
-
--- | Detach python so we don't block (TODO This isn't working reliably)
-pyDetach = ["pid = os.fork()"
-           ,"if(pid != 0):"
-           ,"  exit(0)"]
-
--- | Python code to show a plot
-pyOnscreen = ["plot.draw()"
-             ,"plot.show()"]
-
--- | Python code that saves a figure
-pyFigure output = ["plot.savefig('" ++ output ++ "')"]
+import Data.Aeson
+import Graphics.Matplotlib.Internal
 
---- Running a plot
+-- * Running a plot
 
 -- | Show a plot, blocks until the figure is closed
+onscreen :: Matplotlib -> IO (Either String String)
 onscreen m = withMplot m (\str -> python $ pyIncludes ++ str ++ pyDetach ++ pyOnscreen)
 
 -- | Print the python code that would be executed
+code :: Matplotlib -> IO (Either a String)
 code m = withMplot m (\str -> return $ Right $ unlines $ pyIncludes ++ str ++ pyDetach ++ pyOnscreen)
 
 -- | Save to a file
+figure :: [Char] -> Matplotlib -> IO (Either String String)
 figure filename m = withMplot m (\str -> python $ pyIncludes ++ str ++ pyFigure filename)
 
--- Creating matplotlib computations
-
--- | Create a plot that executes the string as python code
-mplot s = Matplotlib [Exec s]
-
-infixl 5 %
--- | combine two matplotlib commands
-(%) :: Matplotlib -> Matplotlib -> Matplotlib
-(Matplotlib a) % (Matplotlib b) = Matplotlib (a ++ b)
-
-infixl 6 #
--- | Values which can be combined together to form a matplotlib command. Right
--- now matplotlib commands are strings and this is just a helper to construct
--- them.
-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
-
---- Convenience & Data
+-- * Plotting commands
 
 -- | Plot the 'a' and 'b' entries of the data object
-dataPlot :: (MplotValue val, MplotValue val1) => val1 -> val -> [Option] -> Matplotlib
-dataPlot a b opts =
-  mplot $ "p = plot.plot(data[" # a # "], data[" # b # "]" # options opts # ")"
+dataPlot :: (MplotValue val, MplotValue val1) => val1 -> val -> Matplotlib
+dataPlot a b = mp # "p = plot.plot(data[" # a # "], data[" # b # "]" ## ")"
 
 -- | Plot the Haskell objects 'x' and 'y' as a line
-plot :: (ToJSON t, ToJSON t1) => t1 -> t -> [Option] -> Matplotlib
-plot x y opt =
-  readData (x, y) % dataPlot (0::Int) (1::Int) opt
-
--- | Plot & show onscreen
-showPlot :: (ToJSON t, ToJSON t1) => t1 -> t -> [Option] -> IO (Either String String)
-showPlot x y opt = onscreen (plot x y opt)
-
--- | Load the given data into the 'data' array
-readData :: ToJSON a => a -> Matplotlib
-readData d = Matplotlib [LoadData $ encode d]
-
--- ;;; Options
-
--- | An internal helper to convert a list of options to the python code that
--- applies those options in a call.
-options :: [Option] -> String
-options [] = ""
-options xs = "," # xs
-
--- | Combine a list of user options with a default; useful for options such as
--- line styles that require sane defaults but can be overriden.
-def o [] = [o]
-def o l@((B _):_) = l
-def o (x:xs) = x : def o xs
+plot :: (ToJSON t, ToJSON t1) => t1 -> t -> Matplotlib
+plot x y = readData (x, y) % dataPlot 0 1
 
 -- | Show grid lines
-gridLines = mplot "ax.grid(True)"
+gridLines :: Matplotlib
+gridLines = mp # "ax.grid(True)"
 
 -- | Plot x against y where x is a date.
 --   xunit is something like 'weeks', yearStart, monthStart, dayStart are an offset to x.
@@ -209,120 +92,135 @@
 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))"
+  % mp # "data[0] = [datetime.timedelta("#xunit#"=i) + datetime.datetime("#yearStart#","#monthStart#","#dayStart#") for i in data[0]]"
+  % dataPlot 0 1 `def` [o1 "-"]
+  % mp # "ax.xaxis.set_major_formatter(DateFormatter('%B'))"
+  % mp # "ax.xaxis.set_minor_locator(WeekdayLocator(byweekday=6))"
   
 -- | Add a label to the x axis
-xLabel label = mplot $ "plot.xlabel('" # label # "')"
+xLabel :: MplotValue val => val -> Matplotlib
+xLabel label = mp # "plot.xlabel('" # label # "')"
 
 -- | Add a label to the y axis
-yLabel label = mplot $ "plot.ylabel('" # label # "')"
+yLabel :: MplotValue val => val -> Matplotlib
+yLabel label = mp # "plot.ylabel('" # label # "')"
 
 -- | Add a label to the z axis
-zLabel label = mplot $ "plot.zlabel('" # label # "')"
+zLabel :: MplotValue val => val -> Matplotlib
+zLabel label = mp # "plot.zlabel('" # label # "')"
 
 -- | Create a histogram for the 'a' entry of the data array
-dataHistogram a bins opts = mplot $ "plot.hist(data[" # a # "]," # bins # options opts # ")"
+dataHistogram :: (MplotValue val1, MplotValue val) => val1 -> val -> Matplotlib
+dataHistogram a bins = mp # "plot.hist(data[" # a # "]," # bins ## ")"
 
 -- | Plot a histogram for the given values with 'bins'
-histogram values bins opts = readData [values] % dataHistogram (0::Int) bins opts
+histogram :: (MplotValue val, ToJSON t) => t -> val -> Matplotlib
+histogram values bins = readData [values] % dataHistogram 0 bins
 
 -- | Plot & show the histogram
-showHistogram values bins opts = onscreen $ histogram values bins opts
+showHistogram :: (ToJSON t, MplotValue val) => t -> val -> IO (Either String String)
+showHistogram values bins = onscreen $ histogram values bins
 
 -- | Create a scatter plot accessing the given fields of the data array
-dataScatter a b opts = dataPlot a b $ def (B "'.'") opts
+dataScatter :: (MplotValue val1, MplotValue val) => val1 -> val -> Matplotlib
+dataScatter a b = dataPlot a b `def` [o1 "'.'"]
 
 -- | Plot the given values as a scatter plot
-scatter x y opts = plot x y $ def (B "'.'") opts
-
--- | Plot and show a scatter plot
-showScatter x y opts = showPlot x y $ def (B "'.'") opts
+scatter :: (ToJSON t1, ToJSON t) => t1 -> t -> Matplotlib
+scatter x y = plot x y `def` [o1 "'.'"]
 
 -- | Create a line accessing the given entires of the data array
-dataLine a b opts = dataPlot a b $ def (B "'-'") opts
+dataLine :: (MplotValue val1, MplotValue val) => val1 -> val -> Matplotlib
+dataLine a b = dataPlot a b `def` [o1 "'-'"]
 
 -- | Plot a line
-line x y opts = plot x y $ def (B "'-'") opts
+line :: (ToJSON t1, ToJSON t) => t1 -> t -> Matplotlib
+line x y = plot x y `def` [o1 "'-'"]
 
 -- | Plot a line given a function that will be executed for each element of
 -- given list. The list provides the x values, the function the y values.
-lineF f l opts = plot l (map f l) $ def (B "'-'") opts
-
--- | Plot and show a line
-showLine x y opts = showPlot x y $ def (B "'-'") opts
+lineF :: (ToJSON a, ToJSON b) => (a -> b) -> [a] -> Matplotlib
+lineF f l = plot l (map f l) `def` [o1 "'-'"]
 
 -- | Create a 3D contour
 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)
+  % surface 0 1 2
+  % contourRaw 0 1 2 (maximum2 xs) (maximum2 ys) (minimum2 zs)
   % axis3DLabels xs ys zs
 
 -- | Create a 3D projection
 projections xs ys zs =
   readData (xs, ys, zs)
   % axis3DProjection
-  % contourRaw (0::Int) (1::Int) (2::Int) (maximum2 xs) (maximum2 ys) (minimum2 zs)
+  % contourRaw 0 1 2 (maximum2 xs) (maximum2 ys) (minimum2 zs)
   % axis3DLabels xs ys zs
 
 -- | Given a grid of x and y values and a number of steps call the given
 -- function and plot the 3D contour
+contourF :: (ToJSON val, MplotValue val, Ord val) => (Double -> Double -> val) -> Double -> Double -> Double -> Double -> Double -> Matplotlib
 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
+  where xs = mapLinear (\x -> (mapLinear (\_ -> x) yStart yEnd steps)) xStart xEnd steps
+        ys = mapLinear (\_ -> (mapLinear (\y -> y) yStart yEnd steps)) xStart xEnd steps
         zs = mapLinear (\x -> (mapLinear (\y -> f x y) yStart yEnd steps)) xStart xEnd steps
 
 -- | Given a grid of x and y values and a number of steps call the given
 -- function and plot the 3D projection
+projectionsF :: (ToJSON val, MplotValue val, Ord val) => (Double -> Double -> val) -> Double -> Double -> Double -> Double -> Double -> Matplotlib
 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
+  where xs = mapLinear (\x -> (mapLinear (\_ -> x) yStart yEnd steps)) xStart xEnd steps
+        ys = mapLinear (\_ -> (mapLinear (\y -> y) yStart yEnd steps)) xStart xEnd steps
         zs = mapLinear (\x -> (mapLinear (\y -> f x y) yStart yEnd steps)) xStart xEnd steps
 
 -- | Enable 3D projection
-axis3DProjection = mplot $ "ax = plot.figure().gca(projection='3d')"
+axis3DProjection :: Matplotlib
+axis3DProjection = mp # "ax = plot.figure().gca(projection='3d')"
 
 -- | Plot a 3D wireframe accessing the given elements of the data array
-wireframe a b c = mplot $ "ax.plot_wireframe(np.array(data[" # a # "]), np.array(data[" # b # "]), np.array(data[" # c # "]), rstride=1, cstride=1)"
+wireframe :: (MplotValue val2, MplotValue val1, MplotValue val) => val2 -> val1 -> val -> Matplotlib
+wireframe a b c = mp # "ax.plot_wireframe(np.array(data[" # a # "]), np.array(data[" # b # "]), np.array(data[" # c # "]), rstride=1, cstride=1)"
 
 -- | Plot a 3D surface accessing the given elements of the data array
-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)"
+surface :: (MplotValue val2, MplotValue val1, MplotValue val) => val2 -> val1 -> val -> Matplotlib
+surface a b c = mp # "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)"
 
 -- | Plot a contour accessing the given elements of the data array
+contourRaw :: (MplotValue val1, MplotValue val2, MplotValue val5,
+               MplotValue val4, MplotValue val3, MplotValue val) =>
+             val5 -> val4 -> val3 -> val2 -> val1 -> val -> Matplotlib
 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 #")")
+  mp # "ax.contour(data[" # a # "], data[" # b # "], data[" # c # "], zdir='z', offset=" # minC # ")"
+  % mp # "ax.contour(data[" # a # "], data[" # b # "], data[" # c # "], zdir='x', offset=-" # maxA # ")"
+  % mp # "ax.contour(data[" # a # "], data[" # b # "], data[" # c # "], zdir='y', offset=" # maxB #")"
 
 -- | Smallest element of a list of lists
+minimum2 :: (Ord (t a), Ord a, Foldable t1, Foldable t) => t1 (t a) -> a
 minimum2 l = minimum $ minimum l
 -- | Largest element of a list of lists
+maximum2 :: (Ord (t a), Ord a, Foldable t1, Foldable t) => t1 (t a) -> a
 maximum2 l = maximum $ maximum l
 
 -- | Label and set limits of a set of 3D axis
 -- TODO This is a mess, does both more and less than it claims.
 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 # ")")
+  mp # "ax.set_xlabel('X')"
+  % mp # "ax.set_xlim3d(" # minimum2 xs # ", " # maximum2 xs # ")"
+  % mp # "ax.set_ylabel('Y')"
+  % mp # "ax.set_ylim3d(" # minimum2 ys # ", " # maximum2 ys # ")"
+  % mp # "ax.set_zlabel('Z')"
+  % mp # "ax.set_zlim3d(" # minimum2 zs # ", " # maximum2 zs # ")"
 
 -- | Draw a bag graph in a subplot
 -- TODO Why do we need this?
 subplotDataBar a width offset opts =
-  mplot $ "ax.bar(np.arange(len(data[" # a # "]))+" # offset # ", data[" # a # "], " # width # options opts # ")"
+  mp # "ax.bar(np.arange(len(data[" # a # "]))+" # offset # ", data[" # a # "], " # width ## ")" @@ opts
 
 -- | Create a subplot with the coordinates (r,c,f)
-addSubplot r c f opts = mplot $ "ax = plot.figure().add_subplot(" # r # c # f # options opts # ")"
+addSubplot r c f = mp # "ax = plot.figure().add_subplot(" # r # c # f ## ")"
 
 -- | Access a subplot with the coordinates (r,c,f)
-mplotSubplot r c f opts = mplot $ "ax = plot.subplot(" # r # "," # c # "," # f # options opts # ")"
+mplotSubplot r c f = mp # "ax = plot.subplot(" # r # "," # c # "," # f ## ")"
 
 -- | The default bar with
 barDefaultWidth nr = 1.0 / (fromIntegral nr + 1)
@@ -330,75 +228,91 @@
 -- | Create a set of labelled bars of a given height
 subplotBarsLabelled valuesList labels optsList =
   subplotBars valuesList optsList
-  % axisXTickSpacing (length $ head $ valuesList) ((1.0 - barDefaultWidth (length valuesList) / 2.0) :: Double) []
-  % axisXTickLabels labels []
+  % axisXTickSpacing (length $ head $ valuesList) (1.0 - barDefaultWidth (length valuesList) / 2.0)
+  % axisXTickLabels labels
 
 -- | Create a subplot and a set of labelled bars
 -- TODO This is a mess..
 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..]))
+  % addSubplot 1 1 1
+  % (let width = barDefaultWidth (length valuesList) in
+       foldl1 (%) (zipWith3 (\_ opts i -> subplotDataBar i width (width * i) opts) valuesList optsList [0..]))
 
 -- | Add a title
-title s opts = mplot $ "plot.title('" # s # options opts # "')"
+title :: MplotValue val => val -> Matplotlib
+title s = mp # "plot.title('" # s ## "')"
 
 -- | Set the spacing of ticks on the x axis
-axisXTickSpacing nr width opts = mplot $ "ax.set_xticks(np.arange(" # nr # ")+" # width # options opts # ")"
+axisXTickSpacing :: (MplotValue val1, MplotValue val) => val1 -> val -> Matplotlib
+axisXTickSpacing nr width = mp # "ax.set_xticks(np.arange(" # nr # ")+" # width ## ")"
 
 -- | Set the labels on the x axis
-axisXTickLabels labels opts = mplot $ "ax.set_xticklabels( (" # labels # ") " # options opts # " )"
+axisXTickLabels :: MplotValue val => val -> Matplotlib
+axisXTickLabels labels = mp # "ax.set_xticklabels( (" # labels # ") " ## " )"
 
 -- | Update the data array to linearly interpolate between array entries
+interpolate :: (MplotValue val, MplotValue val2, MplotValue val1) => val2 -> val1 -> val -> Matplotlib
 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 # ")")
+  (mp # "data[" # b # "] = mlab.stineman_interp(np.linspace(data[" # a # "][0],data[" # a # "][-1]," # n # "),data[" # a # "],data[" # b # "],None)")
+  % (mp # "data[" # a # "] = np.linspace(data[" # a # "][0],data[" # a # "][-1]," # n # ")")
 
 -- | Plot x against y interpolating with n steps
-plotInterpolated x y n opts =
+plotInterpolated :: (MplotValue val, ToJSON t, ToJSON t1) => t1 -> t -> val -> Matplotlib
+plotInterpolated x y n =
   readData (x, y)
-  % interpolate (0::Int) (1::Int) n
-  % (dataPlot (0::Int) (1::Int) $ def (B "-") opts)
+  % interpolate 0 1 n
+  % dataPlot 0 1 `def` [o1 "-"]
 
 -- | Square up the aspect ratio of a plot.
-squareAxes = mplot "plot.axes().set_aspect('equal')"
+squareAxes :: Matplotlib
+squareAxes = mp # "plot.axes().set_aspect('equal')"
 
 -- | Set the rotation of the labels on the x axis to the given number of degrees
-roateAxesLabels degrees = mplot "labels = plot.axes().get_xticklabels()"
-   % mplot "for label in labels:"
-   % mplot ("    label.set_rotation("#degrees#")")
+roateAxesLabels :: MplotValue val => val -> Matplotlib
+roateAxesLabels degrees = mp # "labels = plot.axes().get_xticklabels()"
+   % mp # "for label in labels:"
+   % mp # "    label.set_rotation("#degrees#")"
 
 -- | Set the x labels to be vertical
-verticalAxes = mplot "labels = plot.axes().get_xticklabels()"
-   % mplot "for label in labels:"
-   % mplot "    label.set_rotation('vertical')"
+verticalAxes :: Matplotlib
+verticalAxes = mp # "labels = plot.axes().get_xticklabels()"
+   % mp # "for label in labels:"
+   % mp # "    label.set_rotation('vertical')"
 
 -- | Set the x scale to be logarithmic
-logX = mplot "plot.axes().set_xscale('log')"
+logX :: Matplotlib
+logX = mp # "plot.axes().set_xscale('log')"
 
 -- | Set the y scale to be logarithmic
-logY = mplot "plot.axes().set_yscale('log')"
+logY :: Matplotlib
+logY = mp # "plot.axes().set_yscale('log')"
 
 -- | Set limits on the x axis
-xlim l u = mplot $ "plot.xlim([" # l # "," # u # "])"
+xlim :: (MplotValue val1, MplotValue val) => val1 -> val -> Matplotlib
+xlim l u = mp # "plot.xlim([" # l # "," # u # "])"
 
 -- | Set limits on the y axis
-ylim l u = mplot $ "plot.ylim([" # l # "," # u # "])"
+ylim :: (MplotValue val1, MplotValue val) => val1 -> val -> Matplotlib
+ylim l u = mp # "plot.ylim([" # l # "," # u # "])"
 
 -- | A handy function to plot a line between two points give a function and a number o steps
-plotMapLinear f s e n opts = line xs ys opts
+plotMapLinear :: ToJSON b => (Double -> b) -> Double -> Double -> Double -> Matplotlib
+plotMapLinear f s e n = line xs ys
   where xs = mapLinear (\x -> x) s e n
         ys = mapLinear (\x -> f x) s e n
 
 -- | Plot a line between 0 and the length of the array with the given y values
-line1 y opts = line [0..length y] y opts
+line1 :: (Foldable t, ToJSON (t a)) => t a -> Matplotlib
+line1 y = line [0..length y] y
 
-matShow d opts =
-  readData d
-  % (mplot $ "plot.matshow(data" # options opts # ")")
+-- | Plot a matrix
+matShow :: ToJSON a => a -> Matplotlib
+matShow d = readData d
+            % (mp # "plot.matshow(data" ## ")")
 
 -- | Plot a KDE of the given functions with an optional start/end and a bandwidth h
+densityBandwidth :: [Double] -> Double -> Maybe (Double, Double) -> Matplotlib
 densityBandwidth l h maybeStartEnd =
   plotMapLinear f (case maybeStartEnd of
                     Nothing -> minimum l
@@ -406,13 +320,13 @@
                   (case maybeStartEnd of
                     Nothing -> maximum l
                     (Just (_, end)) -> end)
-                  100
-                  []
+                   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
 
 -- | Plot a KDE of the given functions; a good bandwith will be chosen automatically
+density :: [Double] -> Maybe (Double, Double) -> Matplotlib
 density l maybeStartEnd =
   densityBandwidth l (((4 * (variance ** 5)) / (fromIntegral $ 3 * length l)) ** (1 / 5) / 3) maybeStartEnd
   where mean = foldl' (+) 0 l / (fromIntegral $ length l)
diff --git a/src/Graphics/Matplotlib/Internal.hs b/src/Graphics/Matplotlib/Internal.hs
new file mode 100644
--- /dev/null
+++ b/src/Graphics/Matplotlib/Internal.hs
@@ -0,0 +1,253 @@
+{-# LANGUAGE FlexibleInstances, ScopedTypeVariables, FlexibleContexts, ExtendedDefaultRules #-}
+-- |
+-- Internal representations of the Matplotlib data. These are not API-stable
+-- and may change. You can easily extend the provided bindings without relying
+-- on the internals exposed here but they are provided just in case.
+module Graphics.Matplotlib.Internal where
+import System.IO.Temp
+import System.Process
+import Data.Aeson
+import Control.Monad
+import System.IO
+import qualified Data.ByteString.Lazy as B
+import Data.List
+import Control.Exception
+import qualified Data.Sequence as S
+import Data.Sequence (Seq, (|>), (><))
+import Data.Maybe
+import GHC.Exts(toList)
+
+-- | A handy miscellaneous function to linearly map over a range of numbers in a given number of steps
+mapLinear :: (Double -> b) -> Double -> Double -> Double -> [b]
+mapLinear f s e n = map (\v -> f $ s + (v * (e - s) / n)) [0..n]
+
+-- $ Basics
+
+-- | The wrapper type for a matplotlib computation.
+data Matplotlib = Matplotlib {
+  mpCommands :: Seq MplotCommand   -- ^ Resolved computations that have been transformed to commands
+  , mpPendingOption :: Maybe ([Option] -> MplotCommand)   -- ^ A pending computation that is affected by applied options
+  , mpRest :: Seq MplotCommand  -- ^ Computations that follow the one that is pending
+  }
+
+-- | A maplotlib command, right now we have a very shallow embedding essentially
+-- dealing in strings containing python code as well as the ability to load
+-- data. The loaded data should be a json object.
+data MplotCommand
+  = LoadData B.ByteString
+  | Exec { es :: String }
+  deriving (Show, Eq, Ord)
+
+-- | Throughout the API we need to accept options in order to expose
+-- matplotlib's many configuration options.
+data Option =
+  -- | results in a=b
+  K String String
+  -- | just inserts the option verbatim as an argument at the end of the function
+  | P String
+  deriving (Show, Eq, Ord)
+
+-- | Convert an 'MplotCommand' to python code, doesn't do much right now
+toPy :: MplotCommand -> String
+toPy (LoadData _) = error "withMplot needed to load data"
+toPy (Exec str)   = str
+
+-- | Resolve the pending command with no options provided.
+resolvePending :: Matplotlib -> Matplotlib
+resolvePending m = m { mpCommands =
+                       (maybe (mpCommands m)
+                              (\pendingCommand -> (mpCommands m |> pendingCommand []))
+                              $ mpPendingOption m) >< mpRest m
+                     , mpPendingOption = Nothing
+                     , mpRest = S.empty}
+
+-- | The io action is given a list of python commands to execute (note that
+-- these are commands in the sense of lines of python code; each inidivudal line
+-- may not be parseable on its own
+withMplot :: Matplotlib -> ([String] -> IO a) -> IO a
+withMplot m f = preload cs []
+  where
+    cs = toList $ mpCommands $ resolvePending m
+    preload [] cmds = f $ map toPy $ reverse cmds
+    preload ((LoadData obj):l) cmds =
+          withSystemTempFile "data.json"
+            (\dataFile dataHandle -> do
+                B.hPutStr dataHandle obj
+                hClose dataHandle
+                preload l $ ((map Exec $ pyReadData dataFile) ++ cmds))
+    preload (c:l) cmds = preload l (c:cmds)
+
+-- | Create a plot that executes the string as python code
+mplotString :: String -> Matplotlib
+mplotString s = Matplotlib S.empty Nothing (S.singleton $ Exec s)
+
+-- | Create an empty plot. This the beginning of most plotting commands.
+mp :: Matplotlib
+mp = Matplotlib S.empty Nothing S.empty
+
+-- | Load the given data into the 'data' array
+readData :: ToJSON a => a -> Matplotlib
+readData d = Matplotlib (S.singleton $ LoadData $ encode d) Nothing S.empty
+
+infixl 5 %
+-- | Combine two matplotlib commands
+(%) :: Matplotlib -> Matplotlib -> Matplotlib
+a % b | isJust $ mpPendingOption b = b { mpCommands = mpCommands (resolvePending a) >< mpCommands b }
+      | otherwise = a { mpRest = mpRest a >< mpCommands b >< mpRest b }
+
+infixl 6 #
+-- | Add Python code to the last matplotlib command
+(#) :: (MplotValue val) => Matplotlib -> val -> Matplotlib
+m # v | S.null $ mpRest m =
+        case mpPendingOption m of
+          Nothing -> m { mpRest = S.singleton $ Exec $ toPython v }
+          (Just f) -> m { mpPendingOption = Just (\o -> Exec $ es (f o) ++ toPython v)}
+      | otherwise = m { mpRest = S.adjust (\(Exec s) -> Exec $ s ++ toPython v) (S.length (mpRest m) - 1) (mpRest m) }
+
+-- | Values which can be combined together to form a matplotlib command. These
+-- specify how values are rendered in Python code.
+class MplotValue val where
+  toPython :: val -> String
+
+instance MplotValue String where
+  toPython s = s
+instance MplotValue [String] where
+  toPython [] = ""
+  toPython (x:xs) = toPython x ++ "," ++ toPython xs
+instance MplotValue Double where
+  toPython s = show s
+instance MplotValue Integer where
+  toPython s = show s
+instance MplotValue Int where
+  toPython s = show s
+instance (MplotValue x) => MplotValue (x, x) where
+  toPython (n, v) = toPython n ++ " = " ++ toPython v
+instance (MplotValue (x, y)) => MplotValue [(x, y)] where
+  toPython [] = ""
+  toPython (x:xs) = toPython x ++ ", " ++ toPython xs
+
+default (Integer, Int, Double)
+
+-- $ Options
+
+-- | Add an option to the last matplotlib command. Commands can have only one option!
+-- optFn :: Matplotlib -> Matplotlib
+optFn :: ([Option] -> String) -> Matplotlib -> Matplotlib
+optFn f l | isJust $ mpPendingOption l = error "Commands can have only open option. TODO Enforce this through the type system or relax it!"
+          | otherwise = l' { mpPendingOption = Just (\os -> Exec (sl ++ f os)) }
+  where (l', (Exec sl)) = removeLast l
+        removeLast x@(Matplotlib _ Nothing s) = (x { mpRest = sdeleteAt (S.length s - 1) s }
+                                                , fromMaybe (Exec "") (slookup (S.length s - 1) s))
+        removeLast _ = error "TODO complex options"
+        -- TODO When containers is >0.5.8 replace these
+        slookup i s | i < S.length s = Just $ S.index s i
+                    | otherwise      = Nothing
+        sdeleteAt i s | i < S.length s = S.take i s >< S.drop (i + 1) s
+                      | otherwise      = s
+
+-- | Merge two commands with options between
+options :: Matplotlib -> Matplotlib
+options l = optFn (\o -> renderOptions o) l
+
+infixl 6 ##
+-- | A combinator like '#' that also inserts an option
+(##) :: MplotValue val => Matplotlib -> val -> Matplotlib
+m ## v = options m # v
+
+-- | An internal helper to convert a list of options to the python code that
+-- applies those options in a call.
+renderOptions :: [Option] -> [Char]
+renderOptions [] = ""
+renderOptions xs = f xs
+  where  f (P a:l) = "," ++ toPython a ++ f l
+         f (K a b:l) = "," ++ toPython a ++  "=" ++ toPython b ++ f l
+         f [] = ""
+
+-- | An internal helper that modifies the options of a plot.
+optionFn :: ([Option] -> [Option]) -> Matplotlib -> Matplotlib
+optionFn f m = case mpPendingOption m of
+                 (Just cmd) -> m { mpPendingOption = Just (\os -> cmd $ f os) }
+                 Nothing -> error "Can't apply an option to a non-option command"
+
+-- | Apply a list of options to a plot resolving any pending options.
+option :: Matplotlib -> [Option] -> Matplotlib
+option m os = resolvePending $ optionFn (\os' -> os ++ os') m
+
+infixl 6 @@
+-- | A combinator for 'option' that applies a list of options to a plot
+(@@) :: Matplotlib -> [Option] -> Matplotlib
+m @@ os = option m os
+
+-- | Bind a list of default options to a plot. Positional options are kept in
+-- order and default that way as well. Keyword arguments are
+def :: Matplotlib -> [Option] -> Matplotlib
+def m os = optionFn (defFn os) m
+
+defFn :: [Option] -> [Option] -> [Option]
+defFn os os' = merge ps' ps ++ (nub $ ks' ++ ks)
+           where isK (K _ _) = True
+                 isK _ = False
+                 isP (P _) = True
+                 isP _ = False
+                 ps  = filter isP os
+                 ps' = filter isP os'
+                 ks  = filter isK os
+                 ks' = filter isK os'
+                 merge l []  = l
+                 merge [] l' = l'
+                 merge (x:l) (_:l') = (x : merge l l')
+
+-- $ Python operations
+
+-- | Run python given a code string.
+python :: Foldable t => t String -> IO (Either String String)
+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))
+
+-- | The standard python includes of every plot
+pyIncludes :: [[Char]]
+pyIncludes = ["import matplotlib"
+             -- TODO Provide a way to set the render backend
+             -- ,"matplotlib.use('GtkAgg')"
+             ,"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"]
+
+-- | The python command that reads external data into the python data array
+pyReadData :: [Char] -> [[Char]]
+pyReadData filename = ["data = json.loads(open('" ++ filename ++ "').read())"]
+
+-- | Detach python so we don't block (TODO This isn't working reliably)
+pyDetach :: [[Char]]
+pyDetach = ["pid = os.fork()"
+           ,"if(pid != 0):"
+           ,"  exit(0)"]
+
+-- | Python code to show a plot
+pyOnscreen :: [[Char]]
+pyOnscreen = ["plot.draw()"
+             ,"plot.show()"]
+
+-- | Python code that saves a figure
+pyFigure :: [Char] -> [[Char]]
+pyFigure output = ["plot.savefig('" ++ output ++ "')"]
+
+-- | Create a positional option
+o1 x = P x
+
+-- | Create a keyword option
+o2 x y = K x y
diff --git a/test/Spec.hs b/test/Spec.hs
--- a/test/Spec.hs
+++ b/test/Spec.hs
@@ -1,3 +1,5 @@
+{-# language ExtendedDefaultRules #-}
+
 import Test.Tasty
 import Test.Tasty.HUnit
 import System.IO.Unsafe
@@ -31,9 +33,10 @@
   , testPlot "line-function" m9
   , testPlot "quadratic" m10
   , testPlot "projections" m11
+  , testPlot "line-options" m12
   ]
 
-xs = [-0.54571992 :: Double,  1.48409716, -0.57545561,  2.13058156, -0.75740497,
+xs = [-0.54571992,  1.48409716, -0.57545561,  2.13058156, -0.75740497,
       -1.27879086, -0.96008858, -1.65482373, -1.69086194, -1.41925464,
        0.68144401,  1.44847131,  1.12124327,  1.32056244, -0.4555279 ,
        1.96002923, -1.34810771,  0.01513306,  1.25298883, -1.07541677,
@@ -53,7 +56,7 @@
       -1.50321042,  0.03949817,  0.35286613,  1.86994544,  1.16249707,
        0.57421291,  1.21151469,  1.74863421,  0.42287859, -1.22785845,
       -0.61650528,  1.76743253, -0.45818694, -1.16560907,  0.0677502 ]
-ys = [ 1.28231455 :: Double,  1.13480471,  0.57738712,  0.10268954,  1.00162163,
+ys = [ 1.28231455,  1.13480471,  0.57738712,  0.10268954,  1.00162163,
       -0.85453571, -1.61049343,  1.33194242,  0.12054943, -0.56568776,
        2.11550009,  0.03663454,  0.24889313,  0.85458325,  0.77326592,
        0.58815223, -0.79997005,  0.54979418,  0.47711544,  0.73004143,
@@ -75,35 +78,38 @@
       -0.8023867 , -2.81354854,  0.39553427, -0.22235586, -1.34302011]
 
 m1 :: Matplotlib
-m1 = histogram xs (8 :: Int) []
+m1 = histogram xs 8
 
 m2 :: Matplotlib
-m2 = histogram xs (8 :: Int) [K "cumulative" "True"]
+m2 = histogram xs 8 @@ [o2 "cumulative" "True"]
 
-m3 = scatter xs ys []
+m3 = scatter xs ys
 
 degreesRadians a = a * pi / 180.0
 radiansDegrees a = a * 180.0 / pi
 
 m4 = contourF (\a b -> sin (degreesRadians a) + cos (degreesRadians b)) (-100) 100 (-200) 200 10
 
-m5 = histogram xs (7 :: Int) []
+m5 = histogram xs 7
    % yLabel "number of queries"
    % xLabel "true positives"
 
 m6 = subplotBarsLabelled
-       [[(40 :: Double), 50, 20, 50], [10, 20, 30, 40], [40, 50, 20, 50]]
+       [[40, 50, 20, 50], [10, 20, 30, 40], [40, 50, 20, 50]]
        ["a", "b", "c", "d"] []
-      % title "Wee a title" []
+      % title "Wee a title"
       % xLabel "X"
       % yLabel "Y"
 
-m7 = densityBandwidth [2.1, 1.3, 0.4, 1.9, 5.1, 6.2] 1.5 (Just (-6, 11)) % ylim (0::Double) (0.2::Double)
+m7 = densityBandwidth [2.1, 1.3, 0.4, 1.9, 5.1, 6.2] 1.5 (Just (-6, 11))
+  % ylim 0 0.2
 
 m8 = density [2.1, 1.3, 0.4, 1.9, 5.1, 6.2] (Just (-6, 11))
 
-m9 = lineF (\x -> x**2) ([0,0.01..1] :: [Double]) []
+m9 = lineF (\x -> x**2) [0,0.01..1]
 
-m10 = plotMapLinear (\x -> x**2) (-2) 2 100 [B "'.'"] % title "Quadratic function" []
+m10 = plotMapLinear (\x -> x**2) (-2) 2 100 @@ [o1 "'.'"] % title "Quadratic function"
 
 m11 = projectionsF (\a b -> cos (degreesRadians a) + sin (degreesRadians b)) (-100) 100 (-200) 200 10
+
+m12 = plot [1,2,3,4,5,6] [1,3,2,5,2,4] @@ [o1 "'go-'", o2 "linewidth" "2"]
