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 +7/−0
- hgg-analyze-bridge.cabal +7/−7
- src/Graphics/Hgg/Bridge/Analyze.hs +95/−41
- src/Graphics/Hgg/Bridge/Analyze/Internal.hs +45/−15
- src/Graphics/Hgg/Bridge/Stat.hs +117/−27
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