-- |
-- Module     :  Graphics.Rendering.Chart.AreaSpots
-- Copyright  :  (c) Malcolm Wallace 2009
-- License    :  BSD-style (see COPYRIGHT file)
--
-- Area spots are a collection of unconnected filled circles,
-- with x,y position, and an independent z value to be represented
-- by the relative area of the spots.

{-# OPTIONS_GHC -XTemplateHaskell #-}

module Graphics.Rendering.Chart.AreaSpots
  ( AreaSpots(..)
  , defaultAreaSpots

  , area_spots_title
  , area_spots_linethick
  , area_spots_linecolour
  , area_spots_fillcolour
  , area_spots_max_radius
  , area_spots_values
  ) where

import qualified Graphics.Rendering.Cairo as C

import Graphics.Rendering.Chart.Types
import Graphics.Rendering.Chart.Plot
import Graphics.Rendering.Chart.Axis
import Data.Accessor.Template
import Data.Colour
import Data.Colour.Names

import Control.Monad

-- stuff that belongs in Data.Tuple
fst3 (a,_,_) = a
snd3 (_,a,_) = a
thd3 (_,_,a) = a


-- | A collection of unconnected spots, with x,y position, and an
--   independent z value to be represented by the area of the spot.
data AreaSpots z x y = AreaSpots
  { area_spots_title_      :: String
  , area_spots_linethick_  :: Double
  , area_spots_linecolour_ :: AlphaColour Double
  , area_spots_fillcolour_ :: AlphaColour Double
  , area_spots_max_radius_ :: Double	-- ^ the largest size of spot
  , area_spots_values_     :: [(x,y,z)]
  }

defaultAreaSpots :: AreaSpots z x y
defaultAreaSpots = AreaSpots
  { area_spots_title_      = ""
  , area_spots_linethick_  = 0.1
  , area_spots_linecolour_ = opaque blue
  , area_spots_fillcolour_ = flip withOpacity 0.2 blue
  , area_spots_max_radius_ = 20  -- in pixels
  , area_spots_values_     = []
  }

instance (PlotValue z) => ToPlot (AreaSpots z) where
    toPlot p = Plot { plot_render_ = renderAreaSpots p
                    , plot_legend_ = [(area_spots_title_ p, renderSpotLegend p)]
                    , plot_all_points_ = ( map fst3 (area_spots_values_ p)
                                         , map snd3 (area_spots_values_ p) )
                    }

renderAreaSpots  :: (PlotValue z) =>
                    AreaSpots z x y -> PointMapFn x y -> CRender ()
renderAreaSpots p pmap = preserveCState $
    forM_ (scaleMax ((area_spots_max_radius_ p)^2)
                    (area_spots_values_ p))
          (\ (x,y,z)-> do
              let radius = sqrt z
              let (CairoPointStyle drawSpotAt)    = filledCircles radius
                                                      (area_spots_fillcolour_ p)
              drawSpotAt (pmap (LValue x, LValue y))
              let (CairoPointStyle drawOutlineAt) = hollowCircles radius
                                                      (area_spots_linethick_ p)
                                                      (area_spots_linecolour_ p)
              drawOutlineAt (pmap (LValue x, LValue y))
          )
  where
    scaleMax :: PlotValue z => Double -> [(x,y,z)] -> [(x,y,Double)]
    scaleMax n points = let largest  = maximum (map (toValue . thd3) points)
                            scale v  = n * toValue v / largest
                        in map (\ (x,y,z) -> (x,y, scale z)) points

renderSpotLegend :: AreaSpots z x y -> Rect -> CRender ()
renderSpotLegend p r@(Rect p1 p2) = preserveCState $ do
    let radius = min (abs (p_y p1 - p_y p2)) (abs (p_x p1 - p_x p2))
        centre = linearInterpolate p1 p2
    let (CairoPointStyle drawSpotAt)    = filledCircles radius
                                            (area_spots_fillcolour_ p)
    drawSpotAt centre
    let (CairoPointStyle drawOutlineAt) = hollowCircles radius
                                            (area_spots_linethick_ p)
                                            (area_spots_linecolour_ p)
    drawOutlineAt centre
  where
    linearInterpolate (Point x0 y0) (Point x1 y1) =
        Point (x0 + abs(x1-x0)/2) (y0 + abs(y1-y0)/2)

-------------------------------------------------------------------------
-- Template haskell to derive Data.Accessor.Accessor
$( deriveAccessors ''AreaSpots )