hgg-core-0.1.0.0: src/Graphics/Hgg/Spec/Column.hs
-- |
-- Module : Graphics.Hgg.Spec.Column
-- Description : データ列参照 (ColRef/Resolver) + inline 変換 + Point2 (Spec の leaf)
-- Copyright : (c) 2026 Aelysce Project (Toshiaki Honda)
-- License : BSD-3-Clause
--
-- Phase 55: 'Graphics.Hgg.Spec' (3420 行) の module 分割で切り出した leaf。
-- 列参照の 3 variant ('ColByName' / 'ColNum' / 'ColTxt') と render 時解決
-- ('Resolver')、 inline 列変換 ('Numeric' / 'Categorical')、 2D 点 ('Point2') を
-- 持つ。 Spec 内の他 module に依存しない最下層。 公開 API は従来どおり
-- 'Graphics.Hgg.Spec' (facade) が re-export する。 挙動・出力は完全に不変。
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverloadedStrings #-}
module Graphics.Hgg.Spec.Column
( -- * ColRef + Resolver
ColRef(..)
, ColData(..)
, Resolver
, emptyResolver
, resolveCol
, resolveNum
, resolveTxt
, colRefName
-- * Inline column conversion
, Numeric(..)
, Categorical(..)
, inline
, inlineCat
-- * Point2
, Point2(..)
) where
import Data.Aeson (FromJSON, ToJSON)
import Data.String (IsString (..))
import Data.Text (Text)
import qualified Data.Text as T
import Data.Vector (Vector)
import qualified Data.Vector as V
import GHC.Generics (Generic)
-- ===========================================================================
-- ColRef + Resolver
-- ===========================================================================
-- | データ列の参照方法。 3 つの variant:
--
-- * 'ColByName' ─ 文字列 col 名。 'Resolver' で実 Vector に解決される。
-- * 'ColNum' ─ 数値 Vector を inline (= 即値、 resolver 不要)
-- * 'ColTxt' ─ 文字列 Vector を inline (= categorical encoding 用)
--
-- 'OverloadedStrings' で `"weight" :: ColRef` が `ColByName "weight"` に。
data ColRef
= ColByName !Text
| ColNum !(Vector Double)
| ColTxt !(Vector Text)
deriving (Generic, Show, Eq)
instance ToJSON ColRef
instance FromJSON ColRef
instance IsString ColRef where
fromString = ColByName . T.pack
-- | Resolver が返すデータ形 (= 数値 or 文字列)。
data ColData
= NumData !(Vector Double)
| TxtData !(Vector Text)
deriving (Show, Eq)
-- | render 時に col 名を Vector に解決する callback。
-- 数値列 / 文字列列 どちらも返せるよう 'ColData' で union。
type Resolver = Text -> Maybe ColData
emptyResolver :: Resolver
emptyResolver _ = Nothing
-- | 'ColRef' を 'ColData' に解決。 inline は variant に応じて直接返す。
resolveCol :: Resolver -> ColRef -> Maybe ColData
resolveCol r (ColByName n) = r n
resolveCol _ (ColNum v) = Just (NumData v)
resolveCol _ (ColTxt v) = Just (TxtData v)
-- | 数値解決 (= 数値列 or 数値 inline のみ成功、 文字列は 'Nothing')。
resolveNum :: Resolver -> ColRef -> Maybe (Vector Double)
resolveNum r cr = case resolveCol r cr of
Just (NumData v) -> Just v
_ -> Nothing
-- | 文字列解決 (= 文字列 inline or 文字列列のみ成功)。
resolveTxt :: Resolver -> ColRef -> Maybe (Vector Text)
resolveTxt r cr = case resolveCol r cr of
Just (TxtData v) -> Just v
_ -> Nothing
-- | ColRef の表示名 (= hover tooltip / legend 等)。
colRefName :: ColRef -> Text
colRefName (ColByName n) = n
colRefName (ColNum _) = "<inline-num>"
colRefName (ColTxt _) = "<inline-txt>"
-- ===========================================================================
-- Inline column conversion
-- ===========================================================================
-- | 数値系 (Vector n / [n], n は Real instance を持つ任意型) を 'ColRef' に。
class Numeric a where
toNumVec :: a -> Vector Double
instance Real n => Numeric (Vector n) where
toNumVec = V.map realToFrac
instance Real n => Numeric [n] where
toNumVec = V.fromList . map realToFrac
-- | 文字列系 (= categorical encoding 用)。
class Categorical a where
toTxtVec :: a -> Vector Text
instance Categorical (Vector Text) where toTxtVec = id
instance Categorical [Text] where toTxtVec = V.fromList
instance Categorical [String] where toTxtVec = V.fromList . map T.pack
-- | 数値 (Vector / List) を inline 'ColRef' に。 'Int' / 'Double' / 'Float' /
-- 'Integer' / 'Word' 等 'Real' instance を持つ任意型に対応。
--
-- > scatter (inline xs) (inline ys)
-- > scatter (inline [1, 2, 3]) (inline [4.0, 5.0, 6.0])
inline :: Numeric a => a -> ColRef
inline = ColNum . toNumVec
-- | 文字列系を inline 'ColRef' に (= categorical encoding 用)。
--
-- > colorBy (inlineCat ["red", "blue", "green"])
inlineCat :: Categorical a => a -> ColRef
inlineCat = ColTxt . toTxtVec
-- ===========================================================================
-- Point2 (= 2D 点・3D 'Point3' と対称)
-- ===========================================================================
-- | 2D 点 (= world space)。 'Graphics.Hgg.ThreeD.Types.Point3' と対称の直積型。
-- inline の点単位 API ('scatterPoints' / 'linePoints') で使う。
--
-- JSON: positional fields → array @[x, y]@ (= aeson Generic デフォルト挙動・
-- 'Point3' と同形式)。 ※ 'Graphics.Hgg.Render' の @Point@ は screen 空間で別物。
data Point2 = Point2 !Double !Double
deriving (Show, Eq, Generic)
instance ToJSON Point2
instance FromJSON Point2