packages feed

hgg-analyze-bridge 0.1.0.0 → 0.2.0.0

raw patch · 5 files changed

+271/−90 lines, 5 filesdep ~directorydep ~hgg-coredep ~hgg-frame

Dependency ranges changed: directory, hgg-core, hgg-frame, hgg-svg

Files

CHANGELOG.md view
@@ -1,5 +1,12 @@ # Changelog for `hgg-analyze-bridge` +## 0.2.0.0 — 2026-08-13++- Supports the hanalyze 0.2.0.1 multi-package split (the dependency on the+  `hanalyze` umbrella package is unchanged; `Hanalyze.Plot` now ships+  separately as `hanalyze-plot`).+- Bilingual (English / Japanese) haddock.+ ## 0.1.0.0 — 2026-07-18  First public release on Hackage.
hgg-analyze-bridge.cabal view
@@ -1,13 +1,13 @@ cabal-version:      3.0 name:               hgg-analyze-bridge-version:            0.1.0.0+version:            0.2.0.0 extra-doc-files:    CHANGELOG.md synopsis:           Bridge from hanalyze (Hanalyze.Model.HBM.ModelGraph) to hgg DAG rendering description:   Renders hanalyze's HBM ModelGraph directly to SVG / PNG / PDF via hgg.   No graphviz CLI or Mermaid CDN dependency, which makes it suitable for   embedding in production applications and for offline batch use.-  .+   The existing Mermaid HTML (Hanalyze.Viz.ModelGraph) and Graphviz DOT   (Hanalyze.Viz.ModelGraphDot) routes in hanalyze remain available;   choose whichever fits the use case.@@ -36,9 +36,9 @@                   , containers           >= 0.6  && < 0.8                   , vector               >= 0.12 && < 0.14                   , dataframe-core       ^>= 1.1-                  , hgg-core    ^>= 0.1-                  , hgg-frame   ^>= 0.1-                  , hgg-svg     ^>= 0.1+                  , hgg-core    ^>= 0.2+                  , hgg-frame   ^>= 0.2+                  , hgg-svg     ^>= 0.2                   , hanalyze >= 0.2 && < 0.3                   , hmatrix              >= 0.20 && < 0.21   default-language: Haskell2010@@ -50,7 +50,7 @@   build-depends:    base                   , text                   , containers-                  , directory+                  , directory            >= 1.3  && < 1.4                   , hgg-analyze-bridge                   , hanalyze >= 0.2 && < 0.3   default-language: Haskell2010@@ -63,7 +63,7 @@                   , vector               >= 0.12 && < 0.14                   , containers                   , text-                  , directory+                  , directory            >= 1.3  && < 1.4                   , hgg-analyze-bridge                   , hgg-core                   , hgg-frame
src/Graphics/Hgg/Bridge/Analyze.hs view
@@ -1,33 +1,53 @@ -- | -- Module      : Graphics.Hgg.Bridge.Analyze--- Description : hanalyze ModelGraph → hgg SVG/PNG/PDF 直描画 bridge+-- Description : Bridge: renders hanalyze's ModelGraph to SVG/PNG/PDF -- Copyright   : (c) 2026 Aelysce Project (Toshiaki Honda) -- License     : BSD-3-Clause ----- hanalyze の 'Hanalyze.Model.HBM.ModelGraph' を、 hgg 経由で+-- [日本語]: hanalyze の 'Hanalyze.Model.HBM.ModelGraph' を、 hgg 経由で -- 直接 SVG / PNG / PDF に描画する公開 API。 graphviz CLI / Mermaid CDN 依存なし。 --+-- [English]: The public API that renders hanalyze's+-- 'Hanalyze.Model.HBM.ModelGraph' directly to SVG / PNG / PDF via+-- hgg, with no dependency on the graphviz CLI or the Mermaid CDN.+-- -- == 3 ルートの選び方 ----- 同じ 'ModelGraph' を出力する 3 種類のルートがある。 用途に応じて使い分け:+-- [日本語]: 同じ 'ModelGraph' を出力する 3 種類のルートがある。 用途に応じて使い分け: ----- +---------------+---------------------------------------------+--------------------+-----------------------+--- | ルート        | 場所                                        | 出力 / 描画依存    | 推奨用途              |--- +===============+=============================================+====================+=======================+--- | Mermaid HTML  | @Hanalyze.Viz.ModelGraph.renderModelGraph@  | .html + CDN script | GitHub README、 ノート |--- +---------------+---------------------------------------------+--------------------+-----------------------+--- | Graphviz DOT  | @Hanalyze.Viz.ModelGraphDot.toDot@          | .dot text + dot CLI| graphviz 連携、 加工   |--- +---------------+---------------------------------------------+--------------------+-----------------------+--- | 本 module     | 'renderModelGraphSVG' (= A3 完了)           | 依存ゼロ           | production、 offline   |--- |               | 'renderModelGraphPNG' / 'renderModelGraphPDF' (= A4 stub、 backend 待ち) |             |             |--- +---------------+---------------------------------------------+--------------------+-----------------------++-- [Mermaid HTML]: @Hanalyze.Viz.ModelGraph.renderModelGraph@ —+--   出力は @.html@ で描画に CDN script が要る。 GitHub README やノート向け。+-- [Graphviz DOT]: @Hanalyze.Viz.ModelGraphDot.toDot@ —+--   出力は @.dot@ テキストで描画に @dot@ CLI が要る。 graphviz 連携・加工向け。+-- [本 module]: 'renderModelGraphSVG' は依存ゼロで production / offline 向け。+--   @renderModelGraphPNG@ / @renderModelGraphPDF@ は backend 待ちの stub。 --+-- [English]: There are 3 routes that all output the same 'ModelGraph'.+-- Choose according to the use case:+--+-- [Mermaid HTML]: @Hanalyze.Viz.ModelGraph.renderModelGraph@ —+--   outputs @.html@, which needs a CDN script to render. For GitHub READMEs+--   and notebooks.+-- [Graphviz DOT]: @Hanalyze.Viz.ModelGraphDot.toDot@ — outputs+--   @.dot@ text, which needs the @dot@ CLI to render. For graphviz+--   integration and post-processing.+-- [This module]: 'renderModelGraphSVG' has zero dependencies, for+--   production / offline use. @renderModelGraphPNG@ / @renderModelGraphPDF@+--   are stubs pending their backends.+-- -- 3 ルートとも同じ 'ModelGraph' 構造 (= node / edge / plate) を表現する。--- visual layout は実装ごとに異なる: 本ルートは graphviz dot 70-80% 同等品質--- (Phase 1 §10.1)。+-- visual layout は実装ごとに異なる: 本ルートは graphviz dot の 70-80% 同等品質。 ----- == 使用例 (= A3 で 'renderModelGraphSVG' 公開予定)+-- [English]: All 3 routes represent the same 'ModelGraph' structure (node /+-- edge / plate). The visual layout differs between implementations: this+-- route achieves roughly 70-80% of graphviz dot's quality. --+-- == 使用例+--+-- [日本語]: 使用例 ('renderModelGraphSVG' は今後公開予定)。+-- [English]: A usage example ('renderModelGraphSVG' is planned for future+-- release).+-- -- @ -- import Graphics.Hgg.Bridge.Analyze (modelGraphToDAGSpec) -- import Hanalyze.Model.HBM (buildModelGraph)@@ -35,7 +55,7 @@ -- main = do --   let mg = buildModelGraph myModel --       (nodes, edges, plates) = modelGraphToDAGSpec mg---   -- → A3 で renderModelGraphSVG file title mg として 1 行で完結予定+--   -- → 将来的に renderModelGraphSVG file title mg として 1 行で完結予定 -- @ {-# LANGUAGE OverloadedStrings #-} module Graphics.Hgg.Bridge.Analyze@@ -64,31 +84,43 @@  import qualified Graphics.Hgg.Bridge.Analyze.Internal as I --- | 'ModelGraph' を ('[DAGNode]', '[DAGEdge]', '[DAGPlate]') の triple に変換。--- 各要素は 'Graphics.Hgg.Spec.dagFromListsWithPlates' に渡せる形になっている。+-- | [日本語]: 'ModelGraph' を ('[DAGNode]', '[DAGEdge]', '[DAGPlate]') の triple に変換。+--   各要素は 'Graphics.Hgg.Spec.dagFromListsWithPlates' に渡せる形になっている。+--   [English]: Converts a 'ModelGraph' to a triple of ('[DAGNode]',+--   '[DAGEdge]', '[DAGPlate]'). Each element is already in the form that+--   'Graphics.Hgg.Spec.dagFromListsWithPlates' accepts. modelGraphToDAGSpec   :: ModelGraph -> ([Spec.DAGNode], [Spec.DAGEdge], [Spec.DAGPlate]) modelGraphToDAGSpec = I.toDAGTriple --- | 'modelGraphToDAGSpec' の node 部分のみ。+-- | [日本語]: 'modelGraphToDAGSpec' の node 部分のみ。+--   [English]: Just the node part of 'modelGraphToDAGSpec'. modelGraphToDAGNodes :: ModelGraph -> [Spec.DAGNode] modelGraphToDAGNodes = I.toDAGNodes --- | 'modelGraphToDAGSpec' の edge 部分のみ (= dePath は 'Nothing'、 layout で埋まる)。+-- | [日本語]: 'modelGraphToDAGSpec' の edge 部分のみ (= dePath は 'Nothing'、 layout で埋まる)。+--   [English]: Just the edge part of 'modelGraphToDAGSpec' (dePath is+--   'Nothing', filled in later by layout). modelGraphToDAGEdges :: ModelGraph -> [Spec.DAGEdge] modelGraphToDAGEdges = I.toDAGEdges --- | 'modelGraphToDAGSpec' の plate 部分のみ (= plate label は @"\<name\> (N=\<size\>)"@)。+-- | [日本語]: 'modelGraphToDAGSpec' の plate 部分のみ (= plate label は @"\<name\> (N=\<size\>)"@)。+--   [English]: Just the plate part of 'modelGraphToDAGSpec' (plate labels+--   take the form @"\<name\> (N=\<size\>)"@). modelGraphToDAGPlates :: ModelGraph -> [Spec.DAGPlate] modelGraphToDAGPlates = I.toDAGPlates  -- ===========================================================================--- Phase 2 A3: SVG 直描画 API+-- SVG 直描画 API -- =========================================================================== --- | 'ModelGraph' を Phase 1 完了の DAG layout (= Sugiyama framework + plate-aware--- ordering + Catmull-Rom spline + port snap) でレンダリングし、 'VisualSpec' に--- 包んで返す。 ユーザは title / theme / size 等を追加合成可能。+-- | [日本語]: 'ModelGraph' を Sugiyama framework + plate-aware+--   ordering + Catmull-Rom spline + port snap の DAG layout でレンダリングし、+--   @VisualSpec@ に包んで返す。 ユーザは title / theme / size 等を追加合成可能。+--   [English]: Renders a 'ModelGraph' with a DAG layout (Sugiyama framework+--   + plate-aware ordering + Catmull-Rom splines + port snapping) and+--   returns it wrapped in a @VisualSpec@. Callers can further compose in+--   title / theme / size and the like. -- -- @ -- let spec = modelGraphToVisualSpec mg@@ -107,8 +139,11 @@                   positioned routed Spec.LayoutHierarchical plates   in Easy.purePlot <> Easy.layer (dagSpec <> Easy.size 22) --- | 'ModelGraph' を SVG ファイルに直描画。 title は plot 上部に表示。--- size / theme 等を細かく指定したい場合は 'modelGraphToVisualSpec' + 'plot' を使う。+-- | [日本語]: 'ModelGraph' を SVG ファイルに直描画。 title は plot 上部に表示。+--   size / theme 等を細かく指定したい場合は 'modelGraphToVisualSpec' + @plot@ を使う。+--   [English]: Renders a 'ModelGraph' directly to an SVG file. The title is+--   shown at the top of the plot. For finer control over size / theme and+--   the like, use 'modelGraphToVisualSpec' with @plot@ instead. -- -- @ -- renderModelGraphSVG \"out\/dag.svg\" \"My HBM\" mg@@ -122,8 +157,11 @@            <> Easy.heightMm 700   in SVGBackend.saveSVG path spec --- | 'renderModelGraphSVG' の ByteString 版 (= ファイル書き出さず Text で返す)。--- web server / pipeline で SVG を直接他経路に流したいときに使う。+-- | [日本語]: 'renderModelGraphSVG' の ByteString 版 (= ファイル書き出さず Text で返す)。+--   web server / pipeline で SVG を直接他経路に流したいときに使う。+--   [English]: The ByteString variant of 'renderModelGraphSVG' (returns+--   Text instead of writing a file). Useful when a web server or pipeline+--   needs to route the SVG elsewhere directly. renderModelGraphSVGBytes :: Text -> ModelGraph -> Text renderModelGraphSVGBytes titleTxt mg =   let spec = modelGraphToVisualSpec mg@@ -139,15 +177,23 @@ _unusedTextIO = TIO.writeFile  -- ===========================================================================--- Phase 2 A4: PNG / PDF 直描画 API (= backend 未実装の stub)+-- PNG / PDF 直描画 API (= backend 未実装の stub) -- =========================================================================== --- | __現状 stub__: hgg-rasterific backend は未実装の placeholder--- (= 実行すると @error \"not implemented yet\"@)。 本 API は signature を先に--- 公開しておき、 backend 実装後に自動的に動くようにする。+-- | [日本語]: __現状 stub__: hgg-rasterific backend は未実装の placeholder+--   (= 実行すると @error \"not implemented yet\"@)。 本 API は signature を先に+--   公開しておき、 backend 実装後に自動的に動くようにする。 ----- 暫定的に PNG が必要なら 'renderModelGraphSVG' で SVG を出力し、 別 tool--- (= @inkscape@ / @rsvg-convert@ 等) で PNG 化する経路を推奨。+--   暫定的に PNG が必要なら 'renderModelGraphSVG' で SVG を出力し、 別 tool+--   (= @inkscape@ / @rsvg-convert@ 等) で PNG 化する経路を推奨。+--   [English]: __Currently a stub__: a placeholder pending the+--   hgg-rasterific backend implementation (calling it raises+--   @error "not implemented yet"@). This API publishes its signature ahead+--   of time so it will start working automatically once the backend lands.+--+--   In the meantime, if PNG is needed, the recommended path is to output+--   SVG with 'renderModelGraphSVG' and convert it with an external tool+--   (e.g. @inkscape@ / @rsvg-convert@). renderModelGraphPNG :: FilePath -> Text -> ModelGraph -> IO () renderModelGraphPNG _path _titleTxt _mg =   error $ unlines@@ -158,12 +204,20 @@     , "  本 API は backend 実装後に自動的に動作します (= signature 安定)。"     ] --- | __現状 stub__: hgg-pdf backend は未実装の placeholder--- (= 実行すると @error \"not implemented yet\"@)。 本 API は signature を先に--- 公開しておき、 backend 実装後に自動的に動くようにする。+-- | [日本語]: __現状 stub__: hgg-pdf backend は未実装の placeholder+--   (= 実行すると @error \"not implemented yet\"@)。 本 API は signature を先に+--   公開しておき、 backend 実装後に自動的に動くようにする。 ----- 暫定的に PDF が必要なら 'renderModelGraphSVG' で SVG を出力し、 別 tool--- (= @rsvg-convert -f pdf@ / @inkscape --export-pdf@ 等) で PDF 化する経路を推奨。+--   暫定的に PDF が必要なら 'renderModelGraphSVG' で SVG を出力し、 別 tool+--   (= @rsvg-convert -f pdf@ / @inkscape --export-pdf@ 等) で PDF 化する経路を推奨。+--   [English]: __Currently a stub__: a placeholder pending the+--   hgg-pdf backend implementation (calling it raises @error "not+--   implemented yet"@). This API publishes its signature ahead of time so+--   it will start working automatically once the backend lands.+--+--   In the meantime, if PDF is needed, the recommended path is to output+--   SVG with 'renderModelGraphSVG' and convert it with an external tool+--   (e.g. @rsvg-convert -f pdf@ / @inkscape --export-pdf@). renderModelGraphPDF :: FilePath -> Text -> ModelGraph -> IO () renderModelGraphPDF _path _titleTxt _mg =   error $ unlines
src/Graphics/Hgg/Bridge/Analyze/Internal.hs view
@@ -1,14 +1,22 @@ -- | -- Module      : Graphics.Hgg.Bridge.Analyze.Internal--- Description : ModelGraph → DAGSpec 変換 (= 公開 API は Bridge.Analyze)+-- Description : ModelGraph to DAGSpec conversion (public API lives in Bridge.Analyze) -- Copyright   : (c) 2026 Aelysce Project (Toshiaki Honda) -- License     : BSD-3-Clause ----- 本 module は internal。 直接利用は推奨しない (= API 安定保証なし)。+-- [日本語]: 本 module は internal。 直接利用は推奨しない (= API 安定保証なし)。 -- 公開 helper は @Graphics.Hgg.Bridge.Analyze@ に。 ----- Phase 2 A2: 'toDAGNodes' / 'toDAGEdges' / 'toDAGPlates' / 'toDAGTriple' の+-- [English]: This module is internal. Direct use is not recommended (no API+-- stability is guaranteed). The public helpers live in+-- @Graphics.Hgg.Bridge.Analyze@.+--+-- 'toDAGNodes' / 'toDAGEdges' / 'toDAGPlates' / 'toDAGTriple' の -- 変換関数群。 NodeKind / Distribution / Plate stack の mapping を担当。+--+-- [English]: The conversion functions 'toDAGNodes' / 'toDAGEdges' /+-- 'toDAGPlates' / 'toDAGTriple'. Responsible for mapping NodeKind /+-- Distribution / the plate stack. {-# LANGUAGE OverloadedStrings #-} module Graphics.Hgg.Bridge.Analyze.Internal   ( -- * 変換@@ -34,8 +42,11 @@ -- 変換層 -- =========================================================================== --- | 全体変換: 'ModelGraph' → ('[DAGNode]', '[DAGEdge]', '[DAGPlate]')。--- 公開 API 側で 'Graphics.Hgg.DAG.dagPlotWithPlates' に渡す形に成形する。+-- | [日本語]: 全体変換: 'ModelGraph' → (@[DAGNode]@, @[DAGEdge]@, @[DAGPlate]@)。+--   公開 API 側で 'Graphics.Hgg.DAG.dagPlotWithPlates' に渡す形に成形する。+--   [English]: The full conversion: 'ModelGraph' to (@[DAGNode]@,+--   @[DAGEdge]@, @[DAGPlate]@). Shapes the result into the form the public+--   API passes to 'Graphics.Hgg.DAG.dagPlotWithPlates'. toDAGTriple :: ModelGraph -> ([Spec.DAGNode], [Spec.DAGEdge], [Spec.DAGPlate]) toDAGTriple mg =   ( toDAGNodes  mg@@ -43,8 +54,11 @@   , toDAGPlates mg   ) --- | 'mgNodes' を 'DAGNode' に変換。 dnX / dnY は 0 (= layout 計算で埋まる)、--- dnDist には分布名を 'Just' で入れる (= 空文字列なら 'Nothing' に正規化)。+-- | [日本語]: 'mgNodes' を @DAGNode@ に変換。 dnX / dnY は 0 (= layout 計算で埋まる)、+--   dnDist には分布名を 'Just' で入れる (= 空文字列なら 'Nothing' に正規化)。+--   [English]: Converts 'mgNodes' to @DAGNode@. dnX / dnY start at 0 (filled+--   in later by layout computation); dnDist holds the distribution name as+--   'Just' (normalized to 'Nothing' when the name is empty). toDAGNodes :: ModelGraph -> [Spec.DAGNode] toDAGNodes mg =   [ Spec.DAGNode@@ -60,14 +74,21 @@   where     nonEmpty t = if T.null t then Nothing else Just t --- | 'mgEdges' (= (parent, child) 列) を 'DAGEdge' に。 dePath = Nothing--- (= layout 計算で routing される)。 deRoute = Nothing (= 未 bake・layout で確定)。+-- | [日本語]: 'mgEdges' (= (parent, child) 列) を @DAGEdge@ に。 dePath = Nothing+--   (= layout 計算で routing される)。 deRoute = Nothing (= 未 bake・layout で確定)。+--   [English]: Converts 'mgEdges' (a list of (parent, child) pairs) to+--   @DAGEdge@. dePath is Nothing (routing happens later, during layout+--   computation); deRoute is Nothing (not yet baked — resolved by layout). toDAGEdges :: ModelGraph -> [Spec.DAGEdge] toDAGEdges mg = [ Spec.DAGEdge p c Nothing Nothing | (p, c) <- mgEdges mg ] --- | 'mgPlates' (= plate 名 → サイズ N) を 'DAGPlate' に。--- 各 plate の dpNodeIds は @nodePlates@ に当該 plate 名を含む node を列挙。--- dpLabel は @"\<plate 名\> (N=\<size\>)"@ の形式 (= dot / PyMC 慣例に近い)。+-- | [日本語]: 'mgPlates' (= plate 名 → サイズ N) を @DAGPlate@ に。+--   各 plate の dpNodeIds は @nodePlates@ に当該 plate 名を含む node を列挙。+--   dpLabel は @"\<plate 名\> (N=\<size\>)"@ の形式 (= dot / PyMC 慣例に近い)。+--   [English]: Converts 'mgPlates' (plate name to size N) to @DAGPlate@.+--   Each plate's dpNodeIds lists the nodes whose @nodePlates@ includes that+--   plate name. dpLabel takes the form @"\<plate name\> (N=\<size\>)"@+--   (close to the dot / PyMC convention). toDAGPlates :: ModelGraph -> [Spec.DAGPlate] toDAGPlates mg =   let nodesInPlate :: Text -> [Text]@@ -84,16 +105,25 @@ -- Helpers -- =========================================================================== --- | analyze の 'NodeKind' を hgg の 'DAGNodeKind' に。+-- | [日本語]: analyze の 'NodeKind' を hgg の @DAGNodeKind@ に。 -- --   * 'LatentN'        → 'Spec.NodeLatent'    (= 楕円、 stochastic latent) --   * 'ObservedN _'    → 'Spec.NodeObserved'  (= 楕円 + 灰塗、 観測)+--+--   [English]: Maps analyze's 'NodeKind' to hgg's @DAGNodeKind@.+--+--   * 'LatentN' maps to 'Spec.NodeLatent' (an ellipse, a stochastic latent)+--   * 'ObservedN _' maps to 'Spec.NodeObserved' (an ellipse filled grey, an+--     observation) mapNodeKind :: NodeKind -> Spec.DAGNodeKind mapNodeKind LatentN       = Spec.NodeLatent mapNodeKind (ObservedN _) = Spec.NodeObserved --- | plate label を @"\<name\> (N=\<size\>)"@ の形式で生成。--- analyze の Mermaid / Graphviz DOT 出力と同じ形式。+-- | [日本語]: plate label を @"\<name\> (N=\<size\>)"@ の形式で生成。+--   analyze の Mermaid / Graphviz DOT 出力と同じ形式。+--   [English]: Generates the plate label in the form+--   @"\<name\> (N=\<size\>)"@ — the same format as analyze's Mermaid /+--   Graphviz DOT output. plateLabel :: Text -> Int -> Text plateLabel name size = name <> " (N=" <> T.pack (show size) <> ")" 
src/Graphics/Hgg/Bridge/Stat.hs view
@@ -1,10 +1,10 @@ -- | -- Module      : Graphics.Hgg.Bridge.Stat--- Description : ggplot 風 stat-in (statLm/statSmooth) の回帰計算を hanalyze に委譲して解決 (Phase 16)+-- Description : ggplot-style stat-in (statLm/statSmooth) via hanalyze -- Copyright   : (c) 2026 Aelysce Project (Toshiaki Honda) -- License     : BSD-3-Clause ----- 系統 B (ggplot 風スタット・イン) の解決ロジック (Phase 16)。+-- [日本語]: 系統 B (ggplot 風スタット・イン) の解決ロジック。 -- --   ggplot の @geom_smooth(method="lm")@ / @stat_smooth@ に相当。 ユーザは plot-core の --   __stat layer__ (@statLm@ / @statSmooth@) を通常 geom と同じく @<>@ で重ねる:@@ -16,7 +16,7 @@ --   @ -- --   stat layer (@MStatLM@/@MStatSmooth@) は plot-core が持つ純タグで、 描画前に本モジュールの---   'resolveStats' が **回帰計算を hanalyze に委譲**して具体 layer (band + line) に展開する。+--   'resolveStats' が __回帰計算を hanalyze に委譲__して具体 layer (band + line) に展開する。 -- --   ★依存方向 (最重要): @plot-core@ は analyze 非依存のまま (タグを持つだけ)。 回帰 fit は --   @plot → analyze@ の逆エッジゆえ本 opt-in 隔離 package (`hgg-analyze-bridge`) のみで行う。@@ -24,10 +24,38 @@ --   ★使い方: バインド後に 'saveSVGBoundStats' / 'renderBoundStats' を使えば、 bpResolver で --   自動的に 'resolveStats' してから描画する (df は 1 回参照)。 -----   ★委譲の内訳: @statLm@ = §3.6 @parseModel "y ~ x"@ + @fitLMF@ で fit + LM @confidenceBand@ で+--   ★委譲の内訳: @statLm@ は @parseModel "y ~ x"@ + @fitLMF@ で fit + LM @confidenceBand@ で --   信頼帯 (設計行列は @designMatrix@、 fitLMF と同一 [1,x] 設計)。 @statSmooth@ = @y ~ bs(x,n)@ の --   fitLMF (B-spline、 曲線のみ・帯なし)。 stat layer の装飾 (color/stroke/alpha) は展開後の --   band/line に引き継がれる。+--+-- [English]: The resolution logic for track B (ggplot-style stat-in).+--+--   Corresponds to ggplot's @geom_smooth(method="lm")@ / @stat_smooth@.+--   Users overlay a plot-core __stat layer__ (@statLm@ / @statSmooth@) with+--   @<>@, just like a normal geom (see the example above).+--+--   A stat layer (@MStatLM@/@MStatSmooth@) is a plain tag held by+--   plot-core; before rendering, this module's 'resolveStats' __delegates the regression computation to hanalyze__+--   and expands the tag into concrete layers (band + line). (See the @<>@-composed example+--   above — the decoration on a stat layer works the same as on a normal+--   geom.)+--+--   Dependency direction (most important): @plot-core@ stays+--   analyze-independent (it only holds the tag). Regression fitting is a+--   reverse edge (@plot → analyze@), so it is confined to this opt-in+--   isolation package (`hgg-analyze-bridge`) alone.+--+--   Usage: after binding, 'saveSVGBoundStats' / 'renderBoundStats'+--   automatically call 'resolveStats' with bpResolver before rendering (the+--   dataframe is referenced only once).+--+--   What is delegated: @statLm@ fits via @parseModel "y ~ x"@ + @fitLMF@+--   and computes the confidence band via LM's @confidenceBand@ (using the+--   design matrix from @designMatrix@ — the same [1,x] design as fitLMF).+--   @statSmooth@ fits @y ~ bs(x,n)@ via fitLMF (B-spline, curve only, no+--   band). A stat layer's decoration (color/stroke/alpha) carries over to+--   the expanded band/line. {-# LANGUAGE OverloadedStrings #-}  module Graphics.Hgg.Bridge.Stat@@ -66,17 +94,30 @@ -- resolveStats: VisualSpec 中の未解決 stat layer を band+line に展開 -- =========================================================================== --- | 'VisualSpec' の各 layer を走査し、 未解決の stat layer (@MStatLM@/@MStatSmooth@) を+-- | [日本語]: 'VisualSpec' の各 layer を走査し、 未解決の stat layer (@MStatLM@/@MStatSmooth@) を --   @Resolver@ でデータ解決 → hanalyze で fit → 具体 layer (band+line / line) に置換する。 --   それ以外の layer は不変。 解決/fit に失敗した stat layer は黙って除去 (描画を妨げない)。+--   [English]: Walks each layer of a 'VisualSpec', and for any unresolved+--   stat layer (@MStatLM@/@MStatSmooth@) resolves its data via the+--   @Resolver@, fits it with hanalyze, and replaces it with concrete layers+--   (band+line / line). Other layers are left unchanged. A stat layer that+--   fails to resolve or fit is silently dropped (so rendering is not+--   blocked). resolveStats :: Resolver -> VisualSpec -> VisualSpec resolveStats r vs = vs { vsLayers = concatMap (expandLayer pal r) (vsLayers vs) }   where pal = statPalette vs --- | B2 群色の元になる categorical palette。 ★Layout の catPalRaw と同じ規律で求める+-- | [日本語]: 群色の元になる categorical palette。 ★Layout の catPalRaw と同じ規律で求める --   (spec.palette 明示 > theme 既定 series)。 これで group 別 stat 線の色が ColorByCol --   scatter (renderer は lpCategoricalPalette を index 参照) と一致する。 --   ggplot hue sentinel (["__ggplot_hue__"]) は群数依存ゆえ 'resolveGrouped' で展開する。+--   [English]: The categorical palette that group colors are drawn from.+--   Derived using the same discipline as Layout's catPalRaw (an explicit+--   spec.palette wins over the theme's default series palette). This keeps+--   per-group stat line colors consistent with ColorByCol scatter (whose+--   renderer looks up lpCategoricalPalette by index). The ggplot hue+--   sentinel (["__ggplot_hue__"]) depends on the group count, so it is+--   expanded by 'resolveGrouped'. statPalette :: VisualSpec -> [Text] statPalette vs =   let themeDefaultPal = themeSeriesPalette (fromMaybe ThemeDefault (getLast (vsTheme vs)))@@ -91,16 +132,23 @@   _                -> [ly]   where grouped f = either (const []) id (resolveGrouped pal r ly f) --- | 単群 stat resolver の型。 装飾/オプションは 'Layer'、 データは xs/ys で受ける+-- | [日本語]: 単群 stat resolver の型。 装飾/オプションは 'Layer'、 データは xs/ys で受ける --   (Resolver からの解決は 'resolveGrouped' が一度だけ行う)。+--   [English]: The type of a single-group stat resolver. Decoration/options+--   come via 'Layer', data via xs/ys (resolution from the Resolver is done+--   once, by 'resolveGrouped'). type StatFn = Layer -> V.Vector Double -> V.Vector Double -> Either String [Layer]  -- ===========================================================================--- B2: group 別 fit (= ggplot geom_smooth(aes(color=g)))+-- group 別 fit (= ggplot geom_smooth(aes(color=g))) -- =========================================================================== --- | stat layer の color encoding が群列 ('ColorByCol') を指すなら、 群ごとに分割 fit し---   群色 (ggplotHue) で複数 line/band を重畳する。 群指定が無ければ単群で 1 回 fit (B1/B3 同等)。+-- | [日本語]: stat layer の color encoding が群列 ('ColorByCol') を指すなら、 群ごとに分割 fit し+--   群色 (ggplotHue) で複数 line/band を重畳する。 群指定が無ければ単群で 1 回 fit。+--   [English]: If a stat layer's color encoding points at a group column+--   ('ColorByCol'), fits separately per group and overlays multiple+--   line/bands in the group colors (ggplotHue). Without a group, fits once+--   as a single group. resolveGrouped :: [Text] -> Resolver -> Layer -> StatFn -> Either String [Layer] resolveGrouped palRaw r ly f = do   xs0 <- colOf r (lyEncX ly)@@ -132,13 +180,17 @@             in either (const []) id (f ly' gx gy)       Right (concatMap perGroup (zip [0 ..] groups)) --- | lyColor が 'ColorByCol' なら群列を Resolver で文字ベクタに解決 (= 群分割キー)。+-- | [日本語]: lyColor が 'ColorByCol' なら群列を Resolver で文字ベクタに解決 (= 群分割キー)。+--   [English]: If lyColor is 'ColorByCol', resolves the group column to a+--   text vector via the Resolver (the group-splitting key). groupColumn :: Resolver -> Layer -> Maybe (V.Vector Text) groupColumn r ly = case getLast (lyColor ly) of   Just (ColorByCol cr) -> resolveTxt r cr   _                    -> Nothing --- | 出現順を保つ distinct (= Render.nubKeep 同等。 群の色対応を安定させる)。+-- | [日本語]: 出現順を保つ distinct (= Render.nubKeep 同等。 群の色対応を安定させる)。+--   [English]: Deduplicates while preserving order of first appearance+--   (equivalent to Render.nubKeep; keeps group-to-color mapping stable). nubKeepOrd :: Eq a => [a] -> [a] nubKeepOrd = go []   where go seen []     = reverse seen@@ -146,14 +198,22 @@           | x `elem` seen = go seen xs           | otherwise     = go (x : seen) xs --- | geom_smooth / stat_lm の回帰線の既定線幅 (Phase 34 A1: ggplot geom_smooth は+-- | [日本語]: geom_smooth / stat_lm の回帰線の既定線幅 (ggplot geom_smooth は --   @linewidth = 2 × 既定線@ = 0.753mm)。@decoOf ly@ (= user 指定 stroke) が後置で --   上書きするので、user が 'stroke' を明示した場合はそちらが優先される。+--   [English]: The default line width for geom_smooth / stat_lm regression+--   lines (ggplot's geom_smooth uses @linewidth = 2 × the default line@ =+--   0.753mm). @decoOf ly@ (the user-specified stroke, if any) overrides it+--   afterward, so an explicit 'stroke' from the user takes priority. smoothLineDefault :: Layer smoothLineDefault = stroke (0.753 *~ mm) --- | 装飾だけを抜き出した Layer (lyKind/encoding は持たない)。 band/line に @<>@ で合成すると+-- | [日本語]: 装飾だけを抜き出した Layer (lyKind/encoding は持たない)。 band/line に @<>@ で合成すると --   color/stroke/alpha/linetype が引き継がれ、 band/line 自身の lyKind は保たれる (First Monoid)。+--   [English]: A Layer holding only decoration (no lyKind/encoding).+--   Composing it into band/line with @<>@ carries over+--   color/stroke/alpha/linetype while preserving band/line's own lyKind+--   (the First monoid). decoOf :: Layer -> Layer decoOf ly = mempty   { lyColor    = lyColor    ly@@ -162,13 +222,17 @@   , lyLinetype = lyLinetype ly   } --- | stat layer の encoding 列を Resolver で数値ベクタに解決。+-- | [日本語]: stat layer の encoding 列を Resolver で数値ベクタに解決。+--   [English]: Resolves a stat layer's encoding column to a numeric vector+--   via the Resolver. colOf :: Resolver -> Last ColRef -> Either String (V.Vector Double) colOf r lc = case getLast lc >>= resolveNum r of   Just v  -> Right v   Nothing -> Left "stat layer の x/y 列を数値として解決できません" --- | lm: 回帰線 + 95% 信頼帯。 帯は半透明 (alpha 既定 0.2)、 線は装飾引き継ぎ。+-- | [日本語]: lm: 回帰線 + 95% 信頼帯。 帯は半透明 (alpha 既定 0.2)、 線は装飾引き継ぎ。+--   [English]: lm: a regression line + 95% confidence band. The band is+--   semi-transparent (default alpha 0.2); the line inherits the decoration. resolveLM :: StatFn resolveLM ly xs ys = do   (fr, _) <- fitFormula "y ~ x" xs ys@@ -189,9 +253,14 @@   Right [ bandLy   -- 帯を先に (背面)         , lineLy ] -- 線を後に (前面) --- | smooth: B-spline 平滑。 knot 数は lyBinCount (既定 6)。---   B1: 'lyStatLevel' が Just (= 'statSmoothCI') なら bs 設計行列の 'LM.confidenceBand' で---   信頼帯 (band) + 曲線 (line)、 Nothing (= 'statSmooth') なら曲線のみ。+-- | [日本語]: smooth: B-spline 平滑。 knot 数は lyBinCount (既定 6)。+--   'lyStatLevel' が Just (= @statSmoothCI@) なら bs 設計行列の 'LM.confidenceBand' で+--   信頼帯 (band) + 曲線 (line)、 Nothing (= @statSmooth@) なら曲線のみ。+--   [English]: smooth: B-spline smoothing. The knot count comes from+--   lyBinCount (default 6). When 'lyStatLevel' is Just (i.e.+--   @statSmoothCI@), computes a confidence band (band) + curve (line) via+--   the bs design matrix's 'LM.confidenceBand'; when Nothing (i.e.+--   @statSmooth@), computes only the curve. resolveSmooth :: StatFn resolveSmooth ly xs ys = do   let n       = maybe 6 id (getLast (lyBinCount ly))@@ -220,8 +289,12 @@       Right [ bandLy   -- 帯を先に (背面)             , lineLy ] -- 線を後に (前面) --- | poly: 多項式回帰 (= ggplot stat_smooth(method="lm", formula=y~poly(x,deg)))。+-- | [日本語]: poly: 多項式回帰 (= ggplot stat_smooth(method="lm", formula=y~poly(x,deg)))。 --   次数 deg は lyBinCount (既定 2)。 poly 設計行列の confidenceBand で band+line に展開。+--   [English]: poly: polynomial regression (equivalent to ggplot's+--   stat_smooth(method="lm", formula=y~poly(x,deg))). The degree comes from+--   lyBinCount (default 2). Expanded into band+line via the poly design+--   matrix's confidenceBand. resolvePoly :: StatFn resolvePoly ly xs ys = do   let deg     = maybe 2 id (getLast (lyBinCount ly))@@ -244,8 +317,12 @@   Right [ bandLy   -- 帯を先に (背面)         , lineLy ] -- 線を後に (前面) --- | resid: 残差 vs fitted 診断散布 (= base R plot(lm) #1)。 y~x で fit し、+-- | [日本語]: resid: 残差 vs fitted 診断散布 (= base R plot(lm) #1)。 y~x で fit し、 --   各点を (fitted, residual=y-fitted) に写した scatter に展開する (装飾は scatter に引き継ぐ)。+--   [English]: resid: a residuals-vs-fitted diagnostic scatter (equivalent+--   to base R's plot(lm) #1). Fits y~x and expands into a scatter mapping+--   each point to (fitted, residual=y-fitted); decoration carries over to+--   the scatter. resolveResid :: StatFn resolveResid ly xs ys = do   (fr, _) <- fitFormula "y ~ x" xs ys@@ -255,16 +332,23 @@       ry     = V.fromList resid   Right [ scatter (ColNum fx) (ColNum ry) <> decoOf ly ] --- | x/y の 2 列 DataFrame を組み、 formula を parse → fitLMF (回帰計算を analyze に委譲)。+-- | [日本語]: x/y の 2 列 DataFrame を組み、 formula を parse → fitLMF (回帰計算を analyze に委譲)。 --   列名は内部固定 ("x"/"y") で、 formula も "x"/"y" を参照する。+--   [English]: Builds a 2-column x/y DataFrame, parses the formula, then+--   fits via fitLMF (delegating the regression computation to analyze). The+--   column names are fixed internally ("x"/"y"), and the formula also+--   refers to "x"/"y". fitFormula :: Text -> V.Vector Double -> V.Vector Double            -> Either String (FitResult, [Text]) fitFormula formula xs ys = do   f <- parseModel formula   fitLMF f (xyFrame xs ys) --- | formula の設計行列 (= confidenceBand 用)。 fitLMF と同じ modelFrame→designMatrixF 経路を+-- | [日本語]: formula の設計行列 (= confidenceBand 用)。 fitLMF と同じ modelFrame→designMatrixF 経路を --   通すので、 bs(x,n) 等の基底展開も fit と完全一致する。+--   [English]: The design matrix for a formula (used for confidenceBand).+--   Goes through the same modelFrame→designMatrixF path as fitLMF, so basis+--   expansions such as bs(x,n) match the fit exactly. designFor :: Text -> V.Vector Double -> V.Vector Double           -> Either String (LA.Matrix Double) designFor formula xs ys = do@@ -273,7 +357,8 @@   (dm, _) <- designMatrixF f mf   Right dm --- | x/y 2 列の内部 DataFrame。+-- | [日本語]: x/y 2 列の内部 DataFrame。+--   [English]: The internal 2-column x/y DataFrame. xyFrame :: V.Vector Double -> V.Vector Double -> DX.DataFrame xyFrame xs ys = DX.fromNamedColumns   [ ("x", DX.fromList (V.toList xs))@@ -283,14 +368,19 @@ -- BoundPlot 向けの描画ラッパ (df 1 回参照・自動解決) -- =========================================================================== --- | バインド済プロットの spec を、 自分が持つ resolver で 'resolveStats' する。+-- | [日本語]: バインド済プロットの spec を、 自分が持つ resolver で 'resolveStats' する。+--   [English]: Runs 'resolveStats' on a bound plot's spec, using the plot's+--   own resolver. resolveBound :: BoundPlot -> BoundPlot resolveBound bp = bp { bpSpec = resolveStats (bpResolver bp) (bpSpec bp) } --- | stat を解決してから SVG 保存 (= ggplot2 の geom_smooth 込み図を 1 回の df 参照で)。+-- | [日本語]: stat を解決してから SVG 保存 (= ggplot2 の geom_smooth 込み図を 1 回の df 参照で)。+--   [English]: Resolves stats and then saves to SVG (produces a figure with+--   a geom_smooth-equivalent, referencing the dataframe only once). saveSVGBoundStats :: FilePath -> BoundPlot -> IO () saveSVGBoundStats path = saveSVGBound path . resolveBound --- | stat を解決してから SVG 文字列を返す。+-- | [日本語]: stat を解決してから SVG 文字列を返す。+--   [English]: Resolves stats and then returns the SVG string. renderBoundStats :: BoundPlot -> Text renderBoundStats = renderBound . resolveBound