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 +5/−0
- hgg-3d.cabal +7/−7
- src/Graphics/Hgg/ThreeD.hs +29/−15
- src/Graphics/Hgg/ThreeD/Axes.hs +107/−45
- src/Graphics/Hgg/ThreeD/Bar.hs +92/−38
- src/Graphics/Hgg/ThreeD/Bound.hs +38/−12
- src/Graphics/Hgg/ThreeD/Browser.hs +67/−24
- src/Graphics/Hgg/ThreeD/Delaunay.hs +28/−10
- src/Graphics/Hgg/ThreeD/Easy.hs +187/−68
- src/Graphics/Hgg/ThreeD/Line.hs +21/−9
- src/Graphics/Hgg/ThreeD/Projection.hs +64/−23
- src/Graphics/Hgg/ThreeD/Scatter.hs +47/−16
- src/Graphics/Hgg/ThreeD/Spec.hs +370/−100
- src/Graphics/Hgg/ThreeD/Surface.hs +54/−21
- src/Graphics/Hgg/ThreeD/Types.hs +153/−69
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