haskellchartsihaskell

Haskell Chart library: make log-log line chart with identical x and y axes


Ok, I'm getting stumped by the Haskell Chart library. I've figured out this way to make a log-log line chart of a Vector of values in Kronos Haskell:

import Data.Vector (Vector, (!))
import qualified Data.Vector as V
import Graphics.Rendering.Chart.Easy hiding (Vector)

logLogChart name points = toRenderable $ execEC $ plot chart
    where chart = line name [V.toList $ V.imap makePoint points]
          makePoint x y = (LogValue (fromIntegral (x+1)), LogValue y)

This certainly does render a reasonable log-log line chart, with automatically chosen ranges for the x and y axes based on the data. One example (as rendered in Kronos Haskell):

Example log-log chart

The problem is that I have a specialized application where I need these two things:

  1. The range of the two axes needs to be the same. (Since my x-axis is 1-based indices into a Vector, this could be simplified to have the range of the y-axis determined by that of the x-axis.)
  2. The dimensions of the rendered chart should be square, not rectangular as in the example above.

I tried looking through the documentation for the library, but it's just got me completely stumped. Any pointers?


Solution

  • This will do but could be vastly improved (not least by removing the unsafePerformIO).

    {-# OPTIONS_GHC -Wall                      #-}
    {-# OPTIONS_GHC -fno-warn-name-shadowing   #-}
    {-# OPTIONS_GHC -fno-warn-type-defaults    #-}
    {-# OPTIONS_GHC -fno-warn-unused-do-bind   #-}
    {-# OPTIONS_GHC -fno-warn-missing-methods  #-}
    {-# OPTIONS_GHC -fno-warn-orphans          #-}
    
    import Graphics.Rendering.Chart hiding ( translate )
    import Graphics.Rendering.Chart.Backend.Diagrams
    import Diagrams.Backend.Cairo.CmdLine
    import Diagrams.Prelude hiding ( render, Renderable )
    import Data.Default.Class
    import Diagrams.Backend.CmdLine
    
    import System.IO.Unsafe
    
    
    pointVals :: [(Double, Double)]
    pointVals = map (\(x,y) -> (log x, log y)) [(1,10), (10, 100), (100, 1000)]
    
    dataPts :: PlotPoints Double Double
    dataPts = plot_points_style .~ filledCircles 2 (opaque red)
              $ plot_points_values .~ pointVals
              $ plot_points_title .~ "Data points"
              $ def
    
    layout :: Layout Double Double
    layout = layout_title .~ "Log vs Log"
             $ layout_y_axis . laxis_generate .~ scaledAxis def (0,10)
             $ layout_x_axis . laxis_generate .~ scaledAxis def (0,10)
             $ layout_plots .~ [toPlot dataPts]
             $ def
    
    myChart :: Renderable ()
    myChart = toRenderable layout
    
    denv :: DEnv
    denv = unsafePerformIO $ defaultEnv vectorAlignmentFns 500 500
    
    displayHeader :: FilePath -> Diagram B R2 -> IO ()
    displayHeader fn =
      mainRender ( DiagramOpts (Just 900) (Just 700) fn
                 , DiagramLoopOpts False Nothing 0
                 )
    
    myDiagram :: Diagram Cairo R2
    myDiagram = fst $ runBackend denv (render myChart (500, 500))
    
    main :: IO ()
    main = displayHeader "LogChart.png" myDiagram