{-# LANGUAGE OverloadedStrings #-}
module Main where
import Graphics.Hgg.Backend.SVG (renderBound)
import Graphics.Hgg.DataFrame (dfResolver, plotDF)
import Graphics.Hgg.Frame (PlotData (..), (|>>))
import Graphics.Hgg.Spec (ColData (..), layer, scatter)
import Data.List (isInfixOf)
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as M
import Data.Text (Text)
import qualified Data.Vector as V
import qualified DataFrame.Internal.Column as DF
import qualified DataFrame.Internal.DataFrame as DF
import System.Directory (getTemporaryDirectory, removeFile)
import System.FilePath ((</>))
import Test.Hspec
main :: IO ()
main = hspec $ do
describe "dfResolver" $ do
it "Double 列を resolve" $ do
let df = DF.fromNamedColumns
[ ("x", DF.fromList ([1.0, 2.0, 3.0] :: [Double])) ]
r = dfResolver df
case r "x" of
Just (NumData v) -> V.toList v `shouldBe` [1.0, 2.0, 3.0]
_ -> expectationFailure "expected NumData"
it "Int 列を resolve (= Double に変換)" $ do
let df = DF.fromNamedColumns
[ ("n", DF.fromList ([10, 20, 30] :: [Int])) ]
r = dfResolver df
case r "n" of
Just (NumData v) -> V.toList v `shouldBe` [10.0, 20.0, 30.0]
_ -> expectationFailure "expected NumData"
it "存在しない列は Nothing" $ do
let df = DF.fromNamedColumns
[ ("x", DF.fromList ([1.0, 2.0] :: [Double])) ]
r = dfResolver df
r "nope" `shouldBe` Nothing
it "nullable (Maybe Int) 列を resolve (NA → NaN・長さ保持)" $ do
let df = DF.fromNamedColumns
[ ("m", DF.fromList ([Just 1, Nothing, Just 3] :: [Maybe Int])) ]
r = dfResolver df
case r "m" of
Just (NumData v) -> do
length (V.toList v) `shouldBe` 3
(V.toList v !! 0, V.toList v !! 2) `shouldBe` (1.0, 3.0)
isNaN (V.toList v !! 1) `shouldBe` True -- NA は NaN
_ -> expectationFailure "expected NumData for Maybe Int column"
it "nullable (Maybe Double) 列を resolve" $ do
let df = DF.fromNamedColumns
[ ("d", DF.fromList ([Just 1.5, Nothing] :: [Maybe Double])) ]
r = dfResolver df
case r "d" of
Just (NumData v) -> do
V.toList v !! 0 `shouldBe` 1.5
isNaN (V.toList v !! 1) `shouldBe` True
_ -> expectationFailure "expected NumData for Maybe Double column"
-- Phase 61: safeColumnAs を columnAsVector 版へ置換した回帰 (§0 実測 4 ケース +
-- end-to-end)。 とくに空 DataFrame は columnAsVector が Left でなく純粋例外を
-- 投げる唯一の入力 (dataframe-operations Operations/Core.hs:825) なので、
-- 「Nothing が返り例外が漏れない」 ことが本 Phase の要。
describe "dfResolver 回帰 (Phase 61 = columnAsVector 化)" $ do
it "型不一致: Text 列は TxtData として resolve (数値型 4 試行が例外を投げず Nothing で通過)" $ do
let df = DF.fromNamedColumns
[ ("s", DF.fromList (["a", "b"] :: [Text])) ]
r = dfResolver df
case r "s" of
Just (TxtData v) -> V.toList v `shouldBe` ["a", "b"]
_ -> expectationFailure "expected TxtData"
it "空 DataFrame は Nothing (EmptyDataSetException を透過させない)" $ do
let r = dfResolver DF.empty
r "x" `shouldBe` Nothing
it "空 DataFrame を plotDF に渡して例外なく SVG が書ける (end-to-end)" $ do
tmp <- getTemporaryDirectory
let path = tmp </> "hgg-df-empty-test.svg"
plotDF path DF.empty (layer (scatter "x" "y"))
svg <- readFile path
("<svg" `isInfixOf` svg) `shouldBe` True -- 空の図が出る = 従来挙動
removeFile path
describe "PlotData DataFrame instance (Phase 14 A4)" $ do
let df = DF.fromNamedColumns
[ ("x", DF.fromList ([1.0, 2.0, 3.0] :: [Double]))
, ("y", DF.fromList ([4.0, 5.0, 6.0] :: [Double])) ]
it "columnNames は全列" $
columnNames df `shouldBe` ["x", "y"]
it "nrows は行数 (dataframeDimensions の fst)" $
nrows df `shouldBe` 3
it "Map と DataFrame で同一 SVG (同データ、 spec-2 §7)" $ do
let mapDF :: Map Text ColData
mapDF = M.fromList
[ ("x", NumData (V.fromList [1.0, 2.0, 3.0]))
, ("y", NumData (V.fromList [4.0, 5.0, 6.0])) ]
renderBound (df |>> layer (scatter "x" "y"))
`shouldBe`
renderBound (mapDF |>> layer (scatter "x" "y"))