packages feed

hgg-svg-0.1.0.0: examples/ScatterDemo.hs

-- | Phase 26 §A-2 新 API デモ: 全 helper を `<>` で paren 無し合成。
--
-- @
-- cabal run scatter-demo
-- @
-- → カレントディレクトリに @scatter-demo.svg@ が生成される。
{-# LANGUAGE OverloadedStrings #-}
module Main where

import           Graphics.Hgg.Backend.SVG (saveSVG, saveSVGWith, saveSVGInteractive)
import           Graphics.Hgg.Unit         (px, (*~))
import qualified Graphics.Hgg.DAG
import           Graphics.Hgg.DAG         ((~>))
import           Graphics.Hgg.Easy
import qualified Graphics.Hgg.Spec
import           Data.Text                (Text)
import qualified Data.Vector              as V

main :: IO ()
main = do
  -- y = x² + regression line を Layer monoid で構築
  let xs = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] :: [Double]
      ys = map (\x -> x * x) xs
      fitY = map (\x -> 8 * x - 12) xs   -- 直線当てはめ (= demo 用)
      spec
        =  purePlot
        <> layer (scatter (inline xs) (inline ys) <> alpha 0.85 <> size 5)
        <> layer (line    (inline xs) (inline fitY) <> color (fromHex "#d62728") <> stroke 2)
        <> title  "y = x²  (with linear fit)"
        <> xLabel "x"
        <> yLabel "y"
        <> widthUnit (800 *~ px)
        <> heightUnit (600 *~ px)
  saveSVG "scatter-demo.svg"      spec
  saveSVG "scatter-demo-dark.svg" (spec <> theme ThemeDark)
  -- categorical 色分け demo: 30 点を 3 group に
  let xs2 = [0, 1 .. 29] :: [Double]
      ys2 = [ sin (x / 3) * 5 + 10 | x <- xs2 ]
      grp = take 30 (cycle (["A", "B", "C"] :: [String]))
      spec2
        =  purePlot
        <> layer (scatter (inline xs2) (inline ys2)
                   <> colorBy (inlineCat grp)
                   <> size 6
                   <> alpha 0.85)
        <> title  "Scatter by group (categorical color)"
        <> xLabel "t" <> yLabel "f(t)"
  saveSVG "scatter-categorical.svg" spec2
  -- Interactive (= hover tooltip + drag pan + wheel zoom、 ブラウザで開いて確認)
  saveSVGInteractive "scatter-interactive.svg" emptyResolver spec2
  -- Box + Density 統計 chart demo (= Phase 26 §E-2)
  let vals = [3.0, 4.0, 4.5, 5.0, 5.0, 5.2, 5.5, 6.0, 6.5, 7.0, 8.0, 12.0]
      boxSpec   = purePlot <> layer (boxplot (inline vals))
                          <> title "Boxplot demo" <> widthUnit (400 *~ px) <> heightUnit (600 *~ px)
      densSpec  = purePlot <> layer (density (inline vals))
                          <> title "Density (KDE) demo"
                          <> xLabel "value" <> yLabel "density"
  saveSVG "box-demo.svg"     boxSpec
  saveSVG "density-demo.svg" densSpec
  -- LogScale demo (= Phase 26 §C-2 #1)
  let xsLog = [1, 10, 100, 1000, 10000] :: [Double]
      ysLog = [2, 30, 500, 8000, 120000] :: [Double]
      logSpec = purePlot
        <> layer (scatter (inline xsLog) (inline ysLog) <> size 6)
        <> xAxis logAxis
        <> yAxis logAxis
        <> title  "Log-Log demo (= xLog + yLog)"
        <> xLabel "x (log)" <> yLabel "y (log)"
  saveSVG "loglog-demo.svg" logSpec
  -- AxisFormat demo (= Phase 26 §C-2 #2)
  let fmtSpec = purePlot
        <> layer (scatter (inline [0.0, 1.0, 2.0]) (inline [0.001234, 0.05678, 1.234]))
        <> yAxis (axisFormat (AxisExponentFmt 2))
        <> xAxis (axisFormat AxisIntegerFmt)
        <> title  "Axis format: Y=exp(2digit), X=integer"
        <> xLabel "x" <> yLabel "y"
  saveSVG "axisformat-demo.svg" fmtSpec
  -- Reference line demo (= Phase 26 §C-2 #3)
  let xsR = [0.0, 1.0, 2.0, 3.0, 4.0, 5.0] :: [Double]
      ysR = [0.1, 1.2, 1.8, 3.3, 4.1, 5.2] :: [Double]
      refSpec = purePlot
        <> layer (scatter (inline xsR) (inline ysR) <> size 6)
        <> refIdentity                   -- y = x (Actual vs Predicted の対角線)
        <> refHorizontal 2.5             -- y = 2.5
        <> refVertical 3.0               -- x = 3.0
        <> title  "Reference lines (y=x / y=2.5 / x=3)"
        <> xLabel "predicted" <> yLabel "actual"
  saveSVG "refline-demo.svg" refSpec
  -- Trellis (facet) demo (= Phase 26 §C-2 #12)
  let r facetN = case facetN of
        "x" -> Just (NumData (V.fromList [1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4]))
        "y" -> Just (NumData (V.fromList [1, 4, 9, 16, 2, 5, 8, 12, 3, 6, 9, 15]))
        "g" -> Just (TxtData (V.fromList ["A","A","A","A","B","B","B","B","C","C","C","C"]))
        _   -> Nothing
      facetSpec = purePlot
        <> layer (scatter "x" "y" <> colorBy "g" <> size 6)
        <> facet "g"
        <> title  "Facet by group (3 panel)"
        <> xLabel "x" <> yLabel "y"
  saveSVGWith "facet-demo.svg" r facetSpec
  -- DAG demo (= §E-6: HBM ModelGraph、 algebraic-graphs 流 API)
  let alphaN = ("alpha" :: Text, "α"     :: Text, NodeLatent)
      betaN  = ("beta"  :: Text, "β"     :: Text, NodeLatent)
      sigmaN = ("sigma" :: Text, "σ"     :: Text, NodeLatent)
      yN     = ("y"     :: Text, "y obs" :: Text, NodeObserved)
      hbmGraph =  alphaN ~> sigmaN
               <> betaN  ~> sigmaN
               <> sigmaN ~> yN
               <> alphaN ~> yN
               <> betaN  ~> yN
      dagSpec = purePlot
        <> layer (Graphics.Hgg.DAG.dagPlot hbmGraph <> size 22)
        <> title  "HBM ModelGraph (§E-6 DAG, hierarchical layout)"
        <> theme  ThemeLight
        <> widthUnit (600 *~ px) <> heightUnit (400 *~ px)
  saveSVG "dag-demo.svg" dagSpec
  -- ユーザ PyMC モデル再現 (= s_C/s_P/b_C/b_P/b1〜b4 priors + Weather/A/Score MutableData
  --   + x_J/x_C/x_P/x deterministic + Y Bernoulli observed、 record/course/person plates)
  let pymcLikeSpec = purePlot
        <> layer (Graphics.Hgg.Spec.dagFromListsWithPlates pymcNodes pymcEdges
                    Graphics.Hgg.Spec.LayoutManual pymcPlates
                  <> size 24)
        <> title "PyMC モデル再現 (自作 hgg)"
        <> theme  ThemeLight
        <> widthUnit (1000 *~ px) <> heightUnit (720 *~ px)
  saveSVG "pymc-like-demo.svg" pymcLikeSpec
  -- TODO-3 (2026-05-29) sub-features demo: jitter + shapeBy + sizeBy + colorCats
  let xs3 = [1.0, 1.0, 2.0, 2.0, 3.0, 3.0, 4.0, 4.0, 5.0, 5.0] :: [Double]
      ys3 = [1.5, 1.8, 2.2, 2.6, 3.1, 3.5, 4.0, 4.4, 4.7, 5.2] :: [Double]
      g3  = ["A","B","A","B","A","B","A","B","A","B"] :: [Text]
      s3  = [1.0, 5.0, 2.0, 4.0, 3.0, 6.0, 4.5, 2.5, 5.5, 3.5] :: [Double]
      todo3Spec = purePlot
        <> layer (scatter (inline xs3) (inline ys3)
                   <> jitterX 0.03
                   <> jitterY 0.03
                   <> colorBy (inlineCat g3)
                   <> colorCats ["A", "B"]
                   <> shapeBy (inlineCat g3)
                   <> shapeMapEntry "A" MShTriangle
                   <> shapeMapEntry "B" MShSquare
                   <> sizeBy (inline s3)
                   <> alpha 0.9)
        <> title  "TODO-3 demo (jitter + shapeBy + sizeBy + colorCats)"
        <> xLabel "x" <> yLabel "y"
        <> widthUnit (700 *~ px) <> heightUnit (500 *~ px)
        <> xAxis (axisRotate 30)
  saveSVG "todo3-demo.svg" todo3Spec
  -- TODO-10 (2026-05-29) font sweep demo: titleFont / tickFont / axisLabelFont をカスタム
  let todo10Spec = purePlot
        <> layer (scatter (inline [1.0, 2.0, 3.0, 4.0, 5.0])
                          (inline [1.0, 3.0, 2.0, 4.0, 3.5]) <> size 6)
        <> title  "TODO-10 font sweep demo (bold italic title + monospace tick)"
        <> xLabel "x axis" <> yLabel "y axis"
        <> titleFont     (fontSize 20 <> fontWeight "bold" <> fontItalic True
                                       <> fontColor "#c0392b")
        <> axisLabelFont (fontSize 14 <> fontFamily "Georgia"
                                       <> fontColor "#2c3e50")
        <> tickFont      (fontSize 10 <> fontFamily "monospace"
                                       <> fontColor "#7f8c8d")
        <> widthUnit (700 *~ px) <> heightUnit (500 *~ px)
  saveSVG "todo10-demo.svg" todo10Spec
  putStrLn "wrote scatter + dark + categorical + interactive + box + density + loglog + axisformat + refline + facet + dag + pymc-like + todo3 + todo10"
  where
    -- PyMC モデル node 定義 (= LayoutManual で位置手動指定、 ユーザ画像と同じ配置)
    -- 画像から読み取り: 4 階層 (= prior super → prior → deterministic → observed)
    -- 上から: s_C/s_P → b_C/b_P/b1〜b4 + Weather/A/Score → x_J/x_C/x_P → x → Y
    pymcNodes :: [Graphics.Hgg.Spec.DAGNode]
    pymcNodes =
      [ -- 階層 0 (= 超事前、 y=0.05): HalfCauchy hyper-priors (s_C / s_P)
        Graphics.Hgg.Spec.dagNodeDist "sC" "s_C" Graphics.Hgg.Spec.NodeLatent "HalfCauchy" 0.30 0.05
      , Graphics.Hgg.Spec.dagNodeDist "sP" "s_P" Graphics.Hgg.Spec.NodeLatent "HalfCauchy" 0.55 0.05
        -- 階層 1 (= prior + data、 y=0.30)
      , Graphics.Hgg.Spec.dagNodeDist "weather" "Weather" Graphics.Hgg.Spec.NodeData    "MutableData" 0.05 0.30
      , Graphics.Hgg.Spec.dagNodeDist "b4"      "b4"      Graphics.Hgg.Spec.NodeLatent  "Flat" 0.18 0.30
      , Graphics.Hgg.Spec.dagNodeDist "bC"      "b_C"     Graphics.Hgg.Spec.NodeLatent  "Normal" 0.30 0.30
      , Graphics.Hgg.Spec.dagNodeDist "b2"      "b2"      Graphics.Hgg.Spec.NodeLatent  "Flat" 0.45 0.30
      , Graphics.Hgg.Spec.dagNodeDist "bP"      "b_P"     Graphics.Hgg.Spec.NodeLatent  "Normal" 0.55 0.30
      , Graphics.Hgg.Spec.dagNodeDist "A"       "A"       Graphics.Hgg.Spec.NodeData    "MutableData" 0.66 0.30
      , Graphics.Hgg.Spec.dagNodeDist "score"   "Score"   Graphics.Hgg.Spec.NodeData    "MutableData" 0.78 0.30
      , Graphics.Hgg.Spec.dagNodeDist "b3"      "b3"      Graphics.Hgg.Spec.NodeLatent  "Flat" 0.92 0.30
        -- 階層 2 (= deterministic、 y=0.55)
      , Graphics.Hgg.Spec.dagNodeDist "xJ" "x_J" Graphics.Hgg.Spec.NodeOther "Deterministic" 0.05 0.55
      , Graphics.Hgg.Spec.dagNodeDist "b1" "b1"  Graphics.Hgg.Spec.NodeLatent "Flat" 0.20 0.55
      , Graphics.Hgg.Spec.dagNodeDist "xC" "x_C" Graphics.Hgg.Spec.NodeOther  "Deterministic" 0.30 0.55
      , Graphics.Hgg.Spec.dagNodeDist "xP" "x_P" Graphics.Hgg.Spec.NodeOther  "Deterministic" 0.66 0.55
        -- 階層 3 (= 合算 deterministic、 y=0.78)
      , Graphics.Hgg.Spec.dagNodeDist "x"  "x"   Graphics.Hgg.Spec.NodeOther  "Deterministic" 0.10 0.78
        -- 階層 4 (= observed、 y=0.96)
      , Graphics.Hgg.Spec.dagNodeDist "Y"  "Y"   Graphics.Hgg.Spec.NodeObserved "Bernoulli" 0.10 0.96
      ]
    pymcEdges :: [Graphics.Hgg.Spec.DAGEdge]
    pymcEdges =
      [ Graphics.Hgg.Spec.dagEdge "sC" "bC"
      , Graphics.Hgg.Spec.dagEdge "sP" "bP"
      , Graphics.Hgg.Spec.dagEdge "weather" "xJ"
      , Graphics.Hgg.Spec.dagEdge "b4" "xJ"
      , Graphics.Hgg.Spec.dagEdge "bC" "xC"
      , Graphics.Hgg.Spec.dagEdge "b2" "xP"
      , Graphics.Hgg.Spec.dagEdge "bP" "xP"
      , Graphics.Hgg.Spec.dagEdge "A" "xP"
      , Graphics.Hgg.Spec.dagEdge "score" "xP"
      , Graphics.Hgg.Spec.dagEdge "b3" "x"
      , Graphics.Hgg.Spec.dagEdge "xJ" "x"
      , Graphics.Hgg.Spec.dagEdge "b1" "x"
      , Graphics.Hgg.Spec.dagEdge "xC" "x"
      , Graphics.Hgg.Spec.dagEdge "xP" "x"
      , Graphics.Hgg.Spec.dagEdge "x"  "Y"
      ]
    pymcPlates :: [Graphics.Hgg.Spec.DAGPlate]
    pymcPlates =
      [ Graphics.Hgg.Spec.DAGPlate "record (2396)"  ["xJ", "x", "Y"]
      , Graphics.Hgg.Spec.DAGPlate "course (10)"    ["bC", "xC"]
      , Graphics.Hgg.Spec.DAGPlate "person (50)"    ["bP", "A", "score", "xP"]
      ]