One of the challenges with building a library like Chart is the tension between ease of use and flexibility. Users want to produce charts with a minimum of code up front, but later want to refine the details. The chart library addresses this through the use of "defaulted records" using Data.Default.Class. Because such records are often nested, we rely on the somewhat intimidating lens library to modify the default values. We end up with code to create chart elements like this:
sinusoid2 = plot_points_title .~ "fn(x)" $ plot_points_values .~ mydata $ plot_points_style . point_color .~ opaque red $ def
This is much simpler and cleaner that the corresponding code using native record accessors, but it still has a certain amount of syntactic overhead.
I’ve added a simple state monad to the library to further clean up the syntax. The state of the monad is the value being constructed, allowing the use of the monadic lens operators. The above code sample becomes:
sinusoid2 = execEC $ do plot_points_title .= "fn(x)" plot_points_values .= mydata plot_points_style . point_color .= opaque red
This may seem only a minor syntactic improvement, but it adds up over an typical chart definition.
A few other changes further reduce the clutter in charting code:
- A new Easy module that includes helper functions and key dependencies
- Simpler "toFile" functions in the rendering backends
- Automatic sequencing of colours for successive plots
All this means that a simple plot can now be a one liner:
import Graphics.Rendering.Chart.Easy import Graphics.Rendering.Chart.Backend.Cairo mydata :: [Double,Double] mydata = ... main = toFile def "test.png" $ plot $ points "lines" mydata
But this extends naturally to more complex charts. The code differences between the new stateful API versus the existing API can been seen in this example.
The stateful API is available in chart v1.3 It is a thin layer over the existing API – both will be continue to be available in the future.