Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to set the range of the axis in the Haskell Chart library

In haskell-chart, how do you change the range of an axis? It seems like it has something to do with lenses and viewports, but as a beginner in Haskell, I'm having a hard time reading the API:

http://hackage.haskell.org/package/Chart-0.14/docs/Graphics-Rendering-Chart-Axis-Types.html

Thanks for any help!

like image 597
Craig Avatar asked Apr 01 '14 19:04

Craig


1 Answers

I would definitely appreciate information on better ways of doing this or more information on how this works with lenses (I still don't really understand what this is doing).

I will have a go at it. This will be a cursory explanation, not just for keeping things simple but also because I am still getting the hang of lens as well. Before starting, note that you linked to the docs of Chart 0.14, a version earlier than the conversion of the API to lens. Here are the current docs that you should refer to instead. Now, to your snippet:

layout_y_axis . laxis_generate .~ scaledAxis def (0,1)

It is a function which is applied to your layout. In it, layout_y_axis and laxis_generate are lenses. Lenses are references; in this case, references to fields in data types. In simple use cases, lenses look a lot field labels, except that, unlike record labels, they are first class, and lots of interesting things can be done with them. In fact, lenses are functions which can be composed with (.); the composition, however, is done from left to right, opposite to usual Haskell practice. Thus in:

layout_y_axis . laxis_generate

layout_y_axis is a reference to a field in the layout, laxis_generate is a reference to a field in the axis (the "function that generates the axis data", according to the docs); composing them (in reverse/OO order) gives a reference to the generating function of the Y axis of the layout.

Beyond the lenses themselves, the other key part of your snippet is (.~), one of the many lens operators. It produces setter functions; that is, it takes a reference and a value and produces a function which sets the target of the reference. In your case, you get a function which makes scaledAxis def (0,1) the generating function of the Y axis of the layout.

Now, if you check the documentation of Graphics.Rendering.Chart.Layout you will find not just the lenses, but also _layout_y_axis and _laxis_generate, which are fields of Layout and LayoutAxis respectively. They are the backing fields of the lenses; in fact the lenses can be, and in this case are, generated automatically from them. Given that the module exports the field labels, you could write your function without lenses, using just record syntax:

\lay -> lay
    { _layout_y_axis =
        (\yax -> yax { _laxis_generate = scaledAxis def (0,1) })
        $ _layout_y_axis lay
    }

That, however, is just too ugly. lens can do a lot more than improve on nasty syntax; in any case, for libraries like Chart, in which field manipulation is done all over the API, just replacing the usual nested record syntax with something neat and composable is a very good thing already.


Note: In the "Easy" module in Chart-1.9, use .= instead of .~ :

import qualified Graphics.Rendering.Chart.Backend.Cairo as C
import qualified Graphics.Rendering.Chart.Easy          as C

plotit outfile points =
  C.toFile C.def outfile $ do
    C.layout_y_axis . C.laxis_generate C..= C.scaledAxis C.def (0, 1)
    C.plot (C.points "n_coffees" points) -- etc.
like image 180
duplode Avatar answered Oct 04 '22 09:10

duplode