{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE Strict #-}

{- |
Module      : Granite.Chart
Copyright   : (c) 2025
License     : MIT
Maintainer  : mschavinda@gmail.com

Convenience builders that construct a 'Chart' from the same kinds of
arguments accepted by the legacy chart functions in "Granite". Use
these when you want the declarative IR while still keeping ergonomic
construction:

@
import Granite.Chart
import qualified Data.Text.IO as T
import Granite.Render.Pipeline

main = do
  let ch = scatterChart
              [("A", [(0,0),(1,1),(2,4)])
              ,("B", [(0,1),(1,3),(2,6)])
              ] (Just "Random points")
  T.putStrLn (renderChartTerminal ch)
@
-}
module Granite.Chart (
    scatterChart,
    lineChart,
) where

import Data.Text (Text)

import Granite.Spec

{- | Build an IR scatter chart from N series, each with its own (x, y)
pairs. Each series becomes a separate 'GeomPoint' layer carrying its
own data frame and a group aesthetic that drives the legend.
-}
scatterChart :: [(Text, [(Double, Double)])] -> Maybe Text -> Chart
scatterChart :: [(Text, [(Double, Double)])] -> Maybe Text -> Chart
scatterChart [(Text, [(Double, Double)])]
sers Maybe Text
title =
    Chart
emptyChart
        { chartTitle = title
        , chartLayers = map mkLayer sers
        }
  where
    mkLayer :: (Text, [(Double, Double)]) -> Layer
mkLayer (Text
name, [(Double, Double)]
pts) =
        let xs :: [Double]
xs = ((Double, Double) -> Double) -> [(Double, Double)] -> [Double]
forall a b. (a -> b) -> [a] -> [b]
map (Double, Double) -> Double
forall a b. (a, b) -> a
fst [(Double, Double)]
pts
            ys :: [Double]
ys = ((Double, Double) -> Double) -> [(Double, Double)] -> [Double]
forall a b. (a -> b) -> [a] -> [b]
map (Double, Double) -> Double
forall a b. (a, b) -> b
snd [(Double, Double)]
pts
            df :: DataFrame
df = [(Text, Column)] -> DataFrame
fromColumns [(Text
"x", [Double] -> Column
ColNum [Double]
xs), (Text
"y", [Double] -> Column
ColNum [Double]
ys)]
         in (Geom -> Layer
defLayer Geom
GeomPoint)
                { layerData = Just df
                , layerMapping =
                    emptyMapping
                        { aesX = Just (ColumnRef "x")
                        , aesY = Just (ColumnRef "y")
                        , aesGroup = Just (ColumnRef name)
                        }
                }

{- | Build an IR line chart from N series; analogous to 'scatterChart'
but with one 'GeomLine' layer per series.
-}
lineChart :: [(Text, [(Double, Double)])] -> Maybe Text -> Chart
lineChart :: [(Text, [(Double, Double)])] -> Maybe Text -> Chart
lineChart [(Text, [(Double, Double)])]
sers Maybe Text
title =
    Chart
emptyChart
        { chartTitle = title
        , chartLayers = map mkLayer sers
        }
  where
    mkLayer :: (Text, [(Double, Double)]) -> Layer
mkLayer (Text
name, [(Double, Double)]
pts) =
        let xs :: [Double]
xs = ((Double, Double) -> Double) -> [(Double, Double)] -> [Double]
forall a b. (a -> b) -> [a] -> [b]
map (Double, Double) -> Double
forall a b. (a, b) -> a
fst [(Double, Double)]
pts
            ys :: [Double]
ys = ((Double, Double) -> Double) -> [(Double, Double)] -> [Double]
forall a b. (a -> b) -> [a] -> [b]
map (Double, Double) -> Double
forall a b. (a, b) -> b
snd [(Double, Double)]
pts
            df :: DataFrame
df = [(Text, Column)] -> DataFrame
fromColumns [(Text
"x", [Double] -> Column
ColNum [Double]
xs), (Text
"y", [Double] -> Column
ColNum [Double]
ys)]
         in (Geom -> Layer
defLayer Geom
GeomLine)
                { layerData = Just df
                , layerMapping =
                    emptyMapping
                        { aesX = Just (ColumnRef "x")
                        , aesY = Just (ColumnRef "y")
                        , aesGroup = Just (ColumnRef name)
                        }
                }