packages feed

hgg-3d 0.1.0.0 → 0.2.0.0

raw patch · 15 files changed

+1269/−457 lines, 15 filesdep ~hgg-coredep ~hgg-framedep ~hgg-pdfPVP ok

version bump matches the API change (PVP)

Dependency ranges changed: hgg-core, hgg-frame, hgg-pdf, hgg-rasterific, hgg-svg

API changes (from Hackage documentation)

Files

CHANGELOG.md view
@@ -1,5 +1,10 @@ # Changelog for `hgg-3d` +## 0.2.0.0 — 2026-08-13++- Follows the hgg-core 0.2 coordinate generalization.+- Bilingual (English / Japanese) haddock.+ ## 0.1.0.0 — 2026-07-18  First public release on Hackage.
hgg-3d.cabal view
@@ -1,6 +1,6 @@ cabal-version:      3.0 name:               hgg-3d-version:            0.1.0.0+version:            0.2.0.0 extra-doc-files:    CHANGELOG.md synopsis:           3D plot (CPU projection) for hgg; works with all 2D backends description:@@ -8,7 +8,7 @@   CPU-based projection plus the painter's algorithm let the existing hgg   2D backends (SVG / PDF / Rasterific) render 3D scatter / line /   wireframe / surface plots as-is.-  .+   No WebGL required — pure Haskell end to end. Interactive 3D (camera   rotation etc.) in web environments is handled by a separate   PureScript / WebGL frontend outside this package, connected through the@@ -50,11 +50,11 @@                   , text                 >= 2.0  && < 2.2                   , containers           >= 0.6  && < 0.8                   , vector               >= 0.13 && < 0.14-                  , hgg-core        ^>= 0.1-                  , hgg-svg         ^>= 0.1-                  , hgg-pdf         ^>= 0.1-                  , hgg-rasterific  ^>= 0.1-                  , hgg-frame       ^>= 0.1+                  , hgg-core        ^>= 0.2+                  , hgg-svg         ^>= 0.2+                  , hgg-pdf         ^>= 0.2+                  , hgg-rasterific  ^>= 0.2+                  , hgg-frame       ^>= 0.2                   , aeson                >= 2.1  && < 2.3                   , bytestring           >= 0.11 && < 0.13                   , directory            >= 1.3  && < 1.4
src/Graphics/Hgg/ThreeD.hs view
@@ -1,28 +1,42 @@ -- | -- Module      : Graphics.Hgg.ThreeD--- Description : hgg-3d 公開エントリ (= 主要 API の再エクスポート)+-- Description : Public entry point of hgg-3d (re-exports the main API) -- Copyright   : (c) 2026 Aelysce Project (Toshiaki Honda) -- License     : BSD-3-Clause ----- 3D plot library (CPU projection + painter's algorithm)。 既存の hgg--- 2D backend (SVG / PDF / Rasterific) でそのまま使える。+-- [日本語]: 3D plot library (CPU projection + painter's algorithm)。 既存の+--   hgg 2D backend (SVG / PDF / Rasterific) でそのまま使える。 ----- 主要型と関数を本 module で再エクスポート。 細部は子 module を参照:+--   主要型と関数を本 module で再エクスポート。 細部は子 module を参照: ----- * "Graphics.Hgg.ThreeD.Types" — Point3 / Vec3 / Mat4 / Camera3D / Projection3D--- * "Graphics.Hgg.ThreeD.Projection" — project3D / lookAt 等の純粋関数--- * "Graphics.Hgg.ThreeD.Axes" — Axes3D (= 立方体 wireframe + 3 軸 tick)--- * "Graphics.Hgg.ThreeD.Scatter" — MScatter3D--- * "Graphics.Hgg.ThreeD.Line" — MLine3D / MWireframe3D--- * "Graphics.Hgg.ThreeD.Surface" — MSurface3D--- * "Graphics.Hgg.ThreeD.Easy" — matplotlib mplot3d 風 helper+--   * "Graphics.Hgg.ThreeD.Types" — Point3 / Vec3 / Mat4 / Camera3D / Projection3D+--   * "Graphics.Hgg.ThreeD.Projection" — project3D / lookAt 等の純粋関数+--   * "Graphics.Hgg.ThreeD.Axes" — Axes3D (= 立方体 wireframe + 3 軸 tick)+--   * "Graphics.Hgg.ThreeD.Scatter" — MScatter3D+--   * "Graphics.Hgg.ThreeD.Line" — MLine3D / MWireframe3D+--   * "Graphics.Hgg.ThreeD.Surface" — MSurface3D+--   * "Graphics.Hgg.ThreeD.Easy" — matplotlib mplot3d 風 helper+-- [English]: A 3D plot library (CPU projection + the painter's algorithm).+--   Works directly with the existing hgg 2D backends (SVG / PDF /+--   Rasterific).+--+--   This module re-exports the main types and functions; see the child+--   modules for the details:+--+--   * "Graphics.Hgg.ThreeD.Types" — Point3 / Vec3 / Mat4 / Camera3D / Projection3D+--   * "Graphics.Hgg.ThreeD.Projection" — pure functions such as project3D / lookAt+--   * "Graphics.Hgg.ThreeD.Axes" — Axes3D (a cube wireframe plus tick marks on the 3 axes)+--   * "Graphics.Hgg.ThreeD.Scatter" — MScatter3D+--   * "Graphics.Hgg.ThreeD.Line" — MLine3D / MWireframe3D+--   * "Graphics.Hgg.ThreeD.Surface" — MSurface3D+--   * "Graphics.Hgg.ThreeD.Easy" — matplotlib mplot3d-style helpers module Graphics.Hgg.ThreeD-  ( -- * 中核型 (= A3 で実装)+  ( -- * 中核型     module Graphics.Hgg.ThreeD.Types-    -- * Projection (= A3 で実装)+    -- * Projection   , module Graphics.Hgg.ThreeD.Projection-    -- * Axes3D (= A4 で実装)-    -- * Scatter / Line / Wireframe / Surface (= A5-A7 で実装)+    -- * Axes3D+    -- * Scatter / Line / Wireframe / Surface   ) where  import Graphics.Hgg.ThreeD.Types
src/Graphics/Hgg/ThreeD/Axes.hs view
@@ -1,19 +1,34 @@ -- | -- Module      : Graphics.Hgg.ThreeD.Axes--- Description : 3D 軸 (立方体 wireframe + 3 軸 tick / label) (Phase 3 A4)+-- Description : 3D axes (cube wireframe with tick marks and labels on 3 axes) -- Copyright   : (c) 2026 Aelysce Project (Toshiaki Honda) -- License     : BSD-3-Clause ----- mplot3d 風の axes: data bounding box (= xMin..zMax) を立方体 wireframe で--- 囲み、 各軸に等間隔の tick を 1 列描画。 ラベルは tick の少し外側へ。+-- [日本語]: mplot3d 風の axes: data bounding box (= xMin..zMax) を立方体+--   wireframe で囲み、 各軸に等間隔の tick を 1 列描画。 ラベルは tick の少し+--   外側へ。 ----- 設計判断:+--   設計判断: -----   * Phase 3 では「奥側面のみ」 を厳密判定せず、 12 辺全て描画 (= 多少視認性---     落ちるが mplot3d 流の hairy wireframe より単純)---   * tick は (xMin..xMax 等) の niceTicks3D で 5 点 default---   * label: tick 値 + 軸名 (= "x"/"y"/"z")---   * 出力は '[Primitive]' (= hgg-core の 2D primitive)、 既存 backend で描画+--     * 「奥側面のみ」 を厳密判定せず、 12 辺全て描画 (= 多少視認性落ちるが+--       mplot3d 流の hairy wireframe より単純)+--     * tick は (xMin..xMax 等) の niceTicks3D で 5 点 default+--     * label: tick 値 + 軸名 (= "x"/"y"/"z")+--     * 出力は '[Primitive]' (= hgg-core の 2D primitive)、 既存+--       backend で描画+-- [English]: mplot3d-style axes: surrounds the data bounding box+--   (xMin..zMax) with a cube wireframe and draws a row of evenly spaced+--   ticks on each axis. Labels sit just outside the ticks.+--+--   Design decisions:+--+--     * Rather than strictly determining "only the back-facing sides", all+--       12 edges are drawn (slightly less legible, but simpler than+--       mplot3d's hairy wireframe)+--     * Ticks default to 5 points via niceTicks3D over (xMin..xMax, etc.)+--     * Labels: the tick value plus the axis name ("x"/"y"/"z")+--     * Output is '[Primitive]' (the 2D primitive from hgg-core),+--       rendered by the existing backends {-# LANGUAGE DeriveGeneric     #-} {-# LANGUAGE OverloadedStrings #-} module Graphics.Hgg.ThreeD.Axes@@ -25,7 +40,7 @@   , renderAxes3D   , renderAxes3DWith   , renderAxes3DWithLabels-    -- * 壁面 pane + gridline (Phase 25 A6)+    -- * 壁面 pane + gridline   , PaneStyle3D (..)   , defaultPaneStyle3D   , renderAxes3DPanes@@ -47,7 +62,8 @@                                                   project3D) import           Graphics.Hgg.ThreeD.Types --- | 3D 軸の bounding box + tick 数。+-- | [日本語]: 3D 軸の bounding box + tick 数。+--   [English]: The 3D axes' bounding box plus the tick count. data Axes3D = Axes3D   { axesXMin  :: !Double   , axesXMax  :: !Double@@ -55,15 +71,20 @@   , axesYMax  :: !Double   , axesZMin  :: !Double   , axesZMax  :: !Double-  , axesNTicks :: !Int       -- ^ 軸あたり tick 数 (= default 5)-  , axesXLog  :: !Bool       -- ^ Phase 25 A8: x 軸を log scale に (既定 False)-  , axesYLog  :: !Bool       -- ^ Phase 25 A8: y 軸を log scale に (既定 False)-  , axesZLog  :: !Bool       -- ^ Phase 25 A8: z 軸を log scale に (既定 False)+  , axesNTicks :: !Int       -- ^ [日本語]: 軸あたり tick 数 (= default 5)+                              --   [English]: The number of ticks per axis (default 5).+  , axesXLog  :: !Bool       -- ^ [日本語]: x 軸を log scale に (既定 False)+                              --   [English]: Whether to use a log scale for the x axis (default False).+  , axesYLog  :: !Bool       -- ^ [日本語]: y 軸を log scale に (既定 False)+                              --   [English]: Whether to use a log scale for the y axis (default False).+  , axesZLog  :: !Bool       -- ^ [日本語]: z 軸を log scale に (既定 False)+                              --   [English]: Whether to use a log scale for the z axis (default False).   } deriving (Show, Eq, Generic) instance ToJSON   Axes3D instance FromJSON Axes3D --- | 単位 cube (= [-1, 1]^3) + tick 5。+-- | [日本語]: 単位 cube (= [-1, 1]^3) + tick 5。+--   [English]: The unit cube (@[-1, 1]^3@) with 5 ticks per axis. defaultAxes3D :: Axes3D defaultAxes3D = Axes3D   { axesXMin = -1, axesXMax = 1@@ -73,8 +94,10 @@   , axesXLog = False, axesYLog = False, axesZLog = False   } --- | 等間隔 tick 位置を 'n' 個生成。 niceNumbers アルゴリズム ではなく素朴な等間隔--- (= mplot3d 風)。+-- | [日本語]: 等間隔 tick 位置を @n@ 個生成。 niceNumbers アルゴリズム ではなく+--   素朴な等間隔 (= mplot3d 風)。+--   [English]: Generates @n@ evenly spaced tick positions. Uses plain even+--   spacing (mplot3d-style) rather than a niceNumbers algorithm. niceTicks3D :: Int -> Double -> Double -> [Double] niceTicks3D n lo hi   | n <= 1 || abs (hi - lo) < 1e-12 = [lo]@@ -82,9 +105,14 @@       let step = (hi - lo) / fromIntegral (n - 1)       in [ lo + step * fromIntegral i | i <- [0 .. n - 1] ] --- | Phase 25 A8: log scale 軸の tick 位置 = @[lo, hi]@ 区間内の 10 の冪--- (decade)。 端 (lo/hi) も含め、 冪が 1 個以下なら端 2 点で補う。 値は正前提--- (lo<=0 は 1e-12 に clamp)。 軸ラベルは元の値のまま 'formatNum' で出る。+-- | [日本語]: log scale 軸の tick 位置 = @[lo, hi]@ 区間内の 10 の冪+--   (decade)。 端 (lo/hi) も含め、 冪が 1 個以下なら端 2 点で補う。 値は正前提+--   (lo<=0 は 1e-12 に clamp)。 軸ラベルは元の値のまま 'formatNum' で出る。+--   [English]: The tick positions for a log-scale axis: powers of 10+--   (decades) within @[lo, hi]@. Includes the endpoints (lo/hi); if fewer+--   than two powers fall in range, falls back to the two endpoints. Values+--   are assumed positive (lo<=0 is clamped to 1e-12). Axis labels still show+--   the original value via 'formatNum'. logTicks3D :: Double -> Double -> [Double] logTicks3D lo0 hi0 =   let lo = max 1e-12 lo0@@ -99,8 +127,10 @@        (_:_:_) -> inRange        _       -> [lo, hi]   -- 冪が足りなければ端 2 点 --- | Phase 25 A8: log フラグで tick 生成を切替える (log = 'logTicks3D'・線形 =--- 'niceTicks3D')。 両 axes renderer が共有。+-- | [日本語]: log フラグで tick 生成を切替える (log = 'logTicks3D'・線形 =+--   'niceTicks3D')。 両 axes renderer が共有。+--   [English]: Switches tick generation based on the log flag (log uses+--   'logTicks3D', linear uses 'niceTicks3D'). Shared by both axes renderers. axisTicks3D :: Bool -> Int -> Double -> Double -> [Double] axisTicks3D isLog n lo hi   | isLog     = logTicks3D lo hi@@ -110,18 +140,26 @@ -- 描画 -- =========================================================================== --- | Axes3D を 2D Primitive 列にレンダリング (= cube wireframe + 3 軸 tick + label)。+-- | [日本語]: Axes3D を 2D Primitive 列にレンダリング (= cube wireframe + 3 軸 tick + label)。+--   [English]: Renders an 'Axes3D' as a list of 2D primitives (cube+--   wireframe plus ticks and labels on the 3 axes). renderAxes3D :: Camera3D -> Projection3D -> Viewport -> Axes3D -> [Primitive] renderAxes3D = renderAxes3DWith id --- | Phase 24 A3: 投影前に座標変換 @f@ を合成する版。 @f@ に正規化--- (データ bbox → [-1,1]^3) を渡すと、 **tick ラベルは元のデータ値のまま**--- 形状だけ view box に収まる (saveSVG3D の正規化 pipeline 用)。+-- | [日本語]: 投影前に座標変換 @f@ を合成する版。 @f@ に正規化+--   (データ bbox → [-1,1]^3) を渡すと、 __tick ラベルは元のデータ値のまま__+--   形状だけ view box に収まる (saveSVG3D の正規化 pipeline 用)。+--   [English]: The variant that composes a coordinate transform @f@ before+--   projection. Passing normalization (data bbox to @[-1,1]^3@) as @f@+--   fits the shape into the view box, __leaving tick labels unchanged__+--   (used by the @saveSVG3D@ normalization pipeline). renderAxes3DWith :: (Point3 -> Point3)                  -> Camera3D -> Projection3D -> Viewport -> Axes3D -> [Primitive] renderAxes3DWith f = renderAxes3DWithLabels f ("x", "y", "z") --- | Phase 24 A8: 軸名を任意指定する版 ('renderAxes3DWith' = @("x","y","z")@)。+-- | [日本語]: 軸名を任意指定する版 ('renderAxes3DWith' = @("x","y","z")@)。+--   [English]: The variant that takes arbitrary axis names ('renderAxes3DWith'+--   is this with @("x","y","z")@). renderAxes3DWithLabels :: (Point3 -> Point3) -> (Text, Text, Text)                        -> Camera3D -> Projection3D -> Viewport -> Axes3D -> [Primitive] renderAxes3DWithLabels f (xName, yName, zName) cam proj vp ax =@@ -234,31 +272,50 @@   in cubeLines <> xTickPrims <> yTickPrims <> zTickPrims <> axisNames  -- ===========================================================================--- 壁面 pane + gridline (Phase 25 A6 = G5)+-- 壁面 pane + gridline -- =========================================================================== --- | Phase 25 A6: 壁面 pane (= mplot3d の背面 3 壁) のスタイル。--- pane = 薄灰の塗り面、 gridline = 壁面に引く tick 格子線 (mplot3d 既定は白)。+-- | [日本語]: 壁面 pane (= mplot3d の背面 3 壁) のスタイル。+--   pane = 薄灰の塗り面、 gridline = 壁面に引く tick 格子線 (mplot3d 既定は白)。+--   [English]: The style for a wall pane (mplot3d's 3 back walls). Pane =+--   the light-gray filled face; gridline = the tick gridlines drawn on the+--   wall (mplot3d's default is white). data PaneStyle3D = PaneStyle3D-  { paneFill    :: !Text    -- ^ 壁面塗り色-  , paneOpacity :: !Double  -- ^ 塗り不透明度 (0..1)-  , paneGrid    :: !Text    -- ^ 格子線色+  { paneFill    :: !Text    -- ^ [日本語]: 壁面塗り色+                              --   [English]: The wall's fill color.+  , paneOpacity :: !Double  -- ^ [日本語]: 塗り不透明度 (0..1)+                              --   [English]: The fill opacity (0..1).+  , paneGrid    :: !Text    -- ^ [日本語]: 格子線色+                              --   [English]: The gridline color.   } deriving (Show, Eq, Generic) instance ToJSON   PaneStyle3D instance FromJSON PaneStyle3D --- | mplot3d 風 default (薄灰 pane + 白格子線)。+-- | [日本語]: mplot3d 風 default (薄灰 pane + 白格子線)。+--   [English]: The mplot3d-style default (a light-gray pane with white+--   gridlines). defaultPaneStyle3D :: PaneStyle3D defaultPaneStyle3D = PaneStyle3D "#eaeaea" 1.0 "#ffffff" --- | Phase 25 A6: 3 つの「奥壁」 を薄灰 pane で塗り、 各壁に tick 格子線を引く--- (mplot3d 標準の axes pane)。 出力は @pane 塗り → gridline@ の順なので、--- 'renderAxes3DWithLabels' (cube wireframe + tick) の **前** に置けば最背面に--- なる (= データ・wireframe が pane の手前に来る)。+-- | [日本語]: 3 つの「奥壁」 を薄灰 pane で塗り、 各壁に tick 格子線を引く+--   (mplot3d 標準の axes pane)。 出力は @pane 塗り → gridline@ の順なので、+--   'renderAxes3DWithLabels' (cube wireframe + tick) の __前__ に置けば最背面に+--   なる (= データ・wireframe が pane の手前に来る)。 ----- 奥壁判定: 各軸の対向 2 面のうち、 面中心の投影 depth ('projDepth'、 +1 が奥)--- が大きい方を奥壁に採る。 視点回転に追従する。 @f@ は 'renderAxes3DWith' と--- 同じ正規化変換 (データ bbox → [-1,1]^3)。+--   奥壁判定: 各軸の対向 2 面のうち、 面中心の投影 depth ('projDepth'、 +1 が奥)+--   が大きい方を奥壁に採る。 視点回転に追従する。 @f@ は 'renderAxes3DWith' と+--   同じ正規化変換 (データ bbox → [-1,1]^3)。+--   [English]: Fills the 3 "back walls" with a light-gray pane and draws+--   tick gridlines on each wall (mplot3d's standard axes pane). The output+--   goes pane fill first, then gridlines, so placing this call __before__+--   'renderAxes3DWithLabels' (the cube wireframe plus ticks) puts it at the+--   very back (data and the wireframe end up drawn in front of the pane).+--+--   Back-wall determination: for each axis's pair of opposing faces, the+--   one with the larger projected depth at its center ('projDepth', where+--   +1 is farthest) is chosen as the back wall — this tracks camera+--   rotation. @f@ is the same normalization transform (data bbox to+--   @[-1,1]^3@) as 'renderAxes3DWith'. renderAxes3DPanes :: (Point3 -> Point3) -> PaneStyle3D                   -> Camera3D -> Projection3D -> Viewport -> Axes3D -> [Primitive] renderAxes3DPanes f sty cam proj vp ax =@@ -310,9 +367,14 @@    in [xPane, yPane, zPane] <> xGrid <> yGrid <> zGrid --- | 数値を短く整形 (= 小数 1 桁、 末尾 0 と . を除去)。 ★末尾 0 除去は **小数点が--- ある場合のみ** (整数の末尾 0 を削ると 10→1・100→1 になるバグを Phase 25 A8 で--- 修正。 log tick の 10/100/1000 で顕在化)。+-- | [日本語]: 数値を短く整形 (= 小数 1 桁、 末尾 0 と . を除去)。 ★末尾 0 除去は+--   __小数点がある場合のみ__ (整数の末尾 0 を削ると 10→1・100→1 になるバグを+--   修正済。 log tick の 10/100/1000 で顕在化)。+--   [English]: Formats a number compactly (1 decimal place, trailing zeros+--   and the decimal point stripped). Trailing-zero stripping only applies+--   __when there is a decimal point__ (stripping trailing zeros from an+--   integer used to turn 10 into 1 and 100 into 1 — a bug now fixed; it+--   surfaced with log ticks like 10/100/1000). formatNum :: Double -> Text formatNum x =   let s = T.pack (show (fromIntegral (round (x * 10) :: Int) / 10.0 :: Double))
src/Graphics/Hgg/ThreeD/Bar.hs view
@@ -1,23 +1,43 @@ -- | -- Module      : Graphics.Hgg.ThreeD.Bar--- Description : 3D bar (直方体 / stick) + 誤差棒 (Phase 25 A5)+-- Description : 3D bar charts (cuboid / stick) with error bars -- Copyright   : (c) 2026 Aelysce Project (Toshiaki Honda) -- License     : BSD-3-Clause ----- DoE / 実験データ定番の 3D 棒グラフ。 各 (x, y) 位置に底面 (base z) から高さ--- (top z) までの棒を立てる。 2 スタイル:+-- [日本語]: DoE / 実験データ定番の 3D 棒グラフ。 各 (x, y) 位置に底面 (base z)+--   から高さ (top z) までの棒を立てる。 2 スタイル: -----   * 'BarCuboid' — 直方体 (top + 4 側面の 5 quad)。 surface 同様 painter's の---     depth 統合 ('barFacesDepth') に乗せ、 scatter/surface と層横断ソートされる。---   * 'BarStick'  — 太い縦線 1 本 (面なし・軽量)。 line/wireframe 同様 depth 統合---     対象外で前面に描く ('renderBarSticks')。+--     * 'BarCuboid' — 直方体 (top + 4 側面の 5 quad)。 surface 同様 painter's の+--       depth 統合 ('barFacesDepth') に乗せ、 scatter/surface と層横断ソートされる。+--     * 'BarStick'  — 太い縦線 1 本 (面なし・軽量)。 line/wireframe 同様 depth 統合+--       対象外で前面に描く ('renderBarSticks')。 ----- 誤差棒 ('renderErrorBars3D') は棒/点の頂点に z 方向 ±err の縦線 + 端キャップ。--- bar・scatter どちらの layer にも付けられる (層の per-point err 列で駆動)。+--   誤差棒 ('renderErrorBars3D') は棒/点の頂点に z 方向 ±err の縦線 + 端キャップ。+--   bar・scatter どちらの layer にも付けられる (層の per-point err 列で駆動)。 ----- ⚠ 入力 ('Bar3D' の点・base・half-width・err) はすべて**正規化済**座標--- ([-1,1]^3・z-aspect 適用後) を渡す前提。 正規化は 'Easy.normLayer3D' /--- 'scaleZLayer' が担い、 本 module は投影 + 幾何のみ。+--   ⚠ 入力 ('Bar3D' の点・base・half-width・err) はすべて__正規化済__座標+--   ([-1,1]^3・z-aspect 適用後) を渡す前提。 正規化は 'Easy.normLayer3D' /+--   @scaleZLayer@ が担い、 本 module は投影 + 幾何のみ。+-- [English]: A 3D bar chart, a DoE / experimental-data staple. Each (x, y)+--   position gets a bar from a base z to a top z. Two styles:+--+--     * 'BarCuboid' — a cuboid (top plus 4 side quads, 5 total). Like a+--       surface, it joins the painter's-algorithm depth integration+--       ('barFacesDepth') and is sorted across layers together with+--       scatter / surface.+--     * 'BarStick'  — a single thick vertical line (no faces, lightweight).+--       Like line / wireframe, it stays outside depth integration and is+--       drawn in front ('renderBarSticks').+--+--   Error bars ('renderErrorBars3D') draw a z-direction ±err vertical line+--   plus end caps at each bar's or point's vertex; they can be attached to+--   either a bar or scatter layer (driven by the layer's per-point err+--   column).+--+--   ⚠ All inputs (the points, base, half-width, err of 'Bar3D') are assumed to be+--   __already normalized__ coordinates (@[-1,1]^3@, after z-aspect is+--   applied). Normalization is handled by 'Easy.normLayer3D' /+--   @scaleZLayer@; this module only does projection and geometry. {-# LANGUAGE DeriveGeneric     #-} {-# LANGUAGE OverloadedStrings #-} module Graphics.Hgg.ThreeD.Bar@@ -43,28 +63,40 @@                                                   project3D) import           Graphics.Hgg.ThreeD.Types --- | 棒のスタイル。+-- | [日本語]: 棒のスタイル。+--   [English]: The bar's style. data BarStyle3D-  = BarCuboid   -- ^ 直方体 (top + 4 側面・depth 統合)-  | BarStick    -- ^ 太い縦線 (面なし・前面描画)+  = BarCuboid   -- ^ [日本語]: 直方体 (top + 4 側面・depth 統合)+                --   [English]: A cuboid (top plus 4 sides; joins depth integration).+  | BarStick    -- ^ [日本語]: 太い縦線 (面なし・前面描画)+                --   [English]: A thick vertical line (no faces; drawn in front).   deriving (Show, Eq, Generic) instance ToJSON   BarStyle3D instance FromJSON BarStyle3D --- | 3D bar 1 series 分の設定 (= 全点共通スタイル・点ごとに棒を立てる)。+-- | [日本語]: 3D bar 1 series 分の設定 (= 全点共通スタイル・点ごとに棒を立てる)。+--   [English]: The configuration for one series of a 3D bar chart (a shared+--   style, with one bar per point). data Bar3D = Bar3D-  { br3Tops   :: ![Point3]    -- ^ 各棒の頂点 (x, y, 高さ z)・正規化済-  , br3BaseZ  :: !Double      -- ^ 底面 z (正規化済・通常 data z=0 の正規化値)-  , br3HalfW  :: !Double      -- ^ footprint 半幅 (正規化空間・x/y 共通)+  { br3Tops   :: ![Point3]    -- ^ [日本語]: 各棒の頂点 (x, y, 高さ z)・正規化済+                                --   [English]: Each bar's top vertex (x, y, height z), already normalized.+  , br3BaseZ  :: !Double      -- ^ [日本語]: 底面 z (正規化済・通常 data z=0 の正規化値)+                                --   [English]: The base z (already normalized; usually the normalized value of data z=0).+  , br3HalfW  :: !Double      -- ^ [日本語]: footprint 半幅 (正規化空間・x/y 共通)+                                --   [English]: The footprint half-width (in normalized space; shared by x/y).   , br3Style  :: !BarStyle3D   , br3Color  :: !Text-  , br3Alpha  :: !Double      -- ^ 面/線の不透明度 (0..1)-  , br3Width  :: !Double      -- ^ stick 線幅 px ('BarStick' 時)+  , br3Alpha  :: !Double      -- ^ [日本語]: 面/線の不透明度 (0..1)+                                --   [English]: The face / line opacity (0..1).+  , br3Width  :: !Double      -- ^ [日本語]: stick 線幅 px ('BarStick' 時)+                                --   [English]: The stick line width in px (used with 'BarStick').   } deriving (Show, Eq, Generic) instance ToJSON   Bar3D instance FromJSON Bar3D --- | default: 青、 base 0、 半幅 0.04、 直方体、 alpha 1、 stick 幅 6。+-- | [日本語]: default: 青、 base 0、 半幅 0.04、 直方体、 alpha 1、 stick 幅 6。+--   [English]: The default: blue, base 0, half-width 0.04, cuboid, alpha 1,+--   stick width 6. defaultBar3D :: [Point3] -> Bar3D defaultBar3D tops = Bar3D   { br3Tops  = tops@@ -80,9 +112,14 @@ -- 直方体 face の depth 列 (= surfaceFacesDepth と同型・大域ソートに混ぜる) -- =========================================================================== --- | 各棒を直方体の 5 quad (top + 4 側面・底面は隠れるので省く) に展開し、--- @(投影 depth, PPath)@ で**未ソート**返す。 'BarStick' 時は @[]@ (= 'renderBarSticks'--- が描く)。 簡易 Lambert shading (surface と同じ light) で立体感を付ける。+-- | [日本語]: 各棒を直方体の 5 quad (top + 4 側面・底面は隠れるので省く) に展開し、+--   @(投影 depth, PPath)@ で__未ソート__返す。 'BarStick' 時は @[]@ (= 'renderBarSticks'+--   が描く)。 簡易 Lambert shading (surface と同じ light) で立体感を付ける。+--   [English]: Expands each bar into the cuboid's 5 quads (top plus 4+--   sides; the bottom is hidden and omitted), returning+--   @(projected depth, PPath)@ pairs __unsorted__. Returns @[]@ for+--   'BarStick' (drawn instead by 'renderBarSticks'). Adds a sense of solidity+--   via simple Lambert shading (the same light as surfaces). barFacesDepth   :: Camera3D -> Projection3D -> Viewport   -> Bar3D -> [(Double, Primitive)]@@ -128,7 +165,9 @@ -- stick スタイル (太い縦線・前面描画) -- =========================================================================== --- | 'BarStick' 時、 各棒を底面→頂点の太い縦線で描く。 'BarCuboid' 時は @[]@。+-- | [日本語]: 'BarStick' 時、 各棒を底面→頂点の太い縦線で描く。 'BarCuboid' 時は @[]@。+--   [English]: For 'BarStick', draws each bar as a thick vertical line from+--   base to top vertex. Returns @[]@ for 'BarCuboid'. renderBarSticks   :: Camera3D -> Projection3D -> Viewport   -> Bar3D -> [Primitive]@@ -145,17 +184,25 @@                (solid (br3Color br) (br3Width br))  -- ===========================================================================--- stem (Phase 26 A4・3D lollipop = 細い垂線 + 先端マーカー・前面描画)+-- stem (3D lollipop = 細い垂線 + 先端マーカー・前面描画) -- =========================================================================== --- | Phase 26 A4: 各点を底面 ('br3BaseZ') → 先端の細い縦線 + 先端の円マーカーで--- 描く (3D lollipop)。 'renderBarSticks' (太線・マーカー無し) と違い stem は--- 細線 + マーカー。 線色/幅/alpha は 'Bar3D' から、 マーカー半径は引数 @markerR@--- (px)。 depth 統合外の前面 overlay。 先端の depth cue で僅かに半径を変える--- (scatter と同じ・近側が大きい)。+-- | [日本語]: 各点を底面 ('br3BaseZ') → 先端の細い縦線 + 先端の円マーカーで+--   描く (3D lollipop)。 'renderBarSticks' (太線・マーカー無し) と違い stem は+--   細線 + マーカー。 線色/幅/alpha は 'Bar3D' から、 マーカー半径は引数 @markerR@+--   (px)。 depth 統合外の前面 overlay。 先端の depth cue で僅かに半径を変える+--   (scatter と同じ・近側が大きい)。+--   [English]: Draws each point as a thin vertical line from the base+--   ('br3BaseZ') to the top, plus a circular marker at the top (a 3D+--   lollipop). Unlike 'renderBarSticks' (a thick line with no marker), a+--   stem is a thin line plus marker. Line color / width / alpha come from+--   'Bar3D'; the marker radius is the @markerR@ argument (px). Drawn as a+--   front overlay outside depth integration. The tip's radius shifts+--   slightly with a depth cue (same as scatter — nearer points are larger). renderStems3D   :: Camera3D -> Projection3D -> Viewport-  -> Bar3D -> Double          -- ^ マーカー基本半径 px+  -> Bar3D -> Double          -- ^ [日本語]: マーカー基本半径 px+                                --   [English]: The marker's base radius, in px.   -> [Primitive] renderStems3D cam proj vp br markerR =   concatMap stem (br3Tops br)@@ -180,13 +227,20 @@ -- 誤差棒 (z 方向 ±err・縦線 + 端キャップ) -- =========================================================================== --- | 各頂点 @(x,y,z)@ に z 方向 ±err の縦線 + 上下端の水平キャップ (画面空間の--- 短い横線) を描く。 err は正規化済 z 量 (= 'normLayer3D' で換算済)。 bar/scatter--- どちらの頂点列にも使える。 err <= 0 の点はスキップ。 線色 'col'・幅 'lw'。+-- | [日本語]: 各頂点 @(x,y,z)@ に z 方向 ±err の縦線 + 上下端の水平キャップ (画面空間の+--   短い横線) を描く。 err は正規化済 z 量 (= 'Graphics.Hgg.ThreeD.Easy.normLayer3D' で換算済)。 bar/scatter+--   どちらの頂点列にも使える。 err <= 0 の点はスキップ。 線色 @col@・幅 @lw@。+--   [English]: Draws a z-direction ±err vertical line at each vertex+--   @(x,y,z)@, plus horizontal end caps (short screen-space lines) at the+--   top and bottom. err is an already-normalized z quantity (converted via+--   'Graphics.Hgg.ThreeD.Easy.normLayer3D'). Works on either a bar or scatter vertex list. Points+--   with err <= 0 are skipped. Line color @col@, width @lw@. renderErrorBars3D   :: Camera3D -> Projection3D -> Viewport-  -> Text -> Double          -- ^ 色・線幅 px-  -> [(Point3, Double)]      -- ^ (頂点, 正規化 err)+  -> Text -> Double          -- ^ [日本語]: 色・線幅 px+                                --   [English]: The color and line width, in px.+  -> [(Point3, Double)]      -- ^ [日本語]: (頂点, 正規化 err)+                                --   [English]: (vertex, normalized err) pairs.   -> [Primitive] renderErrorBars3D cam proj vp col lw pes =   concatMap whisker pes
src/Graphics/Hgg/ThreeD/Bound.hs view
@@ -1,18 +1,30 @@ -- | -- Module      : Graphics.Hgg.ThreeD.Bound--- Description : 3D の df バインド (Phase 24 A6 = 2D `df |>> spec` の 3D 対応)+-- Description : 3D dataframe binding (the 3D counterpart of 2D's `df |>> spec`) -- Copyright   : (c) 2026 Aelysce Project (Toshiaki Honda) -- License     : BSD-3-Clause ----- 2D と同じ書き味で 3D に列名バインドを通す:+-- [日本語]: 2D と同じ書き味で 3D に列名バインドを通す: -- -- > df |>> (layer3D (scatter3D "x" "y" "z" <> colormap3D) <> title3D "...") -- >   :: BoundPlot3D -- > saveSVG3DBound "out.svg" bound ----- 'BindableSpec' (hgg-frame) の 'VisualSpec3D' instance を本 module--- で与える (型の定義 package 側 = 非 orphan)。 検証は 2D と同方針で--- **バインド時に値として** 'PlotDiagnostic' に格納し、 例外は投げない。+--   'BindableSpec' (hgg-frame) の 'VisualSpec3D' instance を本 module+--   で与える (型の定義 package 側 = 非 orphan)。 検証は 2D と同方針で+--   __バインド時に値として__ 'PlotDiagnostic' に格納し、 例外は投げない。+-- [English]: Threads column-name binding through to 3D with the same feel+--   as 2D:+--+-- > df |>> (layer3D (scatter3D "x" "y" "z" <> colormap3D) <> title3D "...")+-- >   :: BoundPlot3D+-- > saveSVG3DBound "out.svg" bound+--+--   This module supplies the 'VisualSpec3D' instance of 'BindableSpec'+--   (hgg-frame) — the package that defines the type, so it is not+--   an orphan. Validation follows the same policy as 2D: it is stored+--   __as a value in 'PlotDiagnostic' at bind time__, and no exception is+--   ever thrown. {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TypeFamilies      #-} module Graphics.Hgg.ThreeD.Bound@@ -41,7 +53,9 @@                                                  lyr3EncX, lyr3EncY, lyr3EncZ,                                                  lyr3TextBy, resolveSpec3D) --- | df バインド済の 3D plot (2D 'Graphics.Hgg.Frame.BoundPlot' の 3D 版)。+-- | [日本語]: df バインド済の 3D plot (2D 'Graphics.Hgg.Frame.BoundPlot' の 3D 版)。+--   [English]: A dataframe-bound 3D plot (the 3D counterpart of the 2D+--   'Graphics.Hgg.Frame.BoundPlot'). data BoundPlot3D = BoundPlot3D   { bp3Resolver    :: Resolver   , bp3Spec        :: VisualSpec3D@@ -54,7 +68,9 @@     BoundPlot3D (toResolver df) spec                 (emptyDfDiagnostics df ++ checkColumns (columnNames df) spec) --- | spec 中の 'ColByName' を df の列名と突合する (編集距離 suggestion 付き)。+-- | [日本語]: spec 中の 'ColByName' を df の列名と突合する (編集距離 suggestion 付き)。+--   [English]: Matches the 'ColByName' references in a spec against the+--   dataframe's column names (with edit-distance suggestions). checkColumns :: [Text] -> VisualSpec3D -> [PlotDiagnostic] checkColumns known spec =   [ PlotError (ColumnNotFound nm (suggest known nm)) (DiagnosticContext (Just i) Nothing)@@ -69,20 +85,30 @@   byName (Just (ColByName nm)) = Just nm   byName _                     = Nothing --- | バインド済 3D plot を SVG 保存 (列参照を解決してから 'saveSVG3D')。+-- | [日本語]: バインド済 3D plot を SVG 保存 (列参照を解決してから 'saveSVG3D')。+--   [English]: Saves a bound 3D plot as SVG (resolves column references,+--   then calls 'saveSVG3D'). saveSVG3DBound :: FilePath -> BoundPlot3D -> IO () saveSVG3DBound path b = saveSVG3D path (resolveSpec3D (bp3Resolver b) (bp3Spec b)) --- | バインド済 3D plot を **WebGL self-contained HTML** として保存+-- | [日本語]: バインド済 3D plot を __WebGL self-contained HTML__ として保存 --   (= 'saveSVG3DBound' の interactive 版・列参照と resolve 産物を解決してから---   'saveHTML3D')。 df 連携 3D の browser 経路欠落を埋める (Phase 27 A2)。+--   'saveHTML3D')。 df 連携 3D の browser 経路欠落を埋める。+--   [English]: Saves a bound 3D plot as __self-contained WebGL HTML__ (the+--   interactive counterpart of 'saveSVG3DBound' — resolves column references+--   and the resolved spec, then calls 'saveHTML3D'). Fills the gap that used+--   to leave dataframe-bound 3D plots without a browser rendering path. saveHTML3DBound :: FilePath -> BoundPlot3D -> IO () saveHTML3DBound path b = saveHTML3D path (resolveSpec3D (bp3Resolver b) (bp3Spec b)) --- | バインド済 3D plot を **ブラウザで interactive 表示** ('showBrowser' の df 連携版)。+-- | [日本語]: バインド済 3D plot を __ブラウザで interactive 表示__ ('showBrowser' の df 連携版)。+--   [English]: Displays a bound 3D plot __interactively in the browser__+--   (the dataframe-bound counterpart of 'showBrowser'). showBrowser3DBound :: BoundPlot3D -> IO () showBrowser3DBound b = showBrowser (resolveSpec3D (bp3Resolver b) (bp3Spec b)) --- | (Resolver, 解決済み spec) を取り出す raw 経路 (2D 'unBound' 同型)。+-- | [日本語]: (Resolver, 解決済み spec) を取り出す raw 経路 (2D 'Graphics.Hgg.Frame.unBound' 同型)。+--   [English]: The raw escape hatch that extracts (Resolver, resolved spec)+--   (mirroring the 2D 'Graphics.Hgg.Frame.unBound'). unBound3D :: BoundPlot3D -> (Resolver, VisualSpec3D) unBound3D b = (bp3Resolver b, resolveSpec3D (bp3Resolver b) (bp3Spec b))
src/Graphics/Hgg/ThreeD/Browser.hs view
@@ -1,6 +1,6 @@ -- | -- Module      : Graphics.Hgg.ThreeD.Browser--- Description : HS-side browser display driver (Phase 5 A5)+-- Description : HS-side browser display driver -- Copyright   : (c) 2026 Aelysce Project (Toshiaki Honda) -- License     : BSD-3-Clause --@@ -10,16 +10,32 @@ -- main = showBrowser $ purePlot3D <> layer3D (scatter3D pts) <> camera (defaultCameraZUp 3) -- @ ----- 中身: spec を aeson で JSON 化、 PS WebGL bundle (= data-files) と inline 結合した--- self-contained HTML を tmp に出力、 OS 別 browser-open コマンド (xdg-open / open / start) で起動。+-- [日本語]: 中身: spec を aeson で JSON 化、 PS WebGL bundle (= data-files) と inline 結合した+--   self-contained HTML を tmp に出力、 OS 別 browser-open コマンド (xdg-open / open / start) で起動。 ----- 設計判断 (= phase-5 計画 md §2.4):+--   設計判断 (= phase-5 計画 md §2.4): -----   * **bundle embed 方式**: cabal data-files 採用 (= dev iteration 速さ重視、---     PS 側更新時 HS 再 build 不要)。 将来 TH 'embedFile' 切替時は 'getBundleJS'---     の中身だけ差替えで済む。 詳細 → @design\/bundle-embed-choice.md@---   * **3 つの出力経路**: 'showBrowser' (= tmp + open)、 'saveHTML3D' (= 配布用、 単一 HTML)、---     'Graphics.Hgg.ThreeD.Easy.saveSVG3D' (= 静的 SVG、 Phase 3 CPU projection 経路)+--     * __bundle embed 方式__: cabal data-files 採用 (= dev iteration 速さ重視、+--       PS 側更新時 HS 再 build 不要)。 将来 TH @embedFile@ 切替時は 'getBundleJS'+--       の中身だけ差替えで済む。 詳細 → @design\/bundle-embed-choice.md@+--     * __3 つの出力経路__: 'showBrowser' (= tmp + open)、 'saveHTML3D' (= 配布用、 単一 HTML)、+--       'Graphics.Hgg.ThreeD.Easy.saveSVG3D' (= 静的 SVG、 CPU projection 経路)+-- [English]: Internals: JSON-encodes the spec via aeson, writes a+--   self-contained HTML file to tmp that inline-embeds the PS WebGL bundle+--   (from data-files), then launches it with the OS-specific browser-open+--   command (xdg-open / open / start).+--+--   Design decisions (see phase-5 plan md §2.4):+--+--     * __The bundle-embed approach__: uses cabal data-files (prioritizing+--       dev iteration speed — no HS rebuild is needed when the PS side+--       changes). If this ever switches to TH @embedFile@, only the body of+--       'getBundleJS' needs to change. Details:+--       @design\/bundle-embed-choice.md@+--     * __Three output paths__: 'showBrowser' (tmp file plus open),+--       'saveHTML3D' (a single distributable HTML file), and+--       'Graphics.Hgg.ThreeD.Easy.saveSVG3D' (a static SVG, via the CPU+--       projection path) {-# LANGUAGE OverloadedStrings #-} module Graphics.Hgg.ThreeD.Browser   ( -- * 主要 API@@ -45,11 +61,18 @@ -- bundle 取得 (= 絶縁レイヤ、 後で TH に swap 可能) -- =========================================================================== --- | PS WebGL bundle (= data/webgl-spec.js) を ByteString で取得。+-- | [日本語]: PS WebGL bundle (= data/webgl-spec.js) を ByteString で取得。 ----- 現在は @cabal data-files@ 経由 ('Paths_hgg_3d.getDataFileName')。--- 将来 TH 'embedFile' に切替時はこの 1 関数のみ差替えれば済むよう設計--- (= 切替手順は @design\/bundle-embed-choice.md@)。+--   現在は @cabal data-files@ 経由 ('Paths_hgg_3d.getDataFileName')。+--   将来 TH @embedFile@ に切替時はこの 1 関数のみ差替えれば済むよう設計+--   (= 切替手順は @design\/bundle-embed-choice.md@)。+--   [English]: Fetches the PS WebGL bundle (data/webgl-spec.js) as a+--   ByteString.+--+--   Currently goes through @cabal data-files@+--   ('Paths_hgg_3d.getDataFileName'). Designed so that a future+--   switch to TH @embedFile@ only requires replacing this one function+--   (the switch procedure is in @design\/bundle-embed-choice.md@). getBundleJS :: IO BS.ByteString getBundleJS = do   path <- getDataFileName "data/webgl-spec.js"@@ -59,10 +82,15 @@ -- showBrowser / saveHTML3D -- =========================================================================== --- | spec を **ブラウザで interactive 表示**。 tmp HTML 生成 + OS 別 browser-open。+-- | [日本語]: spec を __ブラウザで interactive 表示__。 tmp HTML 生成 + OS 別 browser-open。 ----- WebGL2 backend で描画、 mouse drag で camera orbit、 wheel で zoom、--- 右 drag で pan。 操作仕様は Phase 4 demo と同一。+--   WebGL2 backend で描画、 mouse drag で camera orbit、 wheel で zoom、+--   右 drag で pan。 操作仕様は demo と同一。+--   [English]: Displays a spec __interactively in the browser__. Generates a+--   tmp HTML file, then opens it with the OS-specific browser-open command.+--+--   Renders via the WebGL2 backend: mouse drag orbits the camera, the wheel+--   zooms, and right-drag pans. The controls match the demo. showBrowser :: VisualSpec3D -> IO () showBrowser spec = do   tmpDir <- getTemporaryDirectory@@ -70,9 +98,14 @@   saveHTML3D path spec   openInBrowser path --- | spec を **self-contained HTML として保存** (= 配布用、 bundle inline 埋込)。+-- | [日本語]: spec を __self-contained HTML として保存__ (= 配布用、 bundle inline 埋込)。 ----- 出力ファイルは外部依存無し、 ブラウザで直接開ける。+--   出力ファイルは外部依存無し、 ブラウザで直接開ける。+--   [English]: Saves a spec as __self-contained HTML__ (for distribution,+--   with the bundle inline-embedded).+--+--   The output file has no external dependencies and opens directly in a+--   browser. saveHTML3D :: FilePath -> VisualSpec3D -> IO () saveHTML3D path spec = do   bundleJS <- getBundleJS@@ -84,9 +117,13 @@ -- HTML 生成 -- =========================================================================== --- | bundle JS + spec JSON を 1 HTML に埋込。 ESM ではなく通常 script 経路--- (= spago bundle 出力は IIFE、 ESM import すると export named 'main' 不在で失敗、---    Phase 4 で確認済の罠)。+-- | [日本語]: bundle JS + spec JSON を 1 HTML に埋込。 ESM ではなく通常 script 経路+--   (= spago bundle 出力は IIFE、 ESM import すると export named @main@ 不在で失敗、+--      確認済の罠)。+--   [English]: Embeds the bundle JS plus spec JSON into a single HTML file.+--   Uses a plain @<script>@ tag rather than ESM (spago's bundle output is+--   an IIFE; ESM import fails because there is no named export @main@ — a+--   confirmed gotcha). mkHTML :: BS.ByteString -> LBS.ByteString -> LBS.ByteString mkHTML bundleJS specJSON = LBS.concat   [ "<!DOCTYPE html>\n"@@ -135,10 +172,16 @@ -- OS 別 browser-open -- =========================================================================== --- | OS 検出して xdg-open (Linux) / open (macOS) / start (Windows) を呼ぶ。+-- | [日本語]: OS 検出して xdg-open (Linux) / open (macOS) / start (Windows) を呼ぶ。 ----- WSL は @os == \"linux\"@ で xdg-open 経路。 wslview などインストール済なら動作。--- 失敗時は path を stdout に出すので手動で開ける。+--   WSL は @os == \"linux\"@ で xdg-open 経路。 wslview などインストール済なら動作。+--   失敗時は path を stdout に出すので手動で開ける。+--   [English]: Detects the OS and calls xdg-open (Linux), open (macOS), or+--   start (Windows).+--+--   WSL reports @os == \"linux\"@, so it goes through the xdg-open path;+--   this works if @wslview@ or similar is installed. On failure, the path+--   is printed to stdout so it can be opened manually. openInBrowser :: FilePath -> IO () openInBrowser path = case os of   "linux"   -> safeCall ("xdg-open " <> quote path)
src/Graphics/Hgg/ThreeD/Delaunay.hs view
@@ -1,17 +1,29 @@ -- | -- Module      : Graphics.Hgg.ThreeD.Delaunay--- Description : 2D Delaunay 三角分割 (Phase 26 A5・trisurf 用)+-- Description : 2D Delaunay triangulation (for trisurf) -- Copyright   : (c) 2026 Aelysce Project (Toshiaki Honda) -- License     : BSD-3-Clause ----- 不規則 (非 grid) 点群を 'trisurf' で曲面化するための **純 Haskell** 2D Delaunay--- 三角分割。 外部ライブラリ / バイナリ依存ゼロ (= spec §10.7・graphviz parity と--- 同じ「自己完結」 方針)。 アルゴリズムは Bowyer-Watson の素朴版 (O(n²) 程度・--- trisurf の点数は数十〜低数百なので十分)。+-- [日本語]: 不規則 (非 grid) 点群を 'Graphics.Hgg.ThreeD.Spec.trisurf' で曲面化するための __純 Haskell__+--   2D Delaunay 三角分割。 外部ライブラリ / バイナリ依存ゼロ (= spec §10.7・+--   graphviz parity と同じ「自己完結」 方針)。 アルゴリズムは Bowyer-Watson の+--   素朴版 (O(n²) 程度・trisurf の点数は数十〜低数百なので十分)。 ----- 入力は (x,y) 列。 z は呼び出し側で分割後に各頂点へ持ち上げる (= trisurf は--- (x,y) 平面の 2D 分割だけで足り、 3D 四面体分割は不要)。 退化 (共円・共線) は--- incircle 述語の符号で素朴に扱う (demo 用途では実害なし)。+--   入力は (x,y) 列。 z は呼び出し側で分割後に各頂点へ持ち上げる (= trisurf は+--   (x,y) 平面の 2D 分割だけで足り、 3D 四面体分割は不要)。 退化 (共円・共線) は+--   incircle 述語の符号で素朴に扱う (demo 用途では実害なし)。+-- [English]: A __pure Haskell__ 2D Delaunay triangulation for turning+--   irregular (non-grid) point clouds into a surface via 'Graphics.Hgg.ThreeD.Spec.trisurf'. Zero+--   external library / binary dependencies (the same "self-contained"+--   policy as spec §10.7 / graphviz parity). The algorithm is a naive+--   Bowyer-Watson implementation (roughly O(n²), which is fine since+--   'Graphics.Hgg.ThreeD.Spec.trisurf' typically deals with tens to low hundreds of points).+--+--   The input is a list of (x,y) columns; z is lifted onto each vertex by+--   the caller after triangulation ('Graphics.Hgg.ThreeD.Spec.trisurf' only needs the 2D+--   triangulation of the (x,y) plane, not a 3D tetrahedralization).+--   Degenerate cases (co-circular or collinear points) are handled naively+--   via the sign of the incircle predicate (harmless for demo purposes). {-# LANGUAGE BangPatterns #-} module Graphics.Hgg.ThreeD.Delaunay   ( delaunay2D@@ -20,8 +32,12 @@ import           Data.List       (foldl') import qualified Data.Map.Strict as M --- | (x,y) 点列を 2D Delaunay 三角分割し、 三角形を元の点配列への index 三つ組+-- | [日本語]: (x,y) 点列を 2D Delaunay 三角分割し、 三角形を元の点配列への index 三つ組 --   @(i,j,k)@ で返す。 点が 3 未満 / 全点共線などで三角形が作れない時は @[]@。+--   [English]: Triangulates a list of (x,y) points via 2D Delaunay+--   triangulation, returning each triangle as an index triple @(i,j,k)@ into+--   the original point array. Returns @[]@ when fewer than 3 points are+--   given, or triangulation is impossible (e.g. all points collinear). delaunay2D :: [(Double, Double)] -> [(Int, Int, Int)] delaunay2D pts0   | n < 3     = []@@ -64,7 +80,9 @@      triEdges (a, b, c) = [(a, b), (b, c), (c, a)] --- | 点 d が三角形 (a,b,c) の外接円の内部か。 三角形の向き (CCW/CW) を符号で吸収。+-- | [日本語]: 点 d が三角形 (a,b,c) の外接円の内部か。 三角形の向き (CCW/CW) を符号で吸収。+--   [English]: Whether point d lies inside the circumcircle of triangle+--   (a,b,c). The triangle's winding order (CCW/CW) is absorbed via the sign. inCircle :: (Double, Double) -> (Double, Double) -> (Double, Double)          -> (Double, Double) -> Bool inCircle (ax, ay) (bx, by) (cx, cy) (dx, dy) =
src/Graphics/Hgg/ThreeD/Easy.hs view
@@ -1,35 +1,49 @@ -- | -- Module      : Graphics.Hgg.ThreeD.Easy--- Description : 3D 出力経路の薄い wrap (Phase 5 A6 saveSVG3D)+-- Description : A thin wrapper over the 3D output paths (saveSVG3D) -- Copyright   : (c) 2026 Aelysce Project (Toshiaki Honda) -- License     : BSD-3-Clause ----- 'VisualSpec3D' から各出力経路への dispatch helper。 3 経路:+-- [日本語]: 'VisualSpec3D' から各出力経路への dispatch helper。 3 経路: -----   * 'saveSVG3D'   ─ Phase 3 CPU projection (= 静的 SVG 出力、 本 module 実装)---   * 'saveHTML3D'  ─ Phase 4 WebGL bundle 埋込 HTML (= 'Graphics.Hgg.ThreeD.Browser')---   * 'showBrowser' ─ tmp HTML + xdg-open (= 'Graphics.Hgg.ThreeD.Browser')+--   * 'saveSVG3D'   ─ CPU projection (= 静的 SVG 出力、 本 module 実装)+--   * 'Graphics.Hgg.ThreeD.Browser.saveHTML3D'  ─ WebGL bundle 埋込 HTML (= 'Graphics.Hgg.ThreeD.Browser')+--   * 'Graphics.Hgg.ThreeD.Browser.showBrowser' ─ tmp HTML + xdg-open (= 'Graphics.Hgg.ThreeD.Browser') ----- 同 spec を 3 経路に出し分けられる。 用途で使い分け:+--   同 spec を 3 経路に出し分けられる。 用途で使い分け: -- --   * 印刷 / doc 埋込 → 'saveSVG3D'---   * 配布 / 単一 HTML 配布 → 'saveHTML3D'---   * 開発時 interactive 確認 → 'showBrowser'+--   * 配布 / 単一 HTML 配布 → 'Graphics.Hgg.ThreeD.Browser.saveHTML3D'+--   * 開発時 interactive 確認 → 'Graphics.Hgg.ThreeD.Browser.showBrowser'+-- [English]: Dispatch helpers from a 'VisualSpec3D' to each output path.+--   Three paths:+--+--   * 'saveSVG3D'   ─ CPU projection (static SVG output, implemented here)+--   * 'Graphics.Hgg.ThreeD.Browser.saveHTML3D'  ─ WebGL bundle embedded in HTML (see+--     'Graphics.Hgg.ThreeD.Browser')+--   * 'Graphics.Hgg.ThreeD.Browser.showBrowser' ─ a tmp HTML file plus xdg-open (see+--     'Graphics.Hgg.ThreeD.Browser')+--+--   The same spec can be routed to any of the three. Pick based on use case:+--+--   * printing / embedding in a doc → 'saveSVG3D'+--   * distribution as a single HTML file → 'Graphics.Hgg.ThreeD.Browser.saveHTML3D'+--   * interactive checking during development → 'Graphics.Hgg.ThreeD.Browser.showBrowser' {-# LANGUAGE OverloadedStrings #-} module Graphics.Hgg.ThreeD.Easy   ( saveSVG3D-    -- * PDF / PNG 出力 (Phase 24 A8 glue)+    -- * PDF / PNG 出力 (glue)   , savePDF3D   , savePNG3D   , spec3DToPrimitives-    -- * 群別タイル配置 (Phase 24 A7)+    -- * 群別タイル配置   , saveSVG3DFacet   , renderSpec3DInPanel-    -- * 正規化 pipeline (Phase 24 A3・test 用に公開)+    -- * 正規化 pipeline (test 用に公開)   , padAxes3D   , normPoint3D   , normLayer3D-    -- * 投影 contour (Phase 24 A5・test 用に公開)+    -- * 投影 contour (test 用に公開)   , renderProjectedContour3D   ) where @@ -95,24 +109,39 @@                                                     renderErrorBars3D) import           Graphics.Hgg.ThreeD.Types --- | 'VisualSpec3D' を Phase 3 CPU projection で SVG に保存。+-- | [日本語]: 'VisualSpec3D' を CPU projection で SVG に保存。 ----- defaults:+--   defaults: -- --   * camera     → 'defaultCameraZUp' 3 --   * projection → 'defaultPerspective' --   * axes       → 'autoAxes3D' (= layers の Point3 集合から bbox 算出) --   * width / height → 700 / 700 --   * title      → ""+--   [English]: Saves a 'VisualSpec3D' as SVG via CPU projection.+--+--   Defaults:+--+--   * camera     → 'defaultCameraZUp' 3+--   * projection → 'defaultPerspective'+--   * axes       → 'autoAxes3D' (bbox computed from the layers' Point3 sets)+--   * width / height → 700 / 700+--   * title      → "" saveSVG3D :: FilePath -> VisualSpec3D -> IO () saveSVG3D path spec =   let (w, h, title, prims) = spec3DToPrimitives spec   in savePrimitivesSVG path w h title prims --- | Phase 24 A8: 'VisualSpec3D' を (幅, 高さ, タイトル, [Primitive]) に落とす--- 共通核。 各 backend (SVG/PDF/PNG) の出力関数が共有する。 prims は--- 'renderSpec3DInPanel' 単一 panel (= キャンバス全域)。 title は別返し--- (SVG は backend が <text> 化・PDF/PNG は 'savePDF3D'/'savePNG3D' が PText 化)。+-- | [日本語]: 'VisualSpec3D' を (幅, 高さ, タイトル, [Primitive]) に落とす+--   共通核。 各 backend (SVG/PDF/PNG) の出力関数が共有する。 prims は+--   'renderSpec3DInPanel' 単一 panel (= キャンバス全域)。 title は別返し+--   (SVG は backend が <text> 化・PDF/PNG は 'savePDF3D'/'savePNG3D' が PText 化)。+--   [English]: The shared core that reduces a 'VisualSpec3D' to+--   (width, height, title, [Primitive]), shared by each backend's (SVG /+--   PDF / PNG) output function. prims come from a single+--   'renderSpec3DInPanel' panel (the whole canvas). title is returned+--   separately (the SVG backend turns it into @\<text\>@; PDF/PNG have+--   'savePDF3D' / 'savePNG3D' turn it into a @PText@). spec3DToPrimitives :: VisualSpec3D -> (Int, Int, Text, [Primitive]) spec3DToPrimitives spec =   let w     = fromMaybe 700 (getLast (vs3Width  spec))@@ -121,24 +150,35 @@       prims = renderSpec3DInPanel 0 0 (fromIntegral w) (fromIntegral h) spec   in (w, h, title, prims) --- | Phase 24 A8: 3D 図を PDF に保存 ('saveSVG3D' の PDF 版)。 [Primitive] を--- 既存 PDF backend ('savePrimitivesPDF') へ配線。 title は上部中央の PText に--- する (PDF backend は title 引数を持たないため)。 ⚠ PDF 標準フォントは--- Latin-1 のみ (日本語ラベルは 'savePNG3D')。+-- | [日本語]: 3D 図を PDF に保存 ('saveSVG3D' の PDF 版)。 [Primitive] を+--   既存 PDF backend ('savePrimitivesPDF') へ配線。 title は上部中央の PText に+--   する (PDF backend は title 引数を持たないため)。 ⚠ PDF 標準フォントは+--   Latin-1 のみ (日本語ラベルは 'savePNG3D')。+--   [English]: Saves a 3D figure as PDF (the PDF counterpart of+--   'saveSVG3D'). Wires @[Primitive]@ to the existing PDF backend+--   ('savePrimitivesPDF'). The title is turned into a top-centered 'PText'+--   (the PDF backend has no title argument). ⚠ PDF's standard fonts are+--   Latin-1 only (use 'savePNG3D' for Japanese labels). savePDF3D :: FilePath -> VisualSpec3D -> IO () savePDF3D path spec =   let (w, h, title, prims) = spec3DToPrimitives spec   in savePrimitivesPDF path w h (titlePrims w title <> prims) --- | Phase 24 A8: 3D 図を PNG に保存 ('saveSVG3D' の PNG 版・日本語ラベル可)。--- [Primitive] を既存 Rasterific backend ('savePrimitivesPNG') へ配線。+-- | [日本語]: 3D 図を PNG に保存 ('saveSVG3D' の PNG 版・日本語ラベル可)。+--   [Primitive] を既存 Rasterific backend ('savePrimitivesPNG') へ配線。+--   [English]: Saves a 3D figure as PNG (the PNG counterpart of+--   'saveSVG3D'; supports Japanese labels). Wires @[Primitive]@ to the+--   existing Rasterific backend ('savePrimitivesPNG'). savePNG3D :: FilePath -> VisualSpec3D -> IO () savePNG3D path spec =   let (w, h, title, prims) = spec3DToPrimitives spec   in savePrimitivesPNG defaultPNGConfig path w h (titlePrims w title <> prims) --- | タイトルを上部中央の PText に (空なら無し)。 SVG backend の title 描画--- (font-size 16・中央・y≈24) に概ね合わせる。+-- | [日本語]: タイトルを上部中央の PText に (空なら無し)。 SVG backend の title 描画+--   (font-size 16・中央・y≈24) に概ね合わせる。+--   [English]: Turns the title into a top-centered 'PText' (nothing if+--   empty). Roughly matches the SVG backend's title rendering (font-size+--   16, centered, y≈24). titlePrims :: Int -> Text -> [Primitive] titlePrims w title   | T.null title = []@@ -146,10 +186,16 @@       [ PText (Point (fromIntegral w / 2) 24) title               (TextStyle "#333333" 16 "sans-serif" AnchorMiddle 0 "normal" False) ] --- | Phase 24 A7: 'VisualSpec3D' を**任意のパネル矩形** @(px,py,pw,ph)@ 内に--- 描画する純粋核 ('saveSVG3D' と 'saveSVG3DFacet' が共有)。 軸 box・正規化--- pipeline・layer・colorbar をパネル局所座標で出す。 @(0,0,w,h)@ で呼ぶと--- 旧 'saveSVG3D' とビット同一 (margin 50・colorbar 右端)。+-- | [日本語]: 'VisualSpec3D' を__任意のパネル矩形__ @(px,py,pw,ph)@ 内に+--   描画する純粋核 ('saveSVG3D' と 'saveSVG3DFacet' が共有)。 軸 box・正規化+--   pipeline・layer・colorbar をパネル局所座標で出す。 @(0,0,w,h)@ で呼ぶと+--   旧 'saveSVG3D' とビット同一 (margin 50・colorbar 右端)。+--   [English]: The pure core that renders a 'VisualSpec3D' into+--   __an arbitrary panel rectangle__ @(px,py,pw,ph)@ (shared by 'saveSVG3D'+--   and 'saveSVG3DFacet'). Emits the axis box, normalization pipeline,+--   layers, and colorbar in panel-local coordinates. Calling it with+--   @(0,0,w,h)@ is bit-identical to the original 'saveSVG3D' (margin 50,+--   colorbar on the right edge). renderSpec3DInPanel :: Double -> Double -> Double -> Double -> VisualSpec3D -> [Primitive] renderSpec3DInPanel px py pw ph spec =   let layers = vs3Layers spec@@ -218,8 +264,12 @@        <> maybe [] (renderColorbar3D px py pw ph) cbInfo        <> renderLegend3D px py pw ph legendEntries --- | Phase 25 A2: 群色分けの離散凡例 (色チップ + ラベル) をパネル右端に縦並び。--- colorbar があればその左、 無ければ右端。 entries = (カテゴリ, 色) の初出順。+-- | [日本語]: 群色分けの離散凡例 (色チップ + ラベル) をパネル右端に縦並び。+--   colorbar があればその左、 無ければ右端。 entries = (カテゴリ, 色) の初出順。+--   [English]: Renders the discrete group-coloring legend (color chips plus+--   labels), stacked vertically along the panel's right edge. Sits left of+--   the colorbar if there is one, otherwise flush right. entries are+--   (category, color) pairs in first-occurrence order. renderLegend3D :: Double -> Double -> Double -> Double -> [(Text, Text)] -> [Primitive] renderLegend3D _  _  _  _  []      = [] renderLegend3D px py pw ph entries =@@ -236,8 +286,11 @@            , PText (Point (x0 + chip + 6) (y + chip - 2)) cat tsL ]   in concatMap row (zip [0 :: Int ..] entries) --- | Phase 24 A8: depth 統合対象 (surface 面・scatter 点) を @(depth, Primitive)@--- で返す。 line/wireframe/floor は対象外 ([])。+-- | [日本語]: depth 統合対象 (surface 面・scatter 点) を @(depth, Primitive)@+--   で返す。 line/wireframe/floor は対象外 ([])。+--   [English]: Returns the depth-integration targets (surface faces,+--   scatter points) as @(depth, Primitive)@ pairs. line / wireframe / floor+--   are excluded (@[]@). depthItemsOf :: Camera3D -> Projection3D -> Viewport -> Layer3D -> [(Double, Primitive)] depthItemsOf cam proj vp l = case getFirst (lyr3Kind l) of   Just M3Surface -> surfaceFacesDepth  cam proj vp (layerToSurface l)@@ -257,7 +310,9 @@     in trianglesFacesDepth cam proj vp col ecol shaded cmap alpha triPts   _              -> [] --- | Phase 24 A8: depth 統合対象外の layer (line/wireframe・bar stick) を描く。+-- | [日本語]: depth 統合対象外の layer (line/wireframe・bar stick) を描く。+--   [English]: Draws the layers that are excluded from depth integration+--   (line / wireframe, bar sticks). otherLayerPrims :: Camera3D -> Projection3D -> Viewport -> Layer3D -> [Primitive] otherLayerPrims cam proj vp l = case getFirst (lyr3Kind l) of   Just M3Line      -> renderLine3D      cam proj vp (layerToLine      l)@@ -273,8 +328,12 @@   Just M3Quiver    -> renderQuiver3D cam proj vp (layerToQuiver l)   _                -> [] --- | Phase 25 A5: 層の per-point err (正規化済) があれば誤差棒を描く (bar/scatter--- 共通)。 頂点列は正規化済 'lyr3Points'。 最前面に描くため depth 統合外。+-- | [日本語]: 層の per-point err (正規化済) があれば誤差棒を描く (bar/scatter+--   共通)。 頂点列は正規化済 'lyr3Points'。 最前面に描くため depth 統合外。+--   [English]: Draws error bars when a layer's per-point err (already+--   normalized) is present (shared by bar and scatter). Vertices come from+--   the already-normalized 'lyr3Points'. Drawn in front, outside depth+--   integration. errorBarPrimsOf :: Camera3D -> Projection3D -> Viewport -> Layer3D -> [Primitive] errorBarPrimsOf cam proj vp l =   case (getLast (lyr3PtErrs l), getLast (lyr3Points l)) of@@ -282,10 +341,15 @@       renderErrorBars3D cam proj vp "#333333" 1.0 (zip pts es)     _ -> [] --- | Phase 25 A7: テキスト注釈 layer (M3Text) を描く。 正規化済 'lyr3Points' を--- 投影し、 各点に 'lyr3Labels' の文字列を PText で置く (depth 統合外の前面 overlay)。--- 文字色 = 'lyr3Color' (既定 @#333333@)、 サイズ = 'lyr3Size' (既定 11)。 点の--- 少し上 (-6px) に中央寄せ。+-- | [日本語]: テキスト注釈 layer (M3Text) を描く。 正規化済 'lyr3Points' を+--   投影し、 各点に 'lyr3Labels' の文字列を PText で置く (depth 統合外の前面 overlay)。+--   文字色 = 'lyr3Color' (既定 @#333333@)、 サイズ = 'lyr3Size' (既定 11)。 点の+--   少し上 (-6px) に中央寄せ。+--   [English]: Draws the text-annotation layer (M3Text). Projects the+--   already-normalized 'lyr3Points' and places each point's 'lyr3Labels'+--   string as a 'PText' (a front overlay outside depth integration). Text+--   color comes from 'lyr3Color' (default @#333333@), size from+--   'lyr3Size' (default 11). Centered, slightly above each point (-6px). textPrimsOf :: Camera3D -> Projection3D -> Viewport -> Layer3D -> [Primitive] textPrimsOf cam proj vp l = case getFirst (lyr3Kind l) of   Just M3Text ->@@ -299,10 +363,16 @@     in zipWith place pts lbs   _ -> [] --- | Phase 24 A8 / 25 A8: 正規化済 layer を box aspect @(xa, ya, za)@ 倍する。--- surface は grid z (za) と x/y range (xa/ya)、 それ以外は点列の各成分。--- @(1,1,1)@ は恒等 (= 旧出力ビット不変)。 surface の grid x/y は xRange を縮める--- ことで反映 (Surface.hs が xRange を線形補間して頂点を置くため)。+-- | [日本語]: 正規化済 layer を box aspect @(xa, ya, za)@ 倍する。+--   surface は grid z (za) と x/y range (xa/ya)、 それ以外は点列の各成分。+--   @(1,1,1)@ は恒等 (= 旧出力ビット不変)。 surface の grid x/y は xRange を縮める+--   ことで反映 (Surface.hs が xRange を線形補間して頂点を置くため)。+--   [English]: Scales an already-normalized layer by the box aspect+--   @(xa, ya, za)@. For a surface, this scales grid z (za) and the x/y+--   range (xa/ya); for other layers, each component of the point list.+--   @(1,1,1)@ is the identity (bit-identical to legacy output). A surface's+--   grid x/y is reflected by shrinking xRange (since Surface.hs places+--   vertices by linearly interpolating xRange). scaleAspectLayer :: Double -> Double -> Double -> Layer3D -> Layer3D scaleAspectLayer xa ya za l   | xa == 1 && ya == 1 && za == 1 = l@@ -324,12 +394,21 @@         sv (Vec3 vx vy vz) = Vec3 (vx * xa) (vy * ya) (vz * za)         scaleRange a (lo, hi) = (lo * a, hi * a) --- | Phase 24 A7: 群別 3D 図のタイル配置 (= 「群別曲面の並置」)。 N 個の--- @(群ラベル, spec)@ を near-square グリッド (@ncol = ceil(√n)@) に並べ、 各--- パネルを 'renderSpec3DInPanel' で描き、 上部に群ラベルを置く。 colorbar は--- panel 毎 (各 spec の colormap surface に追従)。 camera/axes は各 spec が個別に--- 持つ前提 (analyze の群別 'surfaceOf' 出力をそのまま渡せる)。 1 パネルの--- 既定サイズは 380×380 px (spec が 'width3DV'/'height3DV' を持てばそれを使う)。+-- | [日本語]: 群別 3D 図のタイル配置 (= 「群別曲面の並置」)。 N 個の+--   @(群ラベル, spec)@ を near-square グリッド (@ncol = ceil(√n)@) に並べ、 各+--   パネルを 'renderSpec3DInPanel' で描き、 上部に群ラベルを置く。 colorbar は+--   panel 毎 (各 spec の colormap surface に追従)。 camera/axes は各 spec が個別に+--   持つ前提 (analyze の群別 @surfaceOf@ 出力をそのまま渡せる)。 1 パネルの+--   既定サイズは 380×380 px (spec が @width3DV@/@height3DV@ を持てばそれを使う)。+--   [English]: Tiles a set of grouped 3D figures ("placing group surfaces+--   side by side"). Arranges N @(group label, spec)@ pairs into a+--   near-square grid (@ncol = ceil(√n)@), draws each panel with+--   'renderSpec3DInPanel', and places the group label above it. The+--   colorbar is per-panel (following each spec's colormap surface).+--   camera / axes are assumed to belong to each spec individually (so+--   output from hanalyze's grouped @surfaceOf@ can be passed+--   straight through). Each panel defaults to 380×380 px (used unless the+--   spec sets @width3DV@/@height3DV@). saveSVG3DFacet :: FilePath -> [(Text, VisualSpec3D)] -> IO () saveSVG3DFacet _    []     = pure () saveSVG3DFacet path panels = do@@ -352,7 +431,9 @@       prims = concat (zipWith panelPrims [0 ..] panels)   savePrimitivesSVG path wPx hPx "" prims --- | Phase 24 A3: 退化した軸 (min == max) を ±0.5 に広げる (正規化の 0 割り防止)。+-- | [日本語]: 退化した軸 (min == max) を ±0.5 に広げる (正規化の 0 割り防止)。+--   [English]: Widens a degenerate axis (min == max) to ±0.5 (prevents+--   division by zero during normalization). padAxes3D :: Axes3D -> Axes3D padAxes3D ax =   let pad lo hi | hi > lo   = (lo, hi)@@ -364,16 +445,22 @@         , axesYMin = y0, axesYMax = y1         , axesZMin = z0, axesZMax = z1 } --- | Phase 24 A3 / 25 A8: 軸 bbox を [-1,1]^3 へ写す正規化 (各軸独立)。 log 軸--- ('axes*Log') は log10 空間で affine、 線形軸はそのまま affine。+-- | [日本語]: 軸 bbox を [-1,1]^3 へ写す正規化 (各軸独立)。 log 軸+--   ('axes*Log') は log10 空間で affine、 線形軸はそのまま affine。+--   [English]: Normalizes the axis bbox to @[-1,1]^3@ (each axis+--   independently). A log axis ('axes*Log') is affine in log10 space; a+--   linear axis is affine directly. normPoint3D :: Axes3D -> Point3 -> Point3 normPoint3D ax (Point3 x y z) =   Point3 (normCoord (axesXLog ax) (axesXMin ax) (axesXMax ax) x)          (normCoord (axesYLog ax) (axesYMin ax) (axesYMax ax) y)          (normCoord (axesZLog ax) (axesZMin ax) (axesZMax ax) z) --- | Phase 25 A8: 1 軸の正規化 (log/線形)。 @[lo,hi]@ → @[-1,1]@。 log は値・端を--- 1e-12 に clamp してから log10 空間で affine。+-- | [日本語]: 1 軸の正規化 (log/線形)。 @[lo,hi]@ → @[-1,1]@。 log は値・端を+--   1e-12 に clamp してから log10 空間で affine。+--   [English]: Normalizes a single axis (log or linear): @[lo,hi]@ to+--   @[-1,1]@. For log, values and endpoints are clamped to 1e-12 and then+--   made affine in log10 space. normCoord :: Bool -> Double -> Double -> Double -> Double normCoord isLog lo hi v   | isLog =@@ -384,8 +471,12 @@   | hi <= lo  = 0   | otherwise = -1 + 2 * (v - lo) / (hi - lo) --- | Phase 24 A3: layer のデータを正規化座標に写す。 surface は grid の z と--- x/y range、 それ以外は点列。 colormap の色は z 正規化に対して不変 (affine)。+-- | [日本語]: layer のデータを正規化座標に写す。 surface は grid の z と+--   x/y range、 それ以外は点列。 colormap の色は z 正規化に対して不変 (affine)。+--   [English]: Maps a layer's data into normalized coordinates. For a+--   surface, this is the grid's z and the x/y range; for other layers, the+--   point list. Colormap colors are invariant under z normalization+--   (it's affine). normLayer3D :: Axes3D -> Layer3D -> Layer3D normLayer3D ax l0 = case getFirst (lyr3Kind l) of   Just M3Surface ->@@ -430,9 +521,13 @@                , lyr3Labels = Last (Just (map snd annots)) }           _ -> l0 --- | Phase 24 A2: パネル右端の縦 colorbar (gradient strip + min/mid/max ラベル)。+-- | [日本語]: パネル右端の縦 colorbar (gradient strip + min/mid/max ラベル)。 --   2D の gradient bar 凡例と同じ 'continuousColor' 補間 = palette 整合。---   Phase 24 A7: パネル矩形 @(px,py,pw,ph)@ 局所座標で配置 (facet 対応)。+--   パネル矩形 @(px,py,pw,ph)@ 局所座標で配置 (facet 対応)。+--   [English]: The vertical colorbar (a gradient strip plus min/mid/max+--   labels) along the panel's right edge, using the same 'continuousColor'+--   interpolation as the 2D gradient-bar legend (palette-consistent).+--   Positioned in panel-local coordinates @(px,py,pw,ph)@ (supports facets). renderColorbar3D :: Double -> Double -> Double -> Double -> ([Text], Double, Double) -> [Primitive] renderColorbar3D px py pw ph (stops, zMin, zMax) =   let barW = 14 :: Double@@ -456,14 +551,16 @@   in map strip [0 .. nStrip - 1]      <> map tick [zMin, (zMin + zMax) / 2, zMax] --- | tick 値の短い整形 (Axes3D の formatNum と同形)。+-- | [日本語]: tick 値の短い整形 (Axes3D の formatNum と同形)。+--   [English]: A compact formatting for tick values (the same shape as+--   Axes3D's formatNum). fmtNum3D :: Double -> Text fmtNum3D x =   let s = T.pack (show (fromIntegral (round (x * 10) :: Int) / 10.0 :: Double))   in T.dropWhileEnd (== '.') (T.dropWhileEnd (== '0') s) --- | Phase 24 A5 / #2: surface の contour を壁面へ投影する (matplotlib--- @contour(..., zdir=)@ 相当)。 'lyr3Contours' の各 @(dir, n)@ を描く:+-- | [日本語]: surface の contour を壁面へ投影する (matplotlib+--   @contour(..., zdir=)@ 相当)。 'lyr3Contours' の各 @(dir, n)@ を描く: -- --   * 'ContourZ' (床) = z 等値面 level set @{f=z_k}@ を 'marchingSegments' で抽出し --     floor へ落とす (topographic map)。@@ -471,9 +568,27 @@ --     @z = f(x_k, y)@ を yz 壁へ描く。 --   * 'ContourY' (前後壁) = y 軸の @n@ 位置で断面 @z = f(x, y_k)@ を xz 壁へ描く。 ----- 投影壁は **カメラから遠い面に自動固定** (eye と target の各軸符号で min/max 面を選ぶ・--- 曲面の手前に出て遮らないように)。 grid・aspect 正規化済 layer を受ける。 mpl の--- rotate_axes 経路 (= dir⊥平面で切った断面を壁へ投影) を再現 (実測で数値突合済)。+--   投影壁は __カメラから遠い面に自動固定__ (eye と target の各軸符号で min/max 面を選ぶ・+--   曲面の手前に出て遮らないように)。 grid・aspect 正規化済 layer を受ける。 mpl の+--   rotate_axes 経路 (= dir⊥平面で切った断面を壁へ投影) を再現 (実測で数値突合済)。+--   [English]: Projects a surface's contour onto a wall (equivalent to+--   matplotlib's @contour(..., zdir=)@). Draws each @(dir, n)@ from+--   'lyr3Contours':+--+--   * 'ContourZ' (floor) — extracts the z level-set contour @{f=z_k}@ via+--     'marchingSegments' and drops it onto the floor (a topographic map).+--   * 'ContourX' (left/right wall) — slices the surface at @n@ positions+--     along x, drawing the cross-section profile @z = f(x_k, y)@ on the yz+--     wall.+--   * 'ContourY' (front/back wall) — draws the cross-section+--     @z = f(x, y_k)@ at @n@ positions along y on the xz wall.+--+--   The projection wall __auto-pins to the far face from the camera__+--   (chosen from the eye's and target's sign along each axis, so it never+--   gets in front of the surface and occludes it). Takes an+--   already grid- and aspect-normalized layer. Reproduces matplotlib's+--   rotate_axes path (projecting a cross-section cut perpendicular to+--   @dir@ onto the wall); verified numerically against it. renderProjectedContour3D   :: (Double, Double, Double)   -> Camera3D -> Projection3D -> Viewport -> Layer3D -> [Primitive]@@ -532,8 +647,12 @@               in concatMap section (innerLevels nLev yMin yMax)         in concatMap one cs --- | 1 次元線形補間。 @interp1 nodes vals p@ は昇順 @nodes@ 上の値 @vals@ を位置 @p@ で--- 補間 (範囲外は端値で clamp・隣接 2 点で線形)。 断面 contour の grid 補間に使う。+-- | [日本語]: 1 次元線形補間。 @interp1 nodes vals p@ は昇順 @nodes@ 上の値 @vals@ を位置 @p@ で+--   補間 (範囲外は端値で clamp・隣接 2 点で線形)。 断面 contour の grid 補間に使う。+--   [English]: 1D linear interpolation. @interp1 nodes vals p@ interpolates+--   the values @vals@ over ascending @nodes@ at position @p@ (out-of-range+--   is clamped to the endpoint value; linear between the two neighbors).+--   Used for grid interpolation in cross-section contours. interp1 :: [Double] -> [Double] -> Double -> Double interp1 nodes vals p = go (zip nodes vals)   where
src/Graphics/Hgg/ThreeD/Line.hs view
@@ -1,6 +1,6 @@ -- | -- Module      : Graphics.Hgg.ThreeD.Line--- Description : 3D 線 / wireframe (Phase 3 A6)+-- Description : 3D lines and wireframes -- Copyright   : (c) 2026 Aelysce Project (Toshiaki Honda) -- License     : BSD-3-Clause {-# LANGUAGE DeriveGeneric     #-}@@ -86,15 +86,24 @@   in map mkLine sorted  -- ===========================================================================--- Phase 26 A3: 3D vector field (quiver3D)+-- 3D vector field (quiver3D) -- =========================================================================== --- | 3D vector field。 各矢印は始点 'q3Starts' → 終点 'q3Ends' (= 正規化済・--- autoscale + aspect 適用後の点列)。 描画は両端を投影して 2D 矢印 (本線 + 矢じり)--- にする (= mplot3d も実質 2D 矢じり)。 depth 統合外の前面 overlay。+-- | [日本語]: 3D vector field。 各矢印は始点 'q3Starts' → 終点 'q3Ends' (= 正規化済・+--   autoscale + aspect 適用後の点列)。 描画は両端を投影して 2D 矢印 (本線 + 矢じり)+--   にする (= mplot3d も実質 2D 矢じり)。 depth 統合外の前面 overlay。+--   [English]: A 3D vector field. Each arrow runs from a start point+--   'q3Starts' to an end point 'q3Ends' (already normalized — the point+--   lists have autoscale and aspect already applied). Rendering projects+--   both endpoints and draws a 2D arrow (shaft plus arrowhead), the same+--   way mplot3d effectively draws 2D arrowheads too. Drawn as a front+--   overlay, outside of depth integration. data Quiver3D = Quiver3D-  { q3Starts :: ![Point3]   -- ^ 矢印の始点 (正規化済)-  , q3Ends   :: ![Point3]   -- ^ 矢印の終点 (正規化済・始点 + scaled vec)+  { q3Starts :: ![Point3]   -- ^ [日本語]: 矢印の始点 (正規化済)+                             --   [English]: The arrow's start point (already normalized).+  , q3Ends   :: ![Point3]   -- ^ [日本語]: 矢印の終点 (正規化済・始点 + scaled vec)+                             --   [English]: The arrow's end point (already normalized;+                             --   start point plus the scaled vector).   , q3Color  :: !Text   , q3Width  :: !Double   } deriving (Show, Eq, Generic)@@ -115,8 +124,11 @@           Projected ex ey _ = project e       in arrowHead2D sx sy ex ey style --- | 投影後の 2D 矢印 (本線 + 矢じり 2 本)。 矢じり形状は 2D quiver--- (core Render.Basic drawArrow2D) と同じ (長さ 9px・開き比 0.5)。+-- | [日本語]: 投影後の 2D 矢印 (本線 + 矢じり 2 本)。 矢じり形状は 2D quiver+--   (core Render.Basic drawArrow2D) と同じ (長さ 9px・開き比 0.5)。+--   [English]: The projected 2D arrow (a shaft plus two arrowhead lines).+--   The arrowhead shape matches the 2D quiver's (core Render.Basic+--   drawArrow2D): length 9px, spread ratio 0.5. arrowHead2D :: Double -> Double -> Double -> Double -> LineStyle -> [Primitive] arrowHead2D px1 py1 px2 py2 ls =   let dx = px2 - px1; dy = py2 - py1
src/Graphics/Hgg/ThreeD/Projection.hs view
@@ -1,18 +1,28 @@ -- | -- Module      : Graphics.Hgg.ThreeD.Projection--- Description : 3D → 2D 投影 (Phase 3 A3)+-- Description : 3D to 2D projection -- Copyright   : (c) 2026 Aelysce Project (Toshiaki Honda) -- License     : BSD-3-Clause ----- 標準 3D グラフィックス pipeline:+-- [日本語]: 標準 3D グラフィックス pipeline: -- --   world space → (view 行列、 lookAt) → camera space --               → (projection 行列、 ortho or persp) → clip space --               → (perspective divide) → NDC (= [-1, 1]^3) --               → (viewport 変換) → 2D screen 座標 ----- 'project3D' が全 step をまとめて (Point3 world → Point2D screen + zClip) を返す。--- z は depth cue (= painter's algorithm の sort key) に使う。+--   'project3D' が全 step をまとめて (Point3 world → Point2D screen + zClip) を返す。+--   z は depth cue (= painter's algorithm の sort key) に使う。+-- [English]: The standard 3D graphics pipeline:+--+--   world space → (view matrix, lookAt) → camera space+--               → (projection matrix, ortho or persp) → clip space+--               → (perspective divide) → NDC (@[-1, 1]^3@)+--               → (viewport transform) → 2D screen coordinates+--+--   'project3D' bundles every step and returns (Point3 world to Point2D+--   screen + zClip). z is used as a depth cue (the sort key for the+--   painter's algorithm). {-# LANGUAGE BangPatterns #-} module Graphics.Hgg.ThreeD.Projection   ( -- * Matrix 演算@@ -36,11 +46,13 @@ -- Matrix 基本演算 (= 4x4) -- =========================================================================== --- | 単位行列。+-- | [日本語]: 単位行列。+--   [English]: The identity matrix. identityM :: Mat4 identityM = Mat4 1 0 0 0  0 1 0 0  0 0 1 0  0 0 0 1 --- | 行列積 (= row-major、 自分 × 引数)。+-- | [日本語]: 行列積 (= row-major、 自分 × 引数)。+--   [English]: Matrix multiplication (row-major, @self × argument@). multM :: Mat4 -> Mat4 -> Mat4 multM   (Mat4 a11 a12 a13 a14@@ -72,8 +84,11 @@     (a41*b13 + a42*b23 + a43*b33 + a44*b43)     (a41*b14 + a42*b24 + a43*b34 + a44*b44) --- | Point3 を 4x4 行列で変換 (= 同次座標で w=1 として扱い、 結果も Point3 返す)。--- w 成分は別途扱うので 'project3D' を経由するのが通常。+-- | [日本語]: Point3 を 4x4 行列で変換 (= 同次座標で w=1 として扱い、 結果も Point3 返す)。+--   w 成分は別途扱うので 'project3D' を経由するのが通常。+--   [English]: Transforms a 'Point3' by a 4x4 matrix (treats the homogeneous+--   w component as 1 and returns a 'Point3' too). Since the w component+--   needs separate handling, going through 'project3D' is the usual path. transformPoint :: Mat4 -> Point3 -> Point3 transformPoint   (Mat4 m11 m12 m13 m14@@ -90,10 +105,16 @@ -- View 行列 (= lookAt) / Projection 行列 -- =========================================================================== --- | lookAt: camera の view 行列を生成。 右手系、 OpenGL 流。+-- | [日本語]: lookAt: camera の view 行列を生成。 右手系、 OpenGL 流。 ----- 結果: world space の点 P に対し viewMatrix * P が camera space の P' を返す。--- camera space では camera が原点、 -z 方向を向く (= 右手系 OpenGL 慣例)。+--   結果: world space の点 P に対し viewMatrix * P が camera space の P' を返す。+--   camera space では camera が原点、 -z 方向を向く (= 右手系 OpenGL 慣例)。+--   [English]: lookAt: builds the camera's view matrix. Right-handed,+--   OpenGL-style.+--+--   Result: for a point P in world space, @viewMatrix * P@ gives P' in+--   camera space. In camera space, the camera sits at the origin and looks+--   down -z (the right-handed OpenGL convention). viewMatrix :: Camera3D -> Mat4 viewMatrix (Camera3D (Point3 ex ey ez) (Point3 tx ty tz) up) =   let -- forward = normalize(target - eye)、 ただし camera は -z を向くので f は実は反転して使う@@ -112,10 +133,16 @@        (-fx)    (-fy)    (-fz)    tz'        0        0        0        1 --- | Projection 行列を生成。 'Orthographic' / 'Perspective' どちらも対応。+-- | [日本語]: Projection 行列を生成。 'Orthographic' / 'Perspective' どちらも対応。 ----- 出力: camera space の点 P → clip space の P' (= w 成分も含む)。--- 'project3D' で perspective divide (= x,y,z を w で割る) を続けて NDC へ。+--   出力: camera space の点 P → clip space の P' (= w 成分も含む)。+--   'project3D' で perspective divide (= x,y,z を w で割る) を続けて NDC へ。+--   [English]: Builds the projection matrix. Handles both 'Orthographic' and+--   'Perspective'.+--+--   Output: for a point P in camera space, produces P' in clip space+--   (including the w component). 'project3D' follows up with the+--   perspective divide (dividing x, y, z by w) to reach NDC. projectionMatrix :: Projection3D -> Mat4 projectionMatrix (Orthographic xH yH n f) =   let !sx = 1 / xH@@ -135,8 +162,11 @@ -- Projection 一発 -- =========================================================================== --- | Viewport: NDC (= [-1, 1]^2) → screen pixel への変換パラメタ。--- 通常 Layout 領域に合わせて (xMin, yMin, width, height) で指定。+-- | [日本語]: Viewport: NDC (= [-1, 1]^2) → screen pixel への変換パラメタ。+--   通常 Layout 領域に合わせて (xMin, yMin, width, height) で指定。+--   [English]: Viewport: the parameters for converting NDC (@[-1, 1]^2@) to+--   screen pixels. Usually specified as (xMin, yMin, width, height) to match+--   the layout region. data Viewport = Viewport   { vpX :: !Double   , vpY :: !Double@@ -144,23 +174,34 @@   , vpH :: !Double   } deriving (Show, Eq) --- | NDC [-1,1] の (x, y) → screen pixel への変換。 y は反転 (= SVG 慣例で y 下方向)。+-- | [日本語]: NDC [-1,1] の (x, y) → screen pixel への変換。 y は反転 (= SVG 慣例で y 下方向)。+--   [English]: Converts NDC @[-1,1]@ (x, y) to screen pixels. y is flipped+--   (following the SVG convention where y points down). viewportTransform :: Viewport -> Double -> Double -> (Double, Double) viewportTransform (Viewport vx vy vw vh) ndcX ndcY =   let sx = vx + (ndcX + 1) / 2 * vw       sy = vy + (1 - (ndcY + 1) / 2) * vh   in (sx, sy) --- | 投影結果。 screen 座標 (= viewport 変換後) + z (= depth cue / sort 用、 NDC z)。+-- | [日本語]: 投影結果。 screen 座標 (= viewport 変換後) + z (= depth cue / sort 用、 NDC z)。+--   [English]: The projection result: screen coordinates (after the+--   viewport transform) plus z (the NDC z, used as a depth cue / sort key). data Projected = Projected-  { projX     :: !Double  -- ^ screen pixel x-  , projY     :: !Double  -- ^ screen pixel y-  , projDepth :: !Double  -- ^ NDC z ∈ [-1, 1]、 -1 が手前、 +1 が奥+  { projX     :: !Double  -- ^ [日本語]: screen pixel x+                           --   [English]: The screen pixel x coordinate.+  , projY     :: !Double  -- ^ [日本語]: screen pixel y+                           --   [English]: The screen pixel y coordinate.+  , projDepth :: !Double  -- ^ [日本語]: NDC z ∈ [-1, 1]、 -1 が手前、 +1 が奥+                           --   [English]: The NDC z ∈ [-1, 1]; -1 is nearest, +1 is farthest.   } deriving (Show, Eq) --- | World point → Projected (screen + depth)。+-- | [日本語]: World point → Projected (screen + depth)。 ----- pipeline: world → view → projection → perspective divide → viewport。+--   pipeline: world → view → projection → perspective divide → viewport。+--   [English]: Converts a world point to 'Projected' (screen + depth).+--+--   Pipeline: world to view to projection to perspective divide to+--   viewport. project3D :: Camera3D -> Projection3D -> Viewport -> Point3 -> Projected project3D cam proj vp p =   let view = viewMatrix cam
src/Graphics/Hgg/ThreeD/Scatter.hs view
@@ -1,11 +1,16 @@ -- | -- Module      : Graphics.Hgg.ThreeD.Scatter--- Description : 3D scatter plot (Phase 3 A5)+-- Description : 3D scatter plot -- Copyright   : (c) 2026 Aelysce Project (Toshiaki Honda) -- License     : BSD-3-Clause ----- 3D 点群を camera + projection で 2D に変換し、 painter's algorithm (= z で sort、--- 奥から手前へ) で描画。 depth cue として size と alpha を z で線形に減衰させる。+-- [日本語]: 3D 点群を camera + projection で 2D に変換し、 painter's algorithm+--   (= z で sort、 奥から手前へ) で描画。 depth cue として size と alpha を z で+--   線形に減衰させる。+-- [English]: Converts a 3D point cloud into 2D via the camera and+--   projection, then renders it with the painter's algorithm (sorted by z,+--   drawn from back to front). Size and alpha fade linearly with z as a+--   depth cue. {-# LANGUAGE DeriveGeneric     #-} {-# LANGUAGE OverloadedStrings #-} module Graphics.Hgg.ThreeD.Scatter@@ -27,39 +32,65 @@                                                   project3D) import           Graphics.Hgg.ThreeD.Types --- | 3D scatter 1 series 分の設定。+-- | [日本語]: 3D scatter 1 series 分の設定。+--   [English]: The configuration for one series of a 3D scatter plot. data Scatter3D = Scatter3D-  { sc3Points :: ![Point3]   -- ^ 3D 点群-  , sc3Color  :: !Text       -- ^ 色 (= hex 例 "#1f77b4")-  , sc3Size   :: !Double     -- ^ 基本 size (= 近側の半径 px、 奥は depth cue で小さく)-  , sc3Alpha  :: !Double     -- ^ 基本 alpha (= 近側の値、 奥は depth cue で薄く)+  { sc3Points :: ![Point3]   -- ^ [日本語]: 3D 点群+                              --   [English]: The 3D point cloud.+  , sc3Color  :: !Text       -- ^ [日本語]: 色 (= hex 例 "#1f77b4")+                              --   [English]: The color (hex, e.g. "#1f77b4").+  , sc3Size   :: !Double     -- ^ [日本語]: 基本 size (= 近側の半径 px、 奥は depth cue で小さく)+                              --   [English]: The base size (the near-side radius in+                              --   px; farther points shrink via the depth cue).+  , sc3Alpha  :: !Double     -- ^ [日本語]: 基本 alpha (= 近側の値、 奥は depth cue で薄く)+                              --   [English]: The base alpha (the near-side value;+                              --   farther points fade via the depth cue).   , sc3Colors :: !(Maybe [Text])-    -- ^ Phase 25 A2: 点ごとの色 (= 群色分け / 連続色マップ)。 @Just cs@ で+    -- ^ [日本語]: 点ごとの色 (= 群色分け / 連続色マップ)。 @Just cs@ で     --   @cs !! i@ を点 i に使う (長さが点数未満なら超過点は 'sc3Color')。     --   @Nothing@ = 全点 'sc3Color' (後方互換)。+    --   [English]: A per-point color (for group coloring or a continuous+    --   colormap). With @Just cs@, point i uses @cs !! i@ (points beyond the+    --   list's length fall back to 'sc3Color'). @Nothing@ means every point+    --   uses 'sc3Color' (backward compatible).   , sc3Sizes  :: !(Maybe [Double])-    -- ^ Phase 25 A3: 点ごとの基本 size (= 値マップ bubble)。 @Just ss@ で+    -- ^ [日本語]: 点ごとの基本 size (= 値マップ bubble)。 @Just ss@ で     --   @ss !! i@ を点 i の基本半径に使う (長さが点数未満なら超過点は 'sc3Size')。     --   @Nothing@ = 全点 'sc3Size' (後方互換)。 depth cue は基本 size に乗算。+    --   [English]: A per-point base size (a value-mapped bubble). With+    --   @Just ss@, point i's base radius is @ss !! i@ (points beyond the+    --   list's length fall back to 'sc3Size'). @Nothing@ means every point+    --   uses 'sc3Size' (backward compatible). The depth cue multiplies the+    --   base size.   } deriving (Show, Eq, Generic) instance ToJSON   Scatter3D instance FromJSON Scatter3D --- | default: 青、 size 5、 alpha 0.85、 単色・単 size (per-point 無し)。+-- | [日本語]: default: 青、 size 5、 alpha 0.85、 単色・単 size (per-point 無し)。+--   [English]: The default: blue, size 5, alpha 0.85, a single color and+--   size (no per-point overrides). defaultScatter3D :: [Point3] -> Scatter3D defaultScatter3D ps = Scatter3D ps "#1f77b4" 5 0.85 Nothing Nothing --- | 3D scatter を 2D Primitive (= PCircle) 列に変換。--- z で sort して painter's (= 奥から描画)、 size / alpha は depth cue で減衰。+-- | [日本語]: 3D scatter を 2D Primitive (= PCircle) 列に変換。+--   z で sort して painter's (= 奥から描画)、 size / alpha は depth cue で減衰。+--   [English]: Converts a 3D scatter into a list of 2D primitives (@PCircle@).+--   Sorted by z for the painter's algorithm (drawn back-to-front); size and+--   alpha fade via the depth cue. renderScatter3D   :: Camera3D -> Projection3D -> Viewport   -> Scatter3D -> [Primitive] renderScatter3D cam proj vp sc =   map snd (sortOn (negate . fst) (scatterPointsDepth cam proj vp sc)) --- | Phase 24 A8 (depth 統合): 各点を @(投影 depth, PCircle)@ として**未ソート**で--- 返す (= 'renderScatter3D' は @sortOn (negate.fst)@ → @map snd@・ビット同一)。--- surface 面との層横断 depth 統合に使う (scatter 点が膜を透ける問題の解消)。+-- | [日本語]: (depth 統合): 各点を @(投影 depth, PCircle)@ として__未ソート__で+--   返す (= 'renderScatter3D' は @sortOn (negate.fst)@ → @map snd@・ビット同一)。+--   surface 面との層横断 depth 統合に使う (scatter 点が膜を透ける問題の解消)。+--   [English]: (depth integration): Returns each point as+--   @(projected depth, PCircle)@, __unsorted__ ('renderScatter3D' is+--   bit-identical to @sortOn (negate . fst)@ followed by @map snd@ of this).+--   Used to integrate depth across layers together with surface faces+--   (fixing scatter points showing through a surface). scatterPointsDepth   :: Camera3D -> Projection3D -> Viewport   -> Scatter3D -> [(Double, Primitive)]
src/Graphics/Hgg/ThreeD/Spec.hs view
@@ -1,11 +1,11 @@ -- | -- Module      : Graphics.Hgg.ThreeD.Spec--- Description : 3D VisualSpec / Layer + Monoid (Phase 5 A3)+-- Description : 3D VisualSpec / Layer types with a Monoid instance -- Copyright   : (c) 2026 Aelysce Project (Toshiaki Honda) -- License     : BSD-3-Clause ----- 2D 同型 (= Graphics.Hgg.Spec の 'VisualSpec' / 'Layer' / Monoid 構成) を踏襲した--- 3D 用 spec API。 ユーザ視点で 2D と同じ構文で組める:+-- [日本語]: 2D 同型 (= Graphics.Hgg.Spec の 'Graphics.Hgg.Spec.VisualSpec' / 'Graphics.Hgg.Spec.Layer' / Monoid 構成) を踏襲した+--   3D 用 spec API。 ユーザ視点で 2D と同じ構文で組める: -- -- @ -- -- 2D@@ -16,13 +16,28 @@ --            <> camera (defaultCameraZUp 3) <> axes3D defaultAxes3D <> title3D "..." -- @ ----- 値型は分離 (= 'Layer' と 'Layer3D' は別)。 理由は phase-5 計画 md §2.2。+--   値型は分離 (= 'Graphics.Hgg.Spec.Layer' と 'Layer3D' は別)。 理由は phase-5 計画 md §2.2。+-- [English]: A 3D spec API that mirrors the 2D one (the+--   'Graphics.Hgg.Spec.VisualSpec' / 'Graphics.Hgg.Spec.Layer' / Monoid design in Graphics.Hgg.Spec). From a+--   user's perspective, it's built with the exact same syntax as 2D:+--+-- @+-- -- 2D+-- purePlot <> layer (scatter x y <> color (fromHex "#ff0000") <> alpha 0.7) <> title "..."+--+-- -- 3D (100% syntax-identical)+-- purePlot3D <> layer3D (scatter3D pts <> color3D (fromHex "#ff0000") <> alpha3D 0.7)+--            <> camera (defaultCameraZUp 3) <> axes3D defaultAxes3D <> title3D "..."+-- @+--+--   The value types are kept separate ('Graphics.Hgg.Spec.Layer' and 'Layer3D' are distinct+--   types); see phase-5 plan md §2.2 for why. {-# LANGUAGE DeriveGeneric     #-} {-# LANGUAGE OverloadedStrings #-} module Graphics.Hgg.ThreeD.Spec   ( -- * MarkKind3D     Mark3DKind (..)-    -- * Bar スタイル (Phase 25 A5・Bar module 再 export)+    -- * Bar スタイル (Bar module 再 export)   , BarStyle3D (..)     -- * Layer3D (= 1 layer の per-field Monoid)   , Layer3D (..)@@ -30,7 +45,7 @@   , VisualSpec3D (..)     -- * 純粋起点   , purePlot3D-    -- * Layer 起点 (= 2D 'layer' 同型)+    -- * Layer 起点 (= 2D 'Graphics.Hgg.Spec.layer' 同型)   , layer3D     -- * Mark コンストラクタ (= Layer3D 返却)   , scatter3D@@ -97,7 +112,7 @@   , layerToSurface   , layerToBar   , layerToQuiver-    -- * 列参照の解決 (Phase 24 A6)+    -- * 列参照の解決   , resolveLayer3D   , resolveSpec3D     -- * Helper: layers から bounding box 自動算出 (= Axes 未指定時の default)@@ -135,7 +150,9 @@ -- MarkKind3D -- =========================================================================== --- | 3D layer の幾何種別 (= 2D 'MarkKind' の 3D 版)。+-- | [日本語]: 3D layer の幾何種別 (= 2D 'Graphics.Hgg.Spec.MarkKind' の 3D 版)。+--   [English]: A 3D layer's geometry kind (the 3D counterpart of 2D+--   'Graphics.Hgg.Spec.MarkKind'). data Mark3DKind   = M3Scatter   | M3Line@@ -151,14 +168,21 @@ instance FromJSON Mark3DKind  -- ===========================================================================--- Layer3D (= 2D 'Layer' 同型、 per-field First/Last Monoid)+-- Layer3D (= 2D 'Graphics.Hgg.Spec.Layer' 同型、 per-field First/Last Monoid) -- =========================================================================== --- | 3D 1 layer の rolled-up 表現。 全 field が 'First' or 'Last' なので per-field---   '(<>)' で自然に合成可能 (= 2D 'Layer' と同じ規律)。+-- | [日本語]: 3D 1 layer の rolled-up 表現。 全 field が 'First' or 'Last' なので per-field+--   '(<>)' で自然に合成可能 (= 2D 'Graphics.Hgg.Spec.Layer' と同じ規律)。 -----   * 'lyr3Kind' は 'First' (= 最初に設定した kind が勝つ、 2D 'lyKind' と同じ)---   * その他属性は 'Last' (= 後勝ち、 2D 'lyColor' 等と同じ)+--   * 'lyr3Kind' は 'First' (= 最初に設定した kind が勝つ、 2D @lyKind@ と同じ)+--   * その他属性は 'Last' (= 後勝ち、 2D @lyColor@ 等と同じ)+--   [English]: The rolled-up representation of a single 3D layer. Every+--   field is 'First' or 'Last', so per-field @(\<\>)@ composes naturally+--   (the same discipline as 2D 'Graphics.Hgg.Spec.Layer').+--+--   * 'lyr3Kind' is 'First' (the first kind set wins, same as 2D @lyKind@)+--   * every other attribute is 'Last' (last one wins, same as 2D @lyColor@+--     and friends) data Layer3D = Layer3D   { lyr3Kind      :: !(First Mark3DKind)   , lyr3Points    :: !(Last [Point3])@@ -261,10 +285,12 @@                    mempty mempty mempty mempty mempty mempty  -- ===========================================================================--- VisualSpec3D (= 2D 'VisualSpec' 同型、 layers ++ + per-field Last)+-- VisualSpec3D (= 2D 'Graphics.Hgg.Spec.VisualSpec' 同型、 layers ++ + per-field Last) -- =========================================================================== --- | 3D 図全体の spec。 'layers' は append、 その他は 'Last' (= 後勝ち)。+-- | [日本語]: 3D 図全体の spec。 @layers@ は append、 その他は 'Last' (= 後勝ち)。+--   [English]: The spec for an entire 3D figure. @layers@ appends; every+--   other field is 'Last' (last one wins). data VisualSpec3D = VisualSpec3D   { vs3Layers     :: ![Layer3D]   , vs3Title      :: !(Last Text)@@ -296,14 +322,18 @@                         mempty mempty mempty  -- ===========================================================================--- 純粋起点 + lift helpers (= 2D 'purePlot' / 'layer' 同型)+-- 純粋起点 + lift helpers (= 2D 'Graphics.Hgg.Spec.purePlot' / 'Graphics.Hgg.Spec.layer' 同型) -- =========================================================================== --- | 'VisualSpec3D' 起点 (= 2D 'purePlot' 同型、 'mempty' alias)。+-- | [日本語]: 'VisualSpec3D' 起点 (= 2D 'Graphics.Hgg.Spec.purePlot' 同型、 'mempty' alias)。+--   [English]: The 'VisualSpec3D' starting point (the counterpart of 2D+--   'Graphics.Hgg.Spec.purePlot'; an alias for 'mempty'). purePlot3D :: VisualSpec3D purePlot3D = mempty --- | 'Layer3D' を 'VisualSpec3D' に lift (= 2D 'layer' 同型)。+-- | [日本語]: 'Layer3D' を 'VisualSpec3D' に lift (= 2D 'Graphics.Hgg.Spec.layer' 同型)。+--   [English]: Lifts a 'Layer3D' into a 'VisualSpec3D' (the counterpart of+--   2D 'Graphics.Hgg.Spec.layer'). layer3D :: Layer3D -> VisualSpec3D layer3D l = mempty { vs3Layers = [l] } @@ -311,37 +341,47 @@ -- Mark コンストラクタ (= Layer3D を返却、 後で `<> color3D ...` で属性 append) -- =========================================================================== --- | 3D scatter (= M3Scatter)。 Phase 24 A6: 2D 'Graphics.Hgg.Spec.scatter' と--- 対称の **ColRef 3 つ** (x, y, z)。 列名 (OverloadedStrings) か 'inline' の--- 生値を渡す。 旧 [Point3] 直入れは 'scatter3DPoints'。+-- | [日本語]: 3D scatter (= M3Scatter)。 2D 'Graphics.Hgg.Spec.scatter' と+--   対称の __ColRef 3 つ__ (x, y, z)。 列名 (OverloadedStrings) か 'Graphics.Hgg.Spec.inline' の+--   生値を渡す。 旧 [Point3] 直入れは 'scatter3DPoints'。+--   [English]: A 3D scatter (M3Scatter). Symmetric with 2D+--   'Graphics.Hgg.Spec.scatter': __three ColRefs__ (x, y, z). Pass a column+--   name (via OverloadedStrings) or raw 'Graphics.Hgg.Spec.inline' values. For the older+--   direct @[Point3]@ form, use 'scatter3DPoints'. scatter3D :: ColRef -> ColRef -> ColRef -> Layer3D scatter3D x y z = mempty   { lyr3Kind = First (Just M3Scatter)   , lyr3EncX = Last (Just x), lyr3EncY = Last (Just y), lyr3EncZ = Last (Just z)   } --- | 3D scatter ([Point3] 直入れ・旧 'scatter3D')。+-- | [日本語]: 3D scatter ([Point3] 直入れ・旧 'scatter3D')。+--   [English]: A 3D scatter with @[Point3]@ passed directly (the older form+--   of 'scatter3D'). scatter3DPoints :: [Point3] -> Layer3D scatter3DPoints pts = mempty   { lyr3Kind   = First (Just M3Scatter)   , lyr3Points = Last  (Just pts)   } --- | 3D line (= MLine3D、 連続折れ線)。+-- | [日本語]: 3D line (= MLine3D、 連続折れ線)。+--   [English]: A 3D line (MLine3D, a continuous polyline). line3D :: ColRef -> ColRef -> ColRef -> Layer3D line3D x y z = mempty   { lyr3Kind = First (Just M3Line)   , lyr3EncX = Last (Just x), lyr3EncY = Last (Just y), lyr3EncZ = Last (Just z)   } --- | 3D line ([Point3] 直入れ・旧 'line3D')。+-- | [日本語]: 3D line ([Point3] 直入れ・旧 'line3D')。+--   [English]: A 3D line with @[Point3]@ passed directly (the older form of+--   'line3D'). line3DPoints :: [Point3] -> Layer3D line3DPoints pts = mempty   { lyr3Kind   = First (Just M3Line)   , lyr3Points = Last  (Just pts)   } --- | 3D wireframe (= MWireframe3D、 任意 edge 群)。+-- | [日本語]: 3D wireframe (= MWireframe3D、 任意 edge 群)。+--   [English]: A 3D wireframe (MWireframe3D, an arbitrary set of edges). wireframe3D :: [Point3] -> [(Int, Int)] -> Layer3D wireframe3D pts es = mempty   { lyr3Kind   = First (Just M3Wireframe)@@ -349,10 +389,16 @@   , lyr3Edges  = Last  (Just es)   } --- | Phase 30 A6: 列駆動 3D surface (案C・= 2D 系と対称の ColRef 3 つ x/y/z)。 df の---   long 形 (各行 = 格子点 (x,y,z)) を受け、 'resolveLayer3D' で **内部 pivot** して+-- | [日本語]: 列駆動 3D surface (案C・= 2D 系と対称の ColRef 3 つ x/y/z)。 df の+--   long 形 (各行 = 格子点 (x,y,z)) を受け、 'resolveLayer3D' で __内部 pivot__ して --   grid mesh ('lyr3Grid' + xRange/yRange) に落とす。 grid は x/y が規則格子である前提 --   (= 全 (x_i,y_j) 組が揃う・欠損は NaN 穴)。 既に行列を持っている場合は 'surface3DGrid'。+--   [English]: A column-driven 3D surface (plan C — symmetric with the 2D+--   family's 3 ColRefs x/y/z). Takes a dataframe's long form (each row is a+--   grid point (x,y,z)) and __pivots internally__ via 'resolveLayer3D' into+--   a grid mesh ('lyr3Grid' plus xRange/yRange). Assumes x/y form a regular+--   grid (every (x_i,y_j) pair is present; missing cells become NaN holes).+--   If you already have a matrix, use 'surface3DGrid' instead. -- --   @df |>> layer3D (surface3D \"x\" \"y\" \"z\" <> colormap3D)@ surface3D :: ColRef -> ColRef -> ColRef -> Layer3D@@ -361,33 +407,47 @@   , lyr3EncX = Last (Just x), lyr3EncY = Last (Just y), lyr3EncZ = Last (Just z)   } --- | 3D surface (= MSurface3D、 grid mesh・行列直入れ・旧 'surface3D')。 z 値の+-- | [日本語]: 3D surface (= MSurface3D、 grid mesh・行列直入れ・旧 'surface3D')。 z 値の --   2 次元配列を受け、 x/y 範囲は 'xRange3D'/'yRange3D' (既定 (-1,1)) で与える。+--   [English]: A 3D surface (MSurface3D, a grid mesh with the matrix passed+--   directly; the older form of 'surface3D'). Takes a 2D array of z values;+--   the x/y range is given via 'xRange3D' / 'yRange3D' (default (-1,1)). surface3DGrid :: [[Double]] -> Layer3D surface3DGrid grid = mempty   { lyr3Kind = First (Just M3Surface)   , lyr3Grid = Last  (Just grid)   } --- | Phase 25 A5: 3D bar (= M3Bar)。 'scatter3D' と対称の ColRef 3 つ (x, y,+-- | [日本語]: 3D bar (= M3Bar)。 'scatter3D' と対称の ColRef 3 つ (x, y, --   高さ z)。 各 (x,y) に底面 (data z=0) から高さ z までの棒を立てる。 既定は --   直方体 ('barStyle3D' で stick へ)。 [Point3] 直入れは 'bar3DPoints'。+--   [English]: A 3D bar (M3Bar). Symmetric with 'scatter3D': three ColRefs+--   (x, y, height z). Raises a bar at each (x,y) from the base (data z=0)+--   to height z. Defaults to a cuboid (use 'barStyle3D' to switch to a+--   stick). For direct @[Point3]@ input, use 'bar3DPoints'. bar3D :: ColRef -> ColRef -> ColRef -> Layer3D bar3D x y z = mempty   { lyr3Kind = First (Just M3Bar)   , lyr3EncX = Last (Just x), lyr3EncY = Last (Just y), lyr3EncZ = Last (Just z)   } --- | Phase 25 A5: 3D bar ([Point3] 直入れ・各点の z = 棒の高さ)。+-- | [日本語]: 3D bar ([Point3] 直入れ・各点の z = 棒の高さ)。+--   [English]: A 3D bar with @[Point3]@ passed directly (each point's z is+--   the bar's height). bar3DPoints :: [Point3] -> Layer3D bar3DPoints tops = mempty   { lyr3Kind   = First (Just M3Bar)   , lyr3Points = Last  (Just tops)   } --- | Phase 26 A4: 3D stem (= M3Stem)。 'bar3D' と対称の ColRef 3 つ (x, y, z)。+-- | [日本語]: 3D stem (= M3Stem)。 'bar3D' と対称の ColRef 3 つ (x, y, z)。 --   各 (x,y) に底面 ('stemBaseZ'・既定 0) から z までの細い垂線 + 先端マーカーを --   描く (3D lollipop)。 [Point3] 直入れは 'stem3DPoints'。 底面は 'stemBaseZ' で変更。+--   [English]: A 3D stem (M3Stem). Symmetric with 'bar3D': three ColRefs+--   (x, y, z). Draws a thin vertical line plus a tip marker (a 3D lollipop)+--   from the base ('stemBaseZ', default 0) up to z at each (x,y). For+--   direct @[Point3]@ input, use 'stem3DPoints'. Change the base with+--   'stemBaseZ'. -- --   @stem3D \"x\" \"y\" \"z\" <> color3D (fromHex \"#d62728\") <> stemBaseZ 0@ stem3D :: ColRef -> ColRef -> ColRef -> Layer3D@@ -396,21 +456,31 @@   , lyr3EncX = Last (Just x), lyr3EncY = Last (Just y), lyr3EncZ = Last (Just z)   } --- | Phase 26 A4: 3D stem ([Point3] 直入れ・各点の z = stem の先端高さ)。+-- | [日本語]: 3D stem ([Point3] 直入れ・各点の z = stem の先端高さ)。+--   [English]: A 3D stem with @[Point3]@ passed directly (each point's z is+--   the stem's tip height). stem3DPoints :: [Point3] -> Layer3D stem3DPoints tops = mempty   { lyr3Kind   = First (Just M3Stem)   , lyr3Points = Last  (Just tops)   } --- | Phase 26 A4: stem の底面 z (data 空間・既定 0)。 mplot3d @stem(..., bottom=)@ 相当。---   'normLayer3D' が axes に基づき正規化して 'lyr3BarBaseZ' へ落とす。+-- | [日本語]: stem の底面 z (data 空間・既定 0)。 mplot3d @stem(..., bottom=)@ 相当。+--   'Graphics.Hgg.ThreeD.Easy.normLayer3D' が axes に基づき正規化して 'lyr3BarBaseZ' へ落とす。+--   [English]: The stem's base z (in data space, default 0). Equivalent to+--   mplot3d's @stem(..., bottom=)@. 'Graphics.Hgg.ThreeD.Easy.normLayer3D' normalizes it based on+--   the axes and stores it into 'lyr3BarBaseZ'. stemBaseZ :: Double -> Layer3D stemBaseZ z = mempty { lyr3StemBaseZ = Last (Just z) } --- | Phase 26 A3: 3D vector field (= M3Quiver)。 各 @(位置, ベクトル)@ に矢印を描く+-- | [日本語]: 3D vector field (= M3Quiver)。 各 @(位置, ベクトル)@ に矢印を描く --   (= mplot3d @quiver@)。 矢印長は autoscale (= 最長矢印が cube の ~35%) に --   'vecScale3D' 倍を掛けた長さ。 描画は投影後の 2D 矢印 (本線 + 矢じり)。+--   [English]: A 3D vector field (M3Quiver). Draws an arrow at each+--   @(position, vector)@ (equivalent to mplot3d's @quiver@). Arrow length+--   is autoscaled (the longest arrow becomes ~35% of the cube), multiplied+--   by 'vecScale3D'. Rendering draws the projected 2D arrow (shaft plus+--   arrowhead). -- --   @quiver3D [(Point3 0 0 0, Vec3 1 0 0), (Point3 1 1 1, Vec3 0 0 1)]@ quiver3D :: [(Point3, Vec3)] -> Layer3D@@ -420,13 +490,20 @@   , lyr3Vectors = Last  (Just (map snd items))   } --- | Phase 26 A3: quiver3D 矢印長の倍率 (autoscale × この値・既定 1)。+-- | [日本語]: quiver3D 矢印長の倍率 (autoscale × この値・既定 1)。+--   [English]: The multiplier on quiver3D's arrow length (autoscale times+--   this value, default 1). vecScale3D :: Double -> Layer3D vecScale3D s = mempty { lyr3VecScale = Last (Just s) } --- | Phase 26 A5: trisurf (= M3Trisurf)。 不規則 (非 grid) な 3D 点群を (x,y) 平面で+-- | [日本語]: trisurf (= M3Trisurf)。 不規則 (非 grid) な 3D 点群を (x,y) 平面で --   Delaunay 三角分割して曲面化する (= mplot3d @plot_trisurf@)。 散らばった観測点・ --   GP 事後点など、 規則 grid でない曲面を描ける。 z 連続色は 'colormap3D' で。+--   [English]: trisurf (M3Trisurf). Surfaces an irregular (non-grid) 3D+--   point cloud by Delaunay-triangulating it in the (x,y) plane (equivalent+--   to mplot3d's @plot_trisurf@). Suited to scattered observations, GP+--   posterior points, or other surfaces that don't form a regular grid.+--   Use 'colormap3D' for a continuous z color. -- --   @trisurf pts <> colormap3D viridisStops3D@ trisurf :: [Point3] -> Layer3D@@ -435,11 +512,18 @@   , lyr3Points = Last  (Just pts)   } --- | Phase 30 A6: 列駆動の 3D テキスト注釈 (= 2D 'Graphics.Hgg.Spec.text' と対称・---   ColRef 4 つ x/y/z + **label 列**)。 df の各行を投影し、 label 列の文字を PText で+-- | [日本語]: 列駆動の 3D テキスト注釈 (= 2D 'Graphics.Hgg.Spec.text' と対称・+--   ColRef 4 つ x/y/z + __label 列__)。 df の各行を投影し、 label 列の文字を PText で --   置く。 解決は 'resolveLayer3D' (x/y/z → 'lyr3Points'・label → 'lyr3Labels')。 --   inline 生値版は 'text3DPoints'。 文字色は 'color3D' (既定 @#333333@)、 サイズは --   'size3D' (既定 11)。+--   [English]: A column-driven 3D text annotation (symmetric with 2D+--   'Graphics.Hgg.Spec.text': 4 ColRefs — x/y/z plus a __label column__).+--   Projects each row of the dataframe and places the label column's text+--   via 'Graphics.Hgg.Primitive.PText'. Resolved by 'resolveLayer3D' (x/y/z go to 'lyr3Points',+--   the label goes to 'lyr3Labels'). For the inline raw-value form, use+--   'text3DPoints'. Text color comes from 'color3D' (default @#333333@),+--   size from 'size3D' (default 11). -- --   @text3D \"x\" \"y\" \"z\" \"name\"@ text3D :: ColRef -> ColRef -> ColRef -> ColRef -> Layer3D@@ -449,10 +533,16 @@   , lyr3TextBy = Last (Just lab)   } --- | Phase 30 A6: 3D テキスト注釈 (inline・[(点, 文字列)] 直入れ・旧 'text3D')。 各+-- | [日本語]: 3D テキスト注釈 (inline・[(点, 文字列)] 直入れ・旧 'text3D')。 各 --   @(点, 文字列)@ を投影して PText を出す (depth 統合外の前面 overlay)。 位置は他 --   mark と同じ正規化 / z-aspect pipeline を通る。 'lyr3Annots' (concat Monoid) に --   積むので 'annotate3D' と @<>@ で畳める。+--   [English]: A 3D text annotation (inline, taking @[(point, string)]@+--   directly; the older form of 'text3D'). Projects each+--   @(point, string)@ pair and emits a 'Graphics.Hgg.Primitive.PText' (a front overlay outside+--   depth integration). Positions go through the same normalization /+--   z-aspect pipeline as other marks. Accumulates into 'lyr3Annots' (a+--   concat Monoid), so it folds together with 'annotate3D' via @\<\>@. -- --   @text3DPoints [(Point3 0 0 1, \"peak\"), (Point3 1 1 0, \"corner\")]@ text3DPoints :: [(Point3, Text)] -> Layer3D@@ -461,9 +551,14 @@   , lyr3Annots = items   } --- | Phase 25 A7 / 30 A6: 単一ラベルの注釈。 'lyr3Annots' に 1 件積むので---   @annotate3D a \"A\" <> annotate3D b \"B\"@ のように **畳める** (= 複数注釈が累積。+-- | [日本語]: 単一ラベルの注釈。 'lyr3Annots' に 1 件積むので+--   @annotate3D a \"A\" <> annotate3D b \"B\"@ のように __畳める__ (= 複数注釈が累積。 --   旧実装は 'lyr3Labels' が Last で後勝ち → 畳めなかった)。+--   [English]: A single-label annotation. Accumulates one entry into+--   'lyr3Annots', so it __folds together__ like+--   @annotate3D a \"A\" <> annotate3D b \"B\"@ (multiple annotations+--   accumulate; the older implementation used a 'Last' 'lyr3Labels' where+--   the last one won, so it could not fold). -- --   @annotate3D (Point3 0 0 1) \"max\" <> color3D (fromHex \"#d62728\") <> size3D 13@ annotate3D :: Point3 -> Text -> Layer3D@@ -473,67 +568,111 @@ -- per-layer 属性 (= 2D 'color' / 'size' / 'alpha' 同型、 Layer3D 返却) -- =========================================================================== --- | Phase 30 A5: 固定色 (= 2D 'Graphics.Hgg.Spec.color' 同型・型安全な 'Color')。+-- | [日本語]: 固定色 (= 2D 'Graphics.Hgg.Spec.color' 同型・型安全な 'Color')。 --   @scatter3D pts <> color3D (fromHex "#56B4E9")@。 ワイヤは従来通り Text なので --   入口で 'toCss' 変換して格納する (Render / PS / JSON は無改修)。+--   [English]: A fixed color (the counterpart of 2D+--   'Graphics.Hgg.Spec.color'; a type-safe 'Color').+--   @scatter3D pts <> color3D (fromHex "#56B4E9")@. The wire representation+--   remains Text as before, so it's converted with 'toCss' at the entry+--   point (Render / PS / JSON stay unchanged). color3D :: Color -> Layer3D color3D c = mempty { lyr3Color = Last (Just (toCss c)) } --- | 便利関数 (2D 'Graphics.Hgg.Spec.colorRGBA' の 3D 双子): 8 桁 RGBA hex+-- | [日本語]: 便利関数 (2D 'Graphics.Hgg.Spec.colorRGBA' の 3D 双子): 8 桁 RGBA hex --   (@"#rrggbbaa"@ / 4 桁 @"#rgba"@) を @color3D (fromHex …) <> alpha3D …@ に展開。 --   不正入力は 'error' (total 版は 'colorRGBA3DMaybe')。+--   [English]: A convenience function (the 3D twin of 2D+--   'Graphics.Hgg.Spec.colorRGBA'): expands an 8-digit RGBA hex+--   (@"#rrggbbaa"@, or 4-digit @"#rgba"@) into+--   @color3D (fromHex …) <> alpha3D …@. Invalid input calls 'error' (see+--   'colorRGBA3DMaybe' for a total version). colorRGBA3D :: Text -> Layer3D colorRGBA3D t = let (c, a) = fromHexA t in color3D c <> alpha3D a --- | 'colorRGBA3D' の total 版。 不正な hex は 'Nothing'。+-- | [日本語]: 'colorRGBA3D' の total 版。 不正な hex は 'Nothing'。+--   [English]: The total version of 'colorRGBA3D'. An invalid hex yields+--   'Nothing'. colorRGBA3DMaybe :: Text -> Maybe Layer3D colorRGBA3DMaybe t = (\(c, a) -> color3D c <> alpha3D a) <$> fromHexAMaybe t --- | Phase 25 A2: scatter/line を**カテゴリ列**で群色分けする (+ 離散凡例)。+-- | [日本語]: scatter/line を__カテゴリ列__で群色分けする (+ 離散凡例)。 --   @scatter3D "x" "y" "z" <> colorBy3D "group"@。 色は 2D 同型の ggplot 既定 --   palette ('ggplotHue')。 解決は 'resolveLayer3D' で点ごと色 + 凡例 mapping に --   落とす (カテゴリは初出順)。+--   [English]: Colors scatter/line by group using a __category column__+--   (plus a discrete legend). @scatter3D "x" "y" "z" <> colorBy3D "group"@.+--   Colors follow the same ggplot default palette as 2D ('ggplotHue').+--   Resolved by 'resolveLayer3D' into a per-point color plus a legend+--   mapping (categories in first-occurrence order). colorBy3D :: ColRef -> Layer3D colorBy3D c = mempty { lyr3ColorBy = Last (Just c) } --- | Phase 25 A3: scatter を**数値列**の連続色 (viridis) でマップする (+ colorbar)。---   @scatter3D "x" "y" "z" <> colorContinuousBy3D "temp"@。 A2 'colorBy3D' (カテゴリ+-- | [日本語]: scatter を__数値列__の連続色 (viridis) でマップする (+ colorbar)。+--   @scatter3D "x" "y" "z" <> colorContinuousBy3D "temp"@。 'colorBy3D' (カテゴリ --   →離散凡例) と対。 解決は 'resolveLayer3D' で点ごと色 + colorbar 情報 --   (stops, min, max) に落とす。 stops は surface と共有の 'viridisStops3D'。---   (Phase 30 A5: 2D 'colorContinuousBy' と命名対称化。 旧名 @colorByValue3D@。)+--   (2D 'Graphics.Hgg.Spec.colorContinuousBy' と命名対称化。 旧名 @colorByValue3D@。)+--   [English]: Maps scatter to a continuous color (viridis) via a+--   __numeric column__ (plus a colorbar).+--   @scatter3D "x" "y" "z" <> colorContinuousBy3D "temp"@. The counterpart+--   to 'colorBy3D' (a category to a discrete legend). Resolved by+--   'resolveLayer3D' into a per-point color plus colorbar info+--   (stops, min, max). Shares 'viridisStops3D' with surface. (Named to+--   match 2D's @colorContinuousBy@; the older name was @colorByValue3D@.) colorContinuousBy3D :: ColRef -> Layer3D colorContinuousBy3D c = mempty { lyr3ColorByV = Last (Just c) } --- | Phase 25 A3: scatter の点サイズ (= 基本半径 px) を**数値列**でマップする+-- | [日本語]: scatter の点サイズ (= 基本半径 px) を__数値列__でマップする --   (bubble chart)。 @scatter3D "x" "y" "z" <> sizeBy3D "mass"@。 px 範囲は --   既定 @(4, 18)@、 変えたい時は 'sizeRange3D'。 値→size は線形 (min→範囲下端、 --   max→範囲上端)。 depth cue は基本 size に乗算される。+--   [English]: Maps a scatter's point size (the base radius in px) via a+--   __numeric column__ (a bubble chart). @scatter3D "x" "y" "z" <>+--   sizeBy3D "mass"@. The px range defaults to @(4, 18)@; change it with+--   'sizeRange3D'. Value-to-size is linear (min maps to the range's lower+--   end, max to the upper end). The depth cue multiplies the base size. sizeBy3D :: ColRef -> Layer3D sizeBy3D c = mempty { lyr3SizeBy = Last (Just c) } --- | Phase 25 A3: 'sizeBy3D' の出力 px 範囲を明示指定 (下端, 上端)。 単独では+-- | [日本語]: 'sizeBy3D' の出力 px 範囲を明示指定 (下端, 上端)。 単独では --   無効 ('sizeBy3D' と併用)。 未指定時の既定は @(4, 18)@。+--   [English]: Explicitly sets the output px range (lower, upper) for+--   'sizeBy3D'. Has no effect on its own (use together with 'sizeBy3D').+--   Defaults to @(4, 18)@ when unspecified. sizeRange3D :: (Double, Double) -> Layer3D sizeRange3D r = mempty { lyr3SizeRange = Last (Just r) } --- | Phase 25 A5: bar スタイル (直方体 'BarCuboid' / 縦線 'BarStick')。 既定は+-- | [日本語]: bar スタイル (直方体 'BarCuboid' / 縦線 'BarStick')。 既定は --   'BarCuboid'。 @bar3D "x" "y" "z" <> barStyle3D BarStick@。+--   [English]: The bar style (cuboid 'BarCuboid' or vertical line+--   'BarStick'). Defaults to 'BarCuboid'.+--   @bar3D "x" "y" "z" <> barStyle3D BarStick@. barStyle3D :: BarStyle3D -> Layer3D barStyle3D s = mempty { lyr3BarStyle = Last (Just s) } --- | Phase 25 A5: bar footprint の半幅 (= 軸 span に対する比・既定 0.04)。+-- | [日本語]: bar footprint の半幅 (= 軸 span に対する比・既定 0.04)。 --   正規化空間 ([-1,1]) では x/y 共通でこの値がそのまま半幅になる。+--   [English]: The bar footprint's half-width (as a ratio of the axis span,+--   default 0.04). In normalized space (@[-1,1]@), this value directly+--   becomes the half-width, shared by x/y. barWidth3D :: Double -> Layer3D barWidth3D w = mempty { lyr3BarWidth = Last (Just w) } --- | Phase 25 A5: 誤差棒 (z 方向 ±err) を**数値列**で付ける。 bar・scatter+-- | [日本語]: 誤差棒 (z 方向 ±err) を__数値列__で付ける。 bar・scatter --   どちらの layer にも付けられる (頂点に縦線 + 端キャップ)。 --   @bar3D "x" "y" "z" <> errorBar3D "se"@。+--   [English]: Attaches error bars (z-direction ±err) via a __numeric column__.+--   Can be attached to either a bar or scatter layer (a vertical line plus+--   end caps at each vertex). @bar3D "x" "y" "z" <> errorBar3D "se"@. errorBar3D :: ColRef -> Layer3D errorBar3D c = mempty { lyr3ErrBy = Last (Just c) } --- | Phase 30 A5: surface 等のエッジ線色 (固定色・型安全な 'Color')。+-- | [日本語]: surface 等のエッジ線色 (固定色・型安全な 'Color')。 --   ワイヤは Text 維持 ('toCss' 変換して格納)。+--   [English]: The edge line color for surfaces and similar (a fixed,+--   type-safe 'Color'). The wire representation stays Text ('toCss' is+--   applied at the entry point). edgeColor3D :: Color -> Layer3D edgeColor3D c = mempty { lyr3EdgeColor = Last (Just (toCss c)) } @@ -549,53 +688,93 @@ shaded3D :: Bool -> Layer3D shaded3D b = mempty { lyr3Shaded = Last (Just b) } --- | Phase 24 A2: surface 面色を z 値の連続色 (viridis) にする。+-- | [日本語]: surface 面色を z 値の連続色 (viridis) にする。 --   @layer3D (surface3DGrid grid <> colormap3D)@。 stops を変えたい時は --   'colormapWith3D'。+--   [English]: Colors surface faces by a continuous z color (viridis).+--   @layer3D (surface3DGrid grid <> colormap3D)@. Use 'colormapWith3D' to+--   change the stops. colormap3D :: Layer3D colormap3D = colormapWith3D viridisStops3D --- | Phase 24 A2: 任意 gradient stops の colormap (hex 色の線形補間)。+-- | [日本語]: 任意 gradient stops の colormap (hex 色の線形補間)。+--   [English]: A colormap over arbitrary gradient stops (linear+--   interpolation between hex colors). colormapWith3D :: [Text] -> Layer3D colormapWith3D stops = mempty { lyr3Colormap = Last (Just stops) } --- | surface を**面なしの格子線メッシュ**で描く (matplotlib @plot_wireframe@ 相当)。+-- | [日本語]: surface を__面なしの格子線メッシュ__で描く (matplotlib @plot_wireframe@ 相当)。 --   @surface3D@ に @\<>@ で合成: @layer3D (surface3DGrid grid \<> surfaceWire \<> color3D (fromHex "#2563eb"))@。 --   面を塗らないので 'colormap3D' / 'shaded3D' は無効 (線色は 'color3D')。 任意エッジの --   'wireframe3D' とは別 (こちらは grid から行/列の線メッシュを自動生成)。+--   [English]: Draws a surface as a __faceless gridline mesh__ (equivalent+--   to matplotlib's @plot_wireframe@). Compose with @surface3D@ via+--   @\<\>@: @layer3D (surface3DGrid grid \<\> surfaceWire \<\> color3D+--   (fromHex "#2563eb"))@. Since no faces are filled, 'colormap3D' /+--   'shaded3D' have no effect (line color comes from 'color3D'). Distinct+--   from 'wireframe3D' (arbitrary edges); this one auto-generates the+--   row/column line mesh from the grid. surfaceWire :: Layer3D surfaceWire = mempty { lyr3SurfaceWire = Last (Just True) } --- | 投影 contour の軸 (内部表現)。 'contourX' / 'contourY' / 'contourZ' が設定する。+-- | [日本語]: 投影 contour の軸 (内部表現)。 'contourX' / 'contourY' / 'contourZ' が設定する。 --   matplotlib @contour(..., zdir=)@ 相当 (= dir に垂直な平面で曲面を切り、 壁へ投影)。+--   [English]: The axis for a projected contour (internal representation),+--   set by 'contourX' / 'contourY' / 'contourZ'. Equivalent to matplotlib's+--   @contour(..., zdir=)@ (slices the surface with a plane perpendicular to+--   @dir@ and projects it onto the wall). data ContourDir = ContourX | ContourY | ContourZ   deriving (Show, Eq, Generic) instance ToJSON   ContourDir instance FromJSON ContourDir --- | surface の **x 断面** @n@ 本を左右の壁 (yz 平面) へ投影する (matplotlib---   @contour(..., zdir='x')@ / plotly @contours.x.project@ 相当)。 x 軸を等分した+-- | [日本語]: surface の __x 断面__ @n@ 本を左右の壁 (yz 平面) へ投影する (matplotlib+--   @contour(..., zdir="x")@ / plotly @contours.x.project@ 相当)。 x 軸を等分した --   @n@ 位置で曲面を切り、 各断面プロファイル @z = f(x_k, y)@ を壁に描く。 投影壁は---   **カメラから遠い面に自動固定**。 同じ surface に 'contourY' / 'contourZ' を @\<>@ で+--   __カメラから遠い面に自動固定__。 同じ surface に 'contourY' / 'contourZ' を @\<>@ で --   合成可。 線色は colormap (既定 viridis) の x 連続色。 --   @surface3DGrid grid \<> colormap3D \<> contourX 8 \<> contourY 8 \<> contourZ 8@。+--   [English]: Projects @n@ __x cross-sections__ of a surface onto the+--   left/right walls (yz plane) (equivalent to matplotlib's+--   @contour(..., zdir="x")@ / plotly's @contours.x.project@). Slices the+--   surface at @n@ evenly spaced x positions and draws each cross-section+--   profile @z = f(x_k, y)@ on the wall. The projection wall+--   __auto-pins to the face farthest from the camera__. Composable with+--   'contourY' / 'contourZ' on the same surface via @\<\>@. Line color+--   follows the colormap's (default viridis) continuous x color.+--   @surface3DGrid grid \<\> colormap3D \<\> contourX 8 \<\> contourY 8+--   \<\> contourZ 8@. contourX :: Int -> Layer3D contourX n = mempty { lyr3Contours = [(ContourX, n)] } --- | surface の **y 断面** @n@ 本を前後の壁 (xz 平面) へ投影する (matplotlib---   @contour(..., zdir='y')@ 相当)。 各断面プロファイル @z = f(x, y_k)@ を壁に描く。+-- | [日本語]: surface の __y 断面__ @n@ 本を前後の壁 (xz 平面) へ投影する (matplotlib+--   @contour(..., zdir="y")@ 相当)。 各断面プロファイル @z = f(x, y_k)@ を壁に描く。 --   投影壁はカメラから遠い面に自動固定。 'contourX' / 'contourZ' と合成可。+--   [English]: Projects @n@ __y cross-sections__ of a surface onto the+--   front/back walls (xz plane) (equivalent to matplotlib's+--   @contour(..., zdir="y")@). Draws each cross-section profile+--   @z = f(x, y_k)@ on the wall. The projection wall auto-pins to the face+--   farthest from the camera. Composable with 'contourX' / 'contourZ'. contourY :: Int -> Layer3D contourY n = mempty { lyr3Contours = [(ContourY, n)] } --- | surface の **等高線 (z 等値面)** @n@ 本を床 (xy 平面) へ投影する (matplotlib---   @contour(..., zdir='z')@ / plotly @contours_z@ 相当)。 z 軸を等分した @n@ 値で+-- | [日本語]: surface の __等高線 (z 等値面)__ @n@ 本を床 (xy 平面) へ投影する (matplotlib+--   @contour(..., zdir="z")@ / plotly @contours_z@ 相当)。 z 軸を等分した @n@ 値で --   level set @{f = z_k}@ を抽出し床へ落とす (topographic map)。 投影面はカメラから --   遠い面 (通常は床) に自動固定。 'contourX' / 'contourY' と合成可。+--   [English]: Projects @n@ __contour lines (z level sets)__ of a surface+--   onto the floor (xy plane) (equivalent to matplotlib's+--   @contour(..., zdir="z")@ / plotly's @contours_z@). Extracts the level+--   set @{f = z_k}@ at @n@ evenly spaced z values and drops it onto the+--   floor (a topographic map). The projection face auto-pins to the face+--   farthest from the camera (usually the floor). Composable with+--   'contourX' / 'contourY'. contourZ :: Int -> Layer3D contourZ n = mempty { lyr3Contours = [(ContourZ, n)] } --- | 2D 'ColorByContinuous' と同じ viridis 5-stop (palette 共有)。+-- | [日本語]: 2D 'Graphics.Hgg.Spec.ColorByContinuous' と同じ viridis 5-stop (palette 共有)。+--   [English]: The same viridis 5-stop palette as 2D 'Graphics.Hgg.Spec.ColorByContinuous'+--   (shared). viridisStops3D :: [Text] viridisStops3D = ["#440154", "#3B528B", "#21918C", "#5EC962", "#FDE725"] @@ -609,59 +788,87 @@ -- VisualSpec3D 属性 -- =========================================================================== --- | camera 設定 (= 後勝ち、 'Last')。+-- | [日本語]: camera 設定 (= 後勝ち、 'Last')。+--   [English]: The camera setting (last one wins, 'Last'). camera :: Camera3D -> VisualSpec3D camera c = mempty { vs3Camera = Last (Just c) } --- | projection 設定。+-- | [日本語]: projection 設定。+--   [English]: The projection setting. projection :: Projection3D -> VisualSpec3D projection p = mempty { vs3Proj = Last (Just p) } --- | axes 設定。 名前は 'axes3D' で 2D 'theme' / 'facet' と同型語感。+-- | [日本語]: axes 設定。 名前は 'axes3D' で 2D 'Graphics.Hgg.Spec.theme' / 'Graphics.Hgg.Spec.facet' と同型語感。+--   [English]: The axes setting. Named 'axes3D' to echo the feel of 2D+--   'Graphics.Hgg.Spec.theme' / 'Graphics.Hgg.Spec.facet'. axes3D :: Axes3D -> VisualSpec3D axes3D a = mempty { vs3Axes = Last (Just a) } --- | title (= 2D 'title' 同型、 ただし `3D` suffix で衝突回避)。+-- | [日本語]: title (= 2D 'Graphics.Hgg.Spec.title' 同型、 ただし `3D` suffix で衝突回避)。+--   [English]: The title (the counterpart of 2D 'Graphics.Hgg.Spec.title', with a @3D@ suffix+--   to avoid a name clash). title3D :: Text -> VisualSpec3D title3D t = mempty { vs3Title = Last (Just t) } --- | Phase 24 A8: 軸名 (x, y, z) を任意指定 (既定 "x"/"y"/"z")。+-- | [日本語]: 軸名 (x, y, z) を任意指定 (既定 "x"/"y"/"z")。+--   [English]: Sets arbitrary axis names (x, y, z), defaulting to+--   "x"/"y"/"z". axisTitles3D :: Text -> Text -> Text -> VisualSpec3D axisTitles3D x y z = mempty { vs3AxisTitles = Last (Just (x, y, z)) } --- | Phase 24 A8: z 軸の box 縦横比 (正規化後の z スケール係数・既定 1)。+-- | [日本語]: z 軸の box 縦横比 (正規化後の z スケール係数・既定 1)。 --   @< 1@ で扁平、 @> 1@ で縦長。 軸 box・surface・scatter・床面 contour すべてに --   一貫適用される。+--   [English]: The z axis's box aspect ratio (the post-normalization z+--   scale factor, default 1). @< 1@ flattens, @> 1@ elongates. Applied+--   consistently to the axis box, surface, scatter, and floor contour. zAspect3D :: Double -> VisualSpec3D zAspect3D a = mempty { vs3ZAspect = Last (Just a) } --- | Phase 25 A6: 壁面 pane + gridline の on/off (mplot3d 標準の薄灰 3 壁・既定 ON)。+-- | [日本語]: 壁面 pane + gridline の on/off (mplot3d 標準の薄灰 3 壁・既定 ON)。 --   @pane3D False@ で従来の cube wireframe + tick のみに戻す。+--   [English]: Toggles the wall pane plus gridlines (mplot3d's standard+--   light-gray 3-wall style; default ON). @pane3D False@ reverts to the+--   original cube wireframe plus ticks only. pane3D :: Bool -> VisualSpec3D pane3D b = mempty { vs3Pane = Last (Just b) } --- | Phase 25 A8: x 軸の box 縦横比 (正規化後 x スケール係数・既定 1。 'zAspect3D' の x 版)。+-- | [日本語]: x 軸の box 縦横比 (正規化後 x スケール係数・既定 1。 'zAspect3D' の x 版)。+--   [English]: The x axis's box aspect ratio (the post-normalization x+--   scale factor, default 1; the x counterpart of 'zAspect3D'). xAspect3D :: Double -> VisualSpec3D xAspect3D a = mempty { vs3XAspect = Last (Just a) } --- | Phase 25 A8: y 軸の box 縦横比 (正規化後 y スケール係数・既定 1。 'zAspect3D' の y 版)。+-- | [日本語]: y 軸の box 縦横比 (正規化後 y スケール係数・既定 1。 'zAspect3D' の y 版)。+--   [English]: The y axis's box aspect ratio (the post-normalization y+--   scale factor, default 1; the y counterpart of 'zAspect3D'). yAspect3D :: Double -> VisualSpec3D yAspect3D a = mempty { vs3YAspect = Last (Just a) } --- | Phase 25 A8: 軸を log scale に (x, y, z の順で flag 指定・既定 全 False)。+-- | [日本語]: 軸を log scale に (x, y, z の順で flag 指定・既定 全 False)。 --   log 軸はデータが正の前提 (非正は 1e-12 に clamp)。 tick は 10 の冪 (decade)、 --   ラベルは元の値のまま。 surface の log-z は対応、 surface の log-x/y は現状未対応 --   (point 系 mark = scatter/line/bar は全軸 log 可)。 -- --   @logScale3D False False True@  -- z だけ log (片対数)+--   [English]: Switches axes to a log scale (flags given in x, y, z order,+--   default all False). A log axis assumes positive data (non-positive+--   values are clamped to 1e-12). Ticks are powers of 10 (decades); labels+--   still show the original value. surface's log-z is supported, but+--   surface's log-x/y is not currently (point-style marks — scatter/line/+--   bar — support log on every axis).+--+--   @logScale3D False False True@  -- only z is log (semi-log) logScale3D :: Bool -> Bool -> Bool -> VisualSpec3D logScale3D x y z = mempty { vs3Log = Last (Just (x, y, z)) } --- | width (= canvas 幅 px、 2D 'vsWidth' 同型)。+-- | [日本語]: width (= canvas 幅 px、 2D 'Graphics.Hgg.Spec.vsWidth' 同型)。+--   [English]: The canvas width in px (the counterpart of 2D 'Graphics.Hgg.Spec.vsWidth'). width3DV :: Int -> VisualSpec3D width3DV w = mempty { vs3Width = Last (Just w) } --- | height (= canvas 高さ px、 2D 'vsHeight' 同型)。+-- | [日本語]: height (= canvas 高さ px、 2D 'Graphics.Hgg.Spec.vsHeight' 同型)。+--   [English]: The canvas height in px (the counterpart of 2D 'Graphics.Hgg.Spec.vsHeight'). height3DV :: Int -> VisualSpec3D height3DV h = mempty { vs3Height = Last (Just h) } @@ -670,8 +877,11 @@ -- (= render 経路で使う。 defaults は existing default*3D 関数を経由) -- =========================================================================== --- | Layer3D (= rolled-up) を 'Scatter3D' に変換。 'lyr3Kind' が 'M3Scatter' か+-- | [日本語]: Layer3D (= rolled-up) を 'Scatter3D' に変換。 'lyr3Kind' が 'M3Scatter' か --   未指定の時に意味あり。 必須項目 ('lyr3Points') 未指定なら空 points で。+--   [English]: Converts a rolled-up Layer3D into a 'Scatter3D'. Meaningful+--   when 'lyr3Kind' is 'M3Scatter' or unspecified. If the required+--   'lyr3Points' is unset, uses an empty points list. layerToScatter :: Layer3D -> Scatter3D layerToScatter l =   let pts = fromMaybe [] (getLast (lyr3Points l))@@ -718,9 +928,14 @@        , sf3Wire      = fromMaybe (sf3Wire      base) (getLast (lyr3SurfaceWire l))        } --- | Phase 25 A5: Layer3D を 'Bar3D' に変換 (render 経路で使う)。 点・base・half-width---   は正規化済前提 ('normLayer3D' / 'scaleZLayer' が事前に処理)。 base は+-- | [日本語]: Layer3D を 'Bar3D' に変換 (render 経路で使う)。 点・base・half-width+--   は正規化済前提 ('Graphics.Hgg.ThreeD.Easy.normLayer3D' / @scaleZLayer@ が事前に処理)。 base は --   'lyr3BarBaseZ' (未設定なら 0)、 半幅は 'lyr3BarWidth' (既定 0.04)。+--   [English]: Converts a Layer3D into a 'Bar3D' (used on the render path).+--   Points, base, and half-width are assumed already normalized+--   ('Graphics.Hgg.ThreeD.Easy.normLayer3D' / @scaleZLayer@ handle this beforehand). base comes from+--   'lyr3BarBaseZ' (0 if unset); half-width from 'lyr3BarWidth' (default+--   0.04). layerToBar :: Layer3D -> Bar3D layerToBar l =   let tops = fromMaybe [] (getLast (lyr3Points l))@@ -734,10 +949,16 @@        , br3Width = fromMaybe (br3Width base) (getLast (lyr3Width    l))        } --- | Phase 26 A3: Layer3D を 'Quiver3D' に変換 (render 経路で使う)。 点・ベクトルは---   正規化済前提 ('normLayer3D' / 'scaleAspectLayer' が事前に処理)。 autoscale =+-- | [日本語]: Layer3D を 'Quiver3D' に変換 (render 経路で使う)。 点・ベクトルは+--   正規化済前提 ('Graphics.Hgg.ThreeD.Easy.normLayer3D' / 'Graphics.Hgg.ThreeD.Easy.scaleAspectLayer' が事前に処理)。 autoscale = --   最長矢印が cube の 35% になるよう正規化ベクトル長で割り、 'lyr3VecScale' を掛ける。 --   終点 = 始点 + scale × vec。+--   [English]: Converts a Layer3D into a 'Quiver3D' (used on the render+--   path). Points and vectors are assumed already normalized+--   ('Graphics.Hgg.ThreeD.Easy.normLayer3D' / 'Graphics.Hgg.ThreeD.Easy.scaleAspectLayer' handle this beforehand). autoscale+--   divides by the normalized vector length so the longest arrow becomes+--   35% of the cube, then multiplies by 'lyr3VecScale'. End point = start+--   point + scale × vec. layerToQuiver :: Layer3D -> Quiver3D layerToQuiver l =   let starts = fromMaybe [] (getLast (lyr3Points  l))@@ -759,14 +980,23 @@ -- autoAxes3D: layers から bounding box を自動算出 (= Axes 未指定時の default) -- =========================================================================== --- | 全 layer の Point3 集合から min/max を取って 'Axes3D' を生成。+-- | [日本語]: 全 layer の Point3 集合から min/max を取って 'Axes3D' を生成。 --   surface3D は xRange/yRange + grid 高さで Point3 集合を構成。 --   layer が無い / 全 Point3 が空なら 'defaultAxes3D' (= 単位 cube)。--- Phase 24 A6: 列参照 (Enc) の解決 — Resolver で [Point3] に落とす+--   [English]: Generates an 'Axes3D' by taking min/max over every layer's+--   Point3 set. For surface3D, the Point3 set is built from xRange/yRange+--   plus the grid heights. With no layers, or every Point3 set empty, falls+--   back to 'defaultAxes3D' (the unit cube).+-- 列参照 (Enc) の解決 — Resolver で [Point3] に落とす --- | 層の列参照 (x,y,z) を 'Resolver' で解決して 'lyr3Points' に格納する。---   3 列が全部解決できた時だけ上書き ('inline' 生値は resolver 不要で解決)。+-- | [日本語]: 層の列参照 (x,y,z) を 'Resolver' で解決して 'lyr3Points' に格納する。+--   3 列が全部解決できた時だけ上書き ('Graphics.Hgg.Spec.inline' 生値は resolver 不要で解決)。 --   Enc が無い層 (旧 [Point3] 直入れ・surface 等) は素通し。+--   [English]: Resolves a layer's column references (x,y,z) via a+--   'Resolver' and stores the result into 'lyr3Points'. Only overwrites+--   when all three columns resolve successfully ('Graphics.Hgg.Spec.inline' raw values need+--   no resolver and are already resolved). Layers with no Enc (older direct+--   @[Point3]@ input, surface, etc.) pass through unchanged. resolveLayer3D :: Resolver -> Layer3D -> Layer3D resolveLayer3D r l0 =   let -- まず x/y/z 列参照 → 点列 (Phase 24 A6)@@ -799,9 +1029,13 @@         _ -> l1   in resolveTextBy3D r (resolveErrBy3D r (resolveSizeBy3D r (resolveColorByValue3D r (resolveColorBy3D r l2)))) --- | Phase 30 A6: 列駆動 'text3D' の label 列 ('lyr3TextBy') を解決して 'lyr3Labels' に+-- | [日本語]: 列駆動 'text3D' の label 列 ('lyr3TextBy') を解決して 'lyr3Labels' に --   落とす (x/y/z は 'resolveLayer3D' 先頭で既に 'lyr3Points' へ解決済)。 数値列は --   'numLabel' で文字化。 未指定なら素通し。+--   [English]: Resolves the label column ('lyr3TextBy') of a column-driven+--   'text3D' into 'lyr3Labels' (x/y/z were already resolved into+--   'lyr3Points' earlier in 'resolveLayer3D'). A numeric column is+--   stringified via 'numLabel'. Passes through unchanged when unset. resolveTextBy3D :: Resolver -> Layer3D -> Layer3D resolveTextBy3D r l = case getLast (lyr3TextBy l) of   Nothing -> l@@ -812,10 +1046,17 @@           Nothing          -> []     in if null labels then l else l { lyr3Labels = Last (Just labels) } --- | Phase 30 A6: surface 案C の pivot。 long 形の (xs, ys, zs) を規則格子の z 行列に+-- | [日本語]: surface 案C の pivot。 long 形の (xs, ys, zs) を規則格子の z 行列に --   畳む。 列 = x の昇順 unique・行 = y の昇順 unique。 grid[i][j] = (uxs[j], uys[i]) の z。 --   欠損 (組が無い) セルは NaN (= 描画穴)。 重複 (同一 (x,y)) は後勝ち。 xRange/yRange は --   実データの min/max (空なら (-1,1))。+--   [English]: The pivot for surface plan C. Folds the long-form+--   (xs, ys, zs) into a regular-grid z matrix. Columns are x's ascending+--   uniques, rows are y's ascending uniques; @grid[i][j]@ is the z at+--   @(uxs[j], uys[i])@. Missing cells (no matching pair) become NaN+--   (rendering holes). Duplicates (same (x,y)) let the last one win.+--   xRange/yRange come from the actual data's min/max (defaulting to+--   (-1,1) when empty). pivotSurface3D :: [Double] -> [Double] -> [Double]                -> ([[Double]], (Double, Double), (Double, Double)) pivotSurface3D xs ys zs =@@ -827,8 +1068,12 @@       rng vs = if null vs then (-1, 1) else (minimum vs, maximum vs)   in (grid, rng xs, rng ys) --- | Phase 25 A2: カテゴリ列 ('lyr3ColorBy') を解決して点ごと色 + 凡例 mapping に+-- | [日本語]: カテゴリ列 ('lyr3ColorBy') を解決して点ごと色 + 凡例 mapping に --   落とす。 カテゴリは初出順、 色は 'ggplotHue'。 未指定なら素通し。+--   [English]: Resolves the category column ('lyr3ColorBy') into a+--   per-point color plus a legend mapping. Categories are in+--   first-occurrence order; colors come from 'ggplotHue'. Passes through+--   unchanged when unset. resolveColorBy3D :: Resolver -> Layer3D -> Layer3D resolveColorBy3D r l = case getLast (lyr3ColorBy l) of   Nothing -> l@@ -845,10 +1090,16 @@        else l { lyr3PtColors = Last (Just (map colOf labels))               , lyr3Legend   = Last (Just catColor) } --- | Phase 25 A3: 数値列 ('lyr3ColorByV') を解決して点ごと連続色 (viridis) ++-- | [日本語]: 数値列 ('lyr3ColorByV') を解決して点ごと連続色 (viridis) + --   colorbar 情報 (stops, min, max) に落とす。 値→色は線形正規化 + 'continuousColor' --   (= 2D gradient 凡例 / surface colormap と同じ補間)。 未指定 / 非数値なら素通し。 --   全値同一なら中央色 (t=0.5)。+--   [English]: Resolves the numeric column ('lyr3ColorByV') into a+--   per-point continuous color (viridis) plus colorbar info+--   (stops, min, max). Value-to-color is a linear normalization followed+--   by 'continuousColor' (the same interpolation as the 2D gradient legend+--   / surface colormap). Passes through unchanged when unset or+--   non-numeric. Uses the middle color (t=0.5) when every value is equal. resolveColorByValue3D :: Resolver -> Layer3D -> Layer3D resolveColorByValue3D r l = case getLast (lyr3ColorByV l) of   Nothing -> l@@ -865,10 +1116,15 @@         in l { lyr3PtColors = Last (Just cols)              , lyr3Colorbar = Last (Just (stops, vMin, vMax)) } --- | Phase 25 A3: 数値列 ('lyr3SizeBy') を解決して点ごと基本 size (px) に落とす+-- | [日本語]: 数値列 ('lyr3SizeBy') を解決して点ごと基本 size (px) に落とす --   (bubble)。 値→size は線形 (min→範囲下端、 max→範囲上端)。 範囲は --   'lyr3SizeRange' (既定 (4, 18))。 未指定 / 非数値なら素通し。 全値同一なら --   範囲の中点。+--   [English]: Resolves the numeric column ('lyr3SizeBy') into a per-point+--   base size in px (a bubble chart). Value-to-size is linear (min maps to+--   the range's lower end, max to the upper end). The range comes from+--   'lyr3SizeRange' (default (4, 18)). Passes through unchanged when unset+--   or non-numeric. Uses the range's midpoint when every value is equal. resolveSizeBy3D :: Resolver -> Layer3D -> Layer3D resolveSizeBy3D r l = case getLast (lyr3SizeBy l) of   Nothing -> l@@ -884,9 +1140,13 @@                        else sLo + (sHi - sLo) * (x - vMin) / (vMax - vMin)         in l { lyr3PtSizes = Last (Just (map sizeOf vals)) } --- | Phase 25 A5: 誤差棒の err 列 ('lyr3ErrBy') を解決して点ごと err (data 単位)+-- | [日本語]: 誤差棒の err 列 ('lyr3ErrBy') を解決して点ごと err (data 単位) --   に落とす。 正規化 (data z → [-1,1]) は 'Easy.normLayer3D' が行う。 未指定 / --   非数値なら素通し。+--   [English]: Resolves the error-bar err column ('lyr3ErrBy') into a+--   per-point err (in data units). Normalization (data z to @[-1,1]@) is+--   handled by 'Easy.normLayer3D'. Passes through unchanged when unset or+--   non-numeric. resolveErrBy3D :: Resolver -> Layer3D -> Layer3D resolveErrBy3D r l = case getLast (lyr3ErrBy l) of   Nothing -> l@@ -896,13 +1156,17 @@                in if null vals then l                   else l { lyr3PtErrs = Last (Just vals) } --- | 数値カテゴリのラベル整形 (整数なら末尾 .0 を落とす)。+-- | [日本語]: 数値カテゴリのラベル整形 (整数なら末尾 .0 を落とす)。+--   [English]: Formats a numeric category label (strips a trailing .0 for+--   integers). numLabel :: Double -> Text numLabel x =   let r = fromIntegral (round x :: Int) :: Double   in if r == x then T.pack (show (round x :: Int)) else T.pack (show x) --- | spec 内の全層の列参照を解決する ('saveSVG3DBound' / bind 経路の正本)。+-- | [日本語]: spec 内の全層の列参照を解決する (@saveSVG3DBound@ / bind 経路の正本)。+--   [English]: Resolves the column references in every layer of a spec (the+--   canonical implementation for @saveSVG3DBound@ / bind path). resolveSpec3D :: Resolver -> VisualSpec3D -> VisualSpec3D resolveSpec3D r spec = spec { vs3Layers = map (resolveLayer3D r) (vs3Layers spec) } @@ -926,9 +1190,13 @@               , axesXLog = False, axesYLog = False, axesZLog = False               } --- | Layer から Point3 集合を抽出 (= Surface は grid を Point3 に展開)。--- Phase 25 A5: bar は底面 (z=0) を、 誤差棒のある層は z±err 端を含めて axis box が--- 棒の根本 / whisker を覆うようにする。+-- | [日本語]: Layer から Point3 集合を抽出 (= Surface は grid を Point3 に展開)。+--   bar は底面 (z=0) を、 誤差棒のある層は z±err 端を含めて axis box が+--   棒の根本 / whisker を覆うようにする。+--   [English]: Extracts a layer's Point3 set (for Surface, expands the grid+--   into points). For a bar, includes the base (z=0); for a layer with+--   error bars, includes the z±err endpoints, so the axis box covers the+--   bar's foot / whiskers too. layerPoints :: Layer3D -> [Point3] layerPoints l = case getFirst (lyr3Kind l) of   Just M3Surface ->@@ -966,7 +1234,9 @@   _ -> let pts = fromMaybe [] (getLast (lyr3Points l))        in pts ++ errPoints l pts --- | Phase 25 A5: 誤差棒のある層で、 各点の z±err 端点を返す (axis box 拡張用)。+-- | [日本語]: 誤差棒のある層で、 各点の z±err 端点を返す (axis box 拡張用)。+--   [English]: For a layer with error bars, returns each point's z±err+--   endpoints (used to extend the axis box). errPoints :: Layer3D -> [Point3] -> [Point3] errPoints l pts = case getLast (lyr3PtErrs l) of   Nothing -> []
src/Graphics/Hgg/ThreeD/Surface.hs view
@@ -1,11 +1,15 @@ -- | -- Module      : Graphics.Hgg.ThreeD.Surface--- Description : 3D surface plot (grid mesh + painter's algorithm) (Phase 3 A7)+-- Description : 3D surface plot (grid mesh + painter's algorithm) -- Copyright   : (c) 2026 Aelysce Project (Toshiaki Honda) -- License     : BSD-3-Clause ----- 規則 grid (= 2D 配列の z 値) から triangle mesh を作り、 painter's algorithm--- で奥から塗り潰す。 optional Lambert shading (= flat、 1 directional light)。+-- [日本語]: 規則 grid (= 2D 配列の z 値) から triangle mesh を作り、+--   painter's algorithm で奥から塗り潰す。 optional Lambert shading+--   (= flat、 1 directional light)。+-- [English]: Builds a triangle mesh from a regular grid (a 2D array of z+--   values) and fills it back-to-front with the painter's algorithm.+--   Optional Lambert shading (flat, one directional light). {-# LANGUAGE DeriveGeneric     #-} {-# LANGUAGE OverloadedStrings #-} module Graphics.Hgg.ThreeD.Surface@@ -41,17 +45,30 @@   , sf3EdgeColor  :: !Text   , sf3Shaded     :: !Bool   , sf3Colormap   :: !(Maybe [Text])-    -- ^ Phase 24 A2: @Just stops@ で面色を z 値の連続色 (gradient stops の-    --   線形補間・2D 'ColorByContinuous' と同じ 'continuousColor') にする。+    -- ^ [日本語]: @Just stops@ で面色を z 値の連続色 (gradient stops の+    --   線形補間・2D 'Graphics.Hgg.Spec.ColorByContinuous' と同じ 'continuousColor') にする。     --   @Nothing@ = 従来の単色 'sf3Color' (後方互換)。+    --   [English]: With @Just stops@, face colors follow a continuous z+    --   colormap (linear interpolation of gradient stops, via the same+    --   'continuousColor' used by 2D 'Graphics.Hgg.Spec.ColorByContinuous'). @Nothing@ means+    --   the traditional single color 'sf3Color' (backward compatible).   , sf3Alpha      :: !Double-    -- ^ Phase 25 A4: 面の不透明度 (0..1・既定 1)。 @< 1@ で半透明 surface。-    --   face は depth ソート済 (painter's・A8 で scatter と大域マージ) なので+    -- ^ [日本語]: 面の不透明度 (0..1・既定 1)。 @< 1@ で半透明 surface。+    --   face は depth ソート済 (painter's・scatter と大域マージ) なので     --   back-to-front 合成が正しく重なる。+    --   [English]: The face opacity (0..1, default 1). @< 1@ gives a+    --   semi-transparent surface. Faces are already depth-sorted (via the+    --   painter's algorithm, merged globally with scatter), so+    --   back-to-front compositing layers correctly.   , sf3Wire       :: !Bool-    -- ^ surfaceWire: 面を塗らず grid の行/列を**格子線メッシュ**で描く+    -- ^ [日本語]: surfaceWire: 面を塗らず grid の行/列を__格子線メッシュ__で描く     --   (matplotlib @plot_wireframe@ 相当)。 True で fill 三角形の代わりに     --   隣接 grid 点を結ぶ line segment を depth 統合付きで返す。 colormap/shaded は無効。+    --   [English]: surfaceWire: rather than filling faces, draws the grid's+    --   rows/columns as a __gridline mesh__ (equivalent to matplotlib's+    --   @plot_wireframe@). When True, returns line segments connecting+    --   adjacent grid points (with depth integration) instead of filled+    --   triangles. colormap / shaded are disabled.   } deriving (Show, Eq, Generic) instance ToJSON   Surface3D instance FromJSON Surface3D@@ -92,9 +109,13 @@ renderSurface3D cam proj vp sf =   map snd (sortOn (negate . fst) (surfaceFacesDepth cam proj vp sf)) --- | Phase 24 A8 (depth 統合): 各 face を @(投影 depth, poly Primitive)@ として--- **未ソート**で返す (= 'renderSurface3D' は @sortOn (negate.fst)@ してから--- @map snd@・ビット同一)。 scatter 点との層横断 depth 統合に使う。+-- | [日本語]: (depth 統合): 各 face を @(投影 depth, poly Primitive)@ として+--   __未ソート__で返す (= 'renderSurface3D' は @sortOn (negate.fst)@ してから+--   @map snd@・ビット同一)。 scatter 点との層横断 depth 統合に使う。+--   [English]: (depth integration): Returns each face as+--   @(projected depth, poly Primitive)@, __unsorted__ ('renderSurface3D' is+--   bit-identical to @sortOn (negate . fst)@ followed by @map snd@ of this).+--   Used to integrate depth across layers with scatter points. surfaceFacesDepth   :: Camera3D -> Projection3D -> Viewport   -> Surface3D -> [(Double, Primitive)]@@ -180,18 +201,30 @@     subAsVec (Point3 bx by bz) (Point3 ax ay az) =       Vec3 (bx - ax) (by - ay) (bz - az) --- | Phase 26 A5 (trisurf): grid 非依存に **任意の三角形 face 列**を depth +--- Lambert shading + (任意) colormap で @(投影 depth, PPath)@ 化する--- ('surfaceFacesDepth' の grid 専用ロジックを triangle 列入力へ一般化した版)。--- 頂点は正規化済前提。 colormap の z range は全 face 頂点 z から算出。+-- | [日本語]: (trisurf): grid 非依存に __任意の三角形 face 列__を depth ++--   Lambert shading + (任意) colormap で @(投影 depth, PPath)@ 化する+--   ('surfaceFacesDepth' の grid 専用ロジックを triangle 列入力へ一般化した版)。+--   頂点は正規化済前提。 colormap の z range は全 face 頂点 z から算出。+--   [English]: (trisurf): Converts an __arbitrary list of triangle faces__+--   (not tied to a grid) into @(projected depth, PPath)@ using depth,+--   Lambert shading, and an optional colormap (a generalization of the+--   grid-specific logic in 'surfaceFacesDepth' to a list-of-triangles+--   input). Vertices are assumed already normalized. The colormap's z range+--   is computed from every face vertex's z. trianglesFacesDepth   :: Camera3D -> Projection3D -> Viewport-  -> Text          -- ^ 基本色 (colormap 無しの単色)-  -> Text          -- ^ edge 色-  -> Bool          -- ^ shaded (Lambert)-  -> Maybe [Text]  -- ^ colormap stops (Just で z 連続色)-  -> Double        -- ^ alpha-  -> [(Point3, Point3, Point3)]  -- ^ 三角形 (正規化済)+  -> Text          -- ^ [日本語]: 基本色 (colormap 無しの単色)+                    --   [English]: The base color (a single color when no colormap is used).+  -> Text          -- ^ [日本語]: edge 色+                    --   [English]: The edge color.+  -> Bool          -- ^ [日本語]: shaded (Lambert)+                    --   [English]: Whether to apply Lambert shading.+  -> Maybe [Text]  -- ^ [日本語]: colormap stops (Just で z 連続色)+                    --   [English]: The colormap stops (@Just@ gives a continuous z colormap).+  -> Double        -- ^ [日本語]: alpha+                    --   [English]: The alpha (opacity).+  -> [(Point3, Point3, Point3)]  -- ^ [日本語]: 三角形 (正規化済)+                                  --   [English]: The triangles (already normalized).   -> [(Double, Primitive)] trianglesFacesDepth cam proj vp baseCol edgeCol shaded cmap alpha triangles =   let project = project3D cam proj vp
src/Graphics/Hgg/ThreeD/Types.hs view
@@ -1,15 +1,25 @@ -- | -- Module      : Graphics.Hgg.ThreeD.Types--- Description : 3D の中核型 (Point3 / Vec3 / Mat4 / Camera3D / Projection3D)+-- Description : Core 3D types (Point3 / Vec3 / Mat4 / Camera3D / Projection3D) -- Copyright   : (c) 2026 Aelysce Project (Toshiaki Honda) -- License     : BSD-3-Clause ----- Phase 3 A2 段階: 中核型のみ定義。 関数 (= project3D / lookAt 等) は A3--- (Graphics.Hgg.ThreeD.Projection) で実装。+-- [日本語]: 中核型のみを定義する。 関数 (= project3D / lookAt 等) は+--   Graphics.Hgg.ThreeD.Projection で実装する。 ----- 設計判断: linear package 等の外部依存を入れず、 自前 Vec3 / Mat4 を持つ。--- これで core (= base/vector/text/containers のみ) の依存戦略と整合する。--- 行列演算は 4x4 で十分 (= camera + projection)、 性能不問の前提で simple list 実装。+--   設計判断: linear package 等の外部依存を入れず、 自前 Vec3 / Mat4 を持つ。+--   これで core (= base/vector/text/containers のみ) の依存戦略と整合する。+--   行列演算は 4x4 で十分 (= camera + projection)、 性能不問の前提で simple+--   list 実装。+-- [English]: Defines only the core types here. Functions (@project3D@,+--   @lookAt@, etc.) are implemented in Graphics.Hgg.ThreeD.Projection.+--+--   Design decision: rather than depending on an external package such as+--   @linear@, this module carries its own 'Vec3' / 'Mat4'. This keeps it+--   consistent with core's dependency strategy (base/vector/text/containers+--   only). 4x4 matrices are enough for camera + projection math, and since+--   performance is not a concern here, they are implemented as a simple+--   list of fields. {-# LANGUAGE DeriveGeneric #-} module Graphics.Hgg.ThreeD.Types   ( -- * 幾何プリミティブ@@ -26,7 +36,7 @@   , yUp   , defaultCameraZUp   , defaultCameraYUp-    -- * 視点 preset (Phase 24 A8・z-up)+    -- * 視点 preset (z-up)   , cameraIso   , cameraTop   , cameraFront@@ -45,65 +55,99 @@ import           Data.Aeson      (FromJSON, ToJSON) import           GHC.Generics    (Generic) --- | 3D 点 (= world / camera space どちらでも使う)。+-- | [日本語]: 3D 点 (= world / camera space どちらでも使う)。 ----- JSON: positional fields → array `[x, y, z]` (= aeson Generic デフォルト挙動)。--- PS Argonaut 側も同形式で decode。+--   JSON: positional fields → array `[x, y, z]` (= aeson Generic デフォルト挙動)。+--   PS Argonaut 側も同形式で decode。+--   [English]: A 3D point (used for both world space and camera space).+--+--   JSON: positional fields serialize to the array @[x, y, z]@ (aeson's+--   default Generic behavior). The PureScript Argonaut side decodes the+--   same format. data Point3 = Point3 !Double !Double !Double   deriving (Show, Eq, Generic) instance ToJSON   Point3 instance FromJSON Point3 --- | 3D ベクトル (= 方向 / 法線)。 JSON は Point3 と同じく array `[x, y, z]`。+-- | [日本語]: 3D ベクトル (= 方向 / 法線)。 JSON は Point3 と同じく array `[x, y, z]`。+--   [English]: A 3D vector (a direction or normal). Serializes to JSON as the+--   same array `[x, y, z]` as 'Point3'. data Vec3 = Vec3 !Double !Double !Double   deriving (Show, Eq, Generic) instance ToJSON   Vec3 instance FromJSON Vec3  -- $convention--- hgg-3d は **z-up と y-up の両方をサポート** する。 default は **z-up**--- (= mplot3d / 工学慣例)。 user は用途に応じて切替可能。+-- [日本語]: hgg-3d は __z-up と y-up の両方をサポート__ する。 default は+--   __z-up__ (= mplot3d / 工学慣例)。 user は用途に応じて切替可能。 ----- == 業界の使い分け (= 2026 時点の調査)+--   == 業界の使い分け (= 2026 時点の調査) ----- +-------------------+-------------------------------------------------+------------+--- | 分野              | 主要ライブラリ                                  | up         |--- +===================+=================================================+============+--- | __data viz__      | matplotlib mplot3d / plotly / Mathematica /     | __z-up__   |--- |                   | gnuplot splot / mayavi / R rgl                  |            |--- +-------------------+-------------------------------------------------+------------+--- | __工学 / CAD__    | AutoCAD / SolidWorks / Inventor / Unreal Engine | __z-up__   |--- +-------------------+-------------------------------------------------+------------+--- | __game / CG__     | Unity / Godot 3D / OpenGL 慣例 / DirectX 慣例   | __y-up__   |--- +-------------------+-------------------------------------------------+------------+--- | __3D modeling__   | Blender (= z-up に切替済) / Maya (= y-up)       | 分裂       |--- +-------------------+-------------------------------------------------+------------++--   [data viz]: __z-up__ — matplotlib mplot3d、 plotly、 Mathematica、+--     gnuplot splot、 mayavi、 R rgl+--   [工学 / CAD]: __z-up__ — AutoCAD、 SolidWorks、 Inventor、 Unreal Engine+--   [game / CG]: __y-up__ — Unity、 Godot 3D、 OpenGL 慣例、 DirectX 慣例+--   [3D modeling]: 分裂 — Blender (= z-up に切替済) と Maya (= y-up) で割れている ----- == hgg-3d の default = z-up を選んだ理由+--   == hgg-3d の default = z-up を選んだ理由 ----- * メインターゲットが data viz (= hanalyze の HBM 等)--- * mplot3d / plotly の慣例と整合 → 既存 user 知識が活きる--- * @z = f(x, y)@ の数学慣例と一致 → 'Graphics.Hgg.ThreeD.Surface.Surface3D' が直感的--- * z = 物理量の高さ (= 確率密度 / 計測値 / 標高) の伝統+--   * メインターゲットが data viz (= hanalyze の HBM 等)+--   * mplot3d / plotly の慣例と整合 → 既存 user 知識が活きる+--   * @z = f(x, y)@ の数学慣例と一致 → 'Graphics.Hgg.ThreeD.Surface.Surface3D' が直感的+--   * z = 物理量の高さ (= 確率密度 / 計測値 / 標高) の伝統 ----- == どちらを使うべきか+--   == どちらを使うべきか ----- * __z-up__ ('zUp' / 'defaultCameraZUp'): data viz、 工学解析、 統計分布、 surface plot。 default 推奨--- * __y-up__ ('yUp' / 'defaultCameraYUp'): game-like view、 OpenGL 系 sample との整合、 既存 CG 知識の流用+--   * __z-up__ ('zUp' / 'defaultCameraZUp'): data viz、 工学解析、 統計分布、 surface plot。 default 推奨+--   * __y-up__ ('yUp' / 'defaultCameraYUp'): game-like view、 OpenGL 系 sample との整合、 既存 CG 知識の流用 ----- 両 helper とも up vector を切替えるだけで他は同じ。 任意の up vector が必要なら--- 'Camera3D' を直接構築する。+--   両 helper とも up vector を切替えるだけで他は同じ。 任意の up vector が必要なら+--   'Camera3D' を直接構築する。+--+-- [English]: hgg-3d supports __both z-up and y-up__ conventions. The+--   default is __z-up__ (following the mplot3d / engineering convention);+--   users may switch depending on the use case.+--+--   == Industry conventions (as surveyed in 2026)+--+--   [data viz]: __z-up__ — matplotlib mplot3d, plotly, Mathematica,+--     gnuplot splot, mayavi, R rgl+--   [engineering / CAD]: __z-up__ — AutoCAD, SolidWorks, Inventor, Unreal Engine+--   [game / CG]: __y-up__ — Unity, Godot 3D, the OpenGL convention, the DirectX convention+--   [3D modeling]: split — Blender (switched to z-up) and Maya (y-up) disagree+--+--   == Why hgg-3d chose z-up as the default+--+--   * The main target is data viz (e.g. HBM plots in hanalyze)+--   * It matches the mplot3d / plotly convention, so existing user knowledge transfers+--   * It matches the mathematical convention @z = f(x, y)@, making 'Graphics.Hgg.ThreeD.Surface.Surface3D' intuitive+--   * z as the height of a physical quantity (probability density, a measurement, elevation) is a long-standing tradition+--+--   == Which one should you use+--+--   * __z-up__ ('zUp' / 'defaultCameraZUp'): data viz, engineering analysis, statistical distributions, surface plots. Recommended default+--   * __y-up__ ('yUp' / 'defaultCameraYUp'): game-like views, alignment with OpenGL-style samples, reuse of existing CG knowledge+--+--   Both helpers only swap the up vector; everything else stays the same. If+--   an arbitrary up vector is needed, construct 'Camera3D' directly. --- | z-up convention の up vector (= 'Vec3' 0 0 1)。 data viz / mplot3d 慣例。+-- | [日本語]: z-up convention の up vector (= 'Vec3' 0 0 1)。 data viz / mplot3d 慣例。+--   [English]: The up vector for the z-up convention ('Vec3' 0 0 1). Follows+--   the data-viz / mplot3d convention. zUp :: Vec3 zUp = Vec3 0 0 1 --- | y-up convention の up vector (= 'Vec3' 0 1 0)。 OpenGL / game engine 慣例。+-- | [日本語]: y-up convention の up vector (= 'Vec3' 0 1 0)。 OpenGL / game engine 慣例。+--   [English]: The up vector for the y-up convention ('Vec3' 0 1 0). Follows+--   the OpenGL / game-engine convention. yUp :: Vec3 yUp = Vec3 0 1 0 --- | z-up convention の default camera。 'eye' を data の bounding box 寄りに配置し、--- 'target' を原点、 up を 'zUp' に。 typical 3/4 view (= 上前方斜め見下ろし)。+-- | [日本語]: z-up convention の default camera。 @eye@ を data の bounding box 寄りに配置し、+--   @target@ を原点、 up を 'zUp' に。 typical 3/4 view (= 上前方斜め見下ろし)。+--   [English]: The default camera for the z-up convention. Places @eye@ near+--   the data's bounding box, sets @target@ to the origin, and up to 'zUp' —+--   a typical 3/4 view (looking down diagonally from the front). -- -- @ -- defaultCameraZUp 5  -- camera at (5, -5, 3) → origin (= 上前方斜め)@@ -115,9 +159,13 @@   , cameraUp     = zUp   } --- | y-up convention の default camera。 OpenGL / Unity 慣例に整合する 3/4 view--- (= y = 縦軸、 x/z = 床平面、 camera は y > 0 から見下ろし)。 game-like 表現や--- OpenGL 系 sample との整合に。+-- | [日本語]: y-up convention の default camera。 OpenGL / Unity 慣例に整合する 3/4 view+--   (= y = 縦軸、 x/z = 床平面、 camera は y > 0 から見下ろし)。 game-like 表現や+--   OpenGL 系 sample との整合に。+--   [English]: The default camera for the y-up convention. A 3/4 view+--   consistent with the OpenGL / Unity convention (y is the vertical axis,+--   x/z form the ground plane, and the camera looks down from y > 0). Useful+--   for game-like presentations or alignment with OpenGL-style samples. -- -- @ -- defaultCameraYUp 5  -- camera at (5, 3, 5) → origin (= 上前方斜め、 y = up)@@ -129,13 +177,19 @@   , cameraUp     = yUp   } --- | Phase 24 A8: 視点 preset (z-up・target = 原点)。 'cameraIso' = 'defaultCameraZUp'--- (上前方斜め見下ろし)。 @dist@ は eye と原点の距離スケール。+-- | [日本語]: 視点 preset (z-up・target = 原点)。 'cameraIso' = 'defaultCameraZUp'+--   (上前方斜め見下ろし)。 @dist@ は eye と原点の距離スケール。+--   [English]: A viewpoint preset (z-up, target at the origin). 'cameraIso'+--   is 'defaultCameraZUp' (looking down diagonally from the front). @dist@+--   scales the distance between eye and origin. cameraIso :: Double -> Camera3D cameraIso = defaultCameraZUp --- | Phase 24 A8: 真上から見下ろす視点 (xy 平面を正対・z は奥行き)。 up は y 軸--- (見下ろし時に zUp は eye 方向と平行になり退化するため)。+-- | [日本語]: 真上から見下ろす視点 (xy 平面を正対・z は奥行き)。 up は y 軸+--   (見下ろし時に zUp は eye 方向と平行になり退化するため)。+--   [English]: A top-down viewpoint (facing the xy plane, with z as depth).+--   Up is the y axis, because looking straight down would make 'zUp'+--   parallel to the eye direction and degenerate. cameraTop :: Double -> Camera3D cameraTop dist = Camera3D   { cameraEye    = Point3 0 0 dist@@ -143,7 +197,9 @@   , cameraUp     = yUp   } --- | Phase 24 A8: 正面 (−y 方向から xz 面を正対・z = 縦)。+-- | [日本語]: 正面 (−y 方向から xz 面を正対・z = 縦)。+--   [English]: A front view (facing the xz plane from the −y direction, with+--   z as vertical). cameraFront :: Double -> Camera3D cameraFront dist = Camera3D   { cameraEye    = Point3 0 (negate dist) 0@@ -151,7 +207,9 @@   , cameraUp     = zUp   } --- | Phase 24 A8: 真横 (+x 方向から yz 面を正対・z = 縦)。+-- | [日本語]: 真横 (+x 方向から yz 面を正対・z = 縦)。+--   [English]: A side view (facing the yz plane from the +x direction, with+--   z as vertical). cameraSide :: Double -> Camera3D cameraSide dist = Camera3D   { cameraEye    = Point3 dist 0 0@@ -159,12 +217,16 @@   , cameraUp     = zUp   } --- | aspect 1:1 / fov 45° / near 0.1 / far 100 の sane default。+-- | [日本語]: aspect 1:1 / fov 45° / near 0.1 / far 100 の sane default。+--   [English]: A sane default with aspect 1:1, fov 45°, near 0.1, far 100. defaultPerspective :: Projection3D defaultPerspective = Perspective (pi / 4) 1.0 0.1 100 --- | 4x4 同次座標行列 (= row-major、 16 Double を直接持つ)。--- camera transform / perspective projection / model matrix で使う。+-- | [日本語]: 4x4 同次座標行列 (= row-major、 16 Double を直接持つ)。+--   camera transform / perspective projection / model matrix で使う。+--   [English]: A 4x4 homogeneous-coordinate matrix (row-major, storing 16+--   'Double's directly). Used for the camera transform, perspective+--   projection, and model matrix. data Mat4 = Mat4   !Double !Double !Double !Double   !Double !Double !Double !Double@@ -175,8 +237,11 @@ instance ToJSON   Mat4 instance FromJSON Mat4 --- | Camera 設定。 eye (= 視点)、 target (= 注視点)、 up (= 上方向、 通常 (0,1,0))。--- 'Graphics.Hgg.ThreeD.Projection.lookAt' で view 行列を生成する。+-- | [日本語]: Camera 設定。 eye (= 視点)、 target (= 注視点)、 up (= 上方向、 通常 (0,1,0))。+--   'Graphics.Hgg.ThreeD.Projection.lookAt' で view 行列を生成する。+--   [English]: Camera settings: eye (viewpoint), target (look-at point), and+--   up (up direction, usually (0,1,0)). 'Graphics.Hgg.ThreeD.Projection.lookAt'+--   builds the view matrix from these. data Camera3D = Camera3D   { cameraEye    :: !Point3   , cameraTarget :: !Point3@@ -185,21 +250,32 @@ instance ToJSON   Camera3D instance FromJSON Camera3D --- | Projection 設定。 orthographic / perspective の 2 種。+-- | [日本語]: Projection 設定。 orthographic / perspective の 2 種。 -----   * 'Orthographic': 平行投影。 box の半幅 (xHalf, yHalf, near, far) で領域指定---   * 'Perspective': 透視投影。 fov (= 縦方向視野角、 radians) + aspect ratio +---     near + far クリップ面+--     * 'Orthographic': 平行投影。 box の半幅 (xHalf, yHalf, near, far) で領域指定+--     * 'Perspective': 透視投影。 fov (= 縦方向視野角、 radians) + aspect ratio ++--       near + far クリップ面+--   [English]: Projection settings; either orthographic or perspective.+--+--     * 'Orthographic': parallel projection, specifying the region by box+--       half-widths (xHalf, yHalf, near, far)+--     * 'Perspective': perspective projection, specified by fov (vertical+--       field of view, in radians), aspect ratio, and the near/far clip+--       planes data Projection3D   = Orthographic-      { orthoXHalf :: !Double  -- ^ x 方向半幅-      , orthoYHalf :: !Double  -- ^ y 方向半幅+      { orthoXHalf :: !Double  -- ^ [日本語]: x 方向半幅+                                --   [English]: The half-width along x.+      , orthoYHalf :: !Double  -- ^ [日本語]: y 方向半幅+                                --   [English]: The half-width along y.       , orthoNear  :: !Double       , orthoFar   :: !Double       }   | Perspective-      { perspFov    :: !Double  -- ^ 縦方向 FOV、 radians-      , perspAspect :: !Double  -- ^ aspect ratio (= width / height)+      { perspFov    :: !Double  -- ^ [日本語]: 縦方向 FOV、 radians+                                 --   [English]: The vertical field of view, in radians.+      , perspAspect :: !Double  -- ^ [日本語]: aspect ratio (= width / height)+                                 --   [English]: The aspect ratio (width / height).       , perspNear   :: !Double       , perspFar    :: !Double       }@@ -213,32 +289,40 @@ -- 基本ベクトル演算 -- =========================================================================== --- | 加算。+-- | [日本語]: 加算。+--   [English]: Addition. addV3 :: Vec3 -> Vec3 -> Vec3 addV3 (Vec3 ax ay az) (Vec3 bx by bz) = Vec3 (ax + bx) (ay + by) (az + bz) --- | 減算。+-- | [日本語]: 減算。+--   [English]: Subtraction. subV3 :: Vec3 -> Vec3 -> Vec3 subV3 (Vec3 ax ay az) (Vec3 bx by bz) = Vec3 (ax - bx) (ay - by) (az - bz) --- | スカラー倍。+-- | [日本語]: スカラー倍。+--   [English]: Scalar multiplication. scaleV3 :: Double -> Vec3 -> Vec3 scaleV3 s (Vec3 x y z) = Vec3 (s * x) (s * y) (s * z) --- | 内積。+-- | [日本語]: 内積。+--   [English]: The dot product. dotV3 :: Vec3 -> Vec3 -> Double dotV3 (Vec3 ax ay az) (Vec3 bx by bz) = ax * bx + ay * by + az * bz --- | 外積 (= 右手系)。+-- | [日本語]: 外積 (= 右手系)。+--   [English]: The cross product (right-handed). crossV3 :: Vec3 -> Vec3 -> Vec3 crossV3 (Vec3 ax ay az) (Vec3 bx by bz) =   Vec3 (ay * bz - az * by) (az * bx - ax * bz) (ax * by - ay * bx) --- | L2 ノルム。+-- | [日本語]: L2 ノルム。+--   [English]: The L2 norm. lengthV3 :: Vec3 -> Double lengthV3 v = sqrt (dotV3 v v) --- | 正規化 (= 単位ベクトル化)。 ゼロベクトルは (0,0,0) 返す (= 例外無し)。+-- | [日本語]: 正規化 (= 単位ベクトル化)。 ゼロベクトルは (0,0,0) 返す (= 例外無し)。+--   [English]: Normalization (converts to a unit vector). A zero vector+--   returns (0,0,0) (no exception is thrown). normalizeV3 :: Vec3 -> Vec3 normalizeV3 v =   let l = lengthV3 v