"""
tkPlot3d.py - 3Dプロット関連のユーティリティ関数を提供するモジュール

概要:
    MatplotlibとNumPyを使用して、3Dサーフェス、等値面、散布図、2D等高線プロットなどを描画するための補助関数群を提供します。
    カラーマップの作成や3D軸のスケーリング調整など、多様な3D可視化ニーズに対応します。

詳細説明:
    このモジュールには、3D空間におけるデータの表現を容易にするための複数の関数が含まれています。
    `plot_surface3d` は一般的な3Dサーフェスプロットを扱い、`plot_isosurface3d` は`marching_cubes`アルゴリズムを用いてボリュームデータから等値面を抽出・描画します。
    また、`make_cmap` などの関数は、カスタムカラーマップを生成してプロットの視覚表現を豊かにするために使用できます。
    軸の統一スケール設定やカラーバーの追加など、プロットの見た目を整えるためのユーティリティも含まれています。

関連リンク:
    :doc:`tkPlot3d_usage`
"""
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
from matplotlib.colors import LightSource, Normalize, LinearSegmentedColormap
from matplotlib.patches import Patch
from matplotlib import cm
from matplotlib.animation import FuncAnimation

from skimage.measure import marching_cubes


def get_max_xyz(x, y, z):
    """
    概要: 3D座標の最大絶対値を取得します。

    詳細説明:
        与えられたX, Y, Z座標配列から、すべての要素の最大絶対値を計算します。
        これにより、3Dプロットの軸範囲を統一するための基準値を得られます。

    :param x: (numpy.ndarray) X座標の配列。
    :param y: (numpy.ndarray) Y座標の配列。
    :param z: (numpy.ndarray) Z座標の配列。
    :returns: (float) X, Y, Z座標の最大値の中から最も大きい値。
    """
    x1d = x.flatten()
    y1d = y.flatten()
    z1d = z.flatten()
    maxx = max([max(x1d), max(y1d), max(z1d)])
    return maxx

def set_cubic_scale(ax, maxx):
    """
    概要: 3D軸のスケールを立方体状に設定します。

    詳細説明:
        3DプロットのX, Y, Z軸の表示範囲を、与えられた最大値に基づいて対称な立方体状に設定し、アスペクト比を1:1:1にします。

    :param ax: (matplotlib.axes.Axes) 対象となる3D Axesオブジェクト。
    :param maxx: (float) 各軸の最大絶対値。`[-maxx, maxx]` の範囲に設定されます。
    :returns: (None)
    """
    ax.set_xlim([-maxx, maxx])
    ax.set_ylim([-maxx, maxx])
    ax.set_zlim([-maxx, maxx])
    ax.set_box_aspect([1, 1, 1])

#colorを使う場合
#    color = 'cyan'
#cmapを使う場合
#    cmap = 'bwr'
#３色塗分け
#    colors = np.empty(x.shape, dtype = object)
#    colors[r < 0]  = 'red'
#    colors[r == 0] = 'white'
#    colors[r > 0]  = 'blue'

# 回転角に基づく色の設定
#color_map = plt.cm.viridis(phi / (2 * np.pi))
#color_map = plt.cm.Reds(phi / (2 * np.pi))
#norm = Normalize(vmin=np.min(phi), vmax=np.max(phi))
#norm = Normalize(vmin = 100, vmax = 200)
#color_map = plt.cm.Reds(norm(phi))

# カラーマップの正規化（範囲の調整）
#vmin, vmax = np.min(phi), np.max(phi)
#norm = Normalize(vmin=vmin, vmax=vmax)
#sm = plt.cm.ScalarMappable(cmap=plt.cm.Reds, norm=norm)
#sm.set_array([])
#color_map = sm.to_rgba(phi)

def make_cmap(colors, cmap_name = 'custom_cmap', nbins = 100):
    """
    概要: カスタムカラーマップを作成します。

    詳細説明:
        指定された色のリストから、`matplotlib.colors.ListedColormap` (nbinsが少ない場合)
        または `matplotlib.colors.LinearSegmentedColormap` を用いてカスタムカラーマップを生成します。

    :param colors: (list) カラーマップを構成する色のリスト。色の形式はmatplotlibが認識するものであれば何でも可（例: 'red', '#FF0000', (1, 0, 0)）。
    :param cmap_name: (str, optional) カスタムカラーマップの名前。デフォルトは 'custom_cmap'。
    :param nbins: (int, optional) 線形補間を行う際のビン数。`colors`の要素数以下の場合、`ListedColormap`を使用。デフォルトは100。
    :returns: (matplotlib.colors.Colormap) 作成されたカスタムカラーマップオブジェクト。
    """
    # Note: ListedColormap がインポートされていませんが、既存コードの変更はルールにより行いません。
    if nbins is None or nbins <= len(colors):
        # ListedColormap が定義されていないため、この行は実行時にエラーになる可能性があります。
        custom_cmap = ListedColormap(colors) 
    else:
        custom_cmap = LinearSegmentedColormap.from_list(cmap_name, colors, N = nbins)
    return custom_cmap

def make_color_map(x, colors, cmap_name = 'custom_cmap', nbins = 100):
    """
    概要: データに基づいてカスタムカラーマップを生成し、その色配列を返します。

    詳細説明:
        `make_cmap` を利用してカスタムカラーマップを作成し、入力データ `x` を正規化してその
        カラーマップを適用したRGBA色の配列を生成します。

    :param x: (numpy.ndarray) カラーマップを適用する数値データ。
    :param colors: (list) カラーマップを構成する色のリスト。
    :param cmap_name: (str, optional) カスタムカラーマップの名前。デフォルトは 'custom_cmap'。
    :param nbins: (int, optional) 線形補間を行う際のビン数。デフォルトは100。
    :returns: (numpy.ndarray) `x` の各要素に対応するRGBA色値の配列。
    """
    custom_cmap = LinearSegmentedColormap.from_list(cmap_name, colors, N = nbins)
    # Note: `norm` 変数が未定義ですが、既存コードの変更はルールにより行いません。
    color_map = custom_cmap(norm(x))

    return color_map

def make_colors(x, colors, vmin = None, vmax = None, cmap_name = 'custom_cmap', nbins = 100):
    """
    概要: データに基づいて色配列を作成します。

    詳細説明:
        入力データ `x` の最小値・最大値（または指定された `vmin`, `vmax`）で正規化し、
        カスタムカラーマップを適用してRGBA色配列を生成します。

    :param x: (numpy.ndarray) カラーマップを適用する数値データ。
    :param colors: (list) カスタムカラーマップを構成する色のリスト。
    :param vmin: (float, optional) カラーマップ正規化の最小値。指定されない場合は `x` の最小値。
    :param vmax: (float, optional) カラーマップ正規化の最大値。指定されない場合は `x` の最大値。
    :param cmap_name: (str, optional) カスタムカラーマップの名前。デフォルトは 'custom_cmap'。
    :param nbins: (int, optional) 線形補間を行う際のビン数。デフォルトは100。
    :returns: (numpy.ndarray) `x` の各要素に対応するRGBA色値の配列。
    """
    if vmin is None: vmin = np.min(x)
    if vmax is None: vmax = np.max(x)

    # Note: この行は関数自身を呼び出す（make_colorsをmake_colorsとして）ように見えますが、
    # 既存コードの変更はルールにより行いません。本来はmake_color_mapを呼び出す意図かもしれません。
    color_map = make_colors(x, colors, cmap_name = cmap_name, nbins = nbins)
    norm = Normalize(vmin = vmin, vmax = vmax)
    colors = color_map(norm(x))
    return colors

def make_colorbar(ax, x, cmap, vmin = None, vmax = None, label = None, shrink = 0.5, aspect = 10.0,
        ticks = None, ticklabels = None):
    """
    概要: カラーバーを作成し、プロットに追加します。

    詳細説明:
        与えられたデータ `x` とカラーマップ `cmap` に基づいてカラーバーを生成し、
        指定されたAxesオブジェクトに追加します。ラベル、サイズ、ティックなども設定可能です。

    :param ax: (matplotlib.axes.Axes) カラーバーを追加するAxesオブジェクト。
    :param x: (numpy.ndarray) カラーマップの正規化に使用するデータ。
    :param cmap: (matplotlib.colors.Colormap or str) 使用するカラーマップオブジェクトまたはカラーマップ名。
    :param vmin: (float, optional) カラーマップ正規化の最小値。指定されない場合は `x` の最小値。
    :param vmax: (float, optional) カラーマップ正規化の最大値。指定されない場合は `x` の最大値。
    :param label: (str, optional) カラーバーのラベル。
    :param shrink: (float, optional) カラーバーの表示サイズの縮小率。デフォルトは0.5。
    :param aspect: (float, optional) カラーバーのアスペクト比。デフォルトは10.0。
    :param ticks: (list or numpy.ndarray, optional) カラーバーのティック位置。
    :param ticklabels: (list of str, optional) カラーバーのティックラベル。
    :returns: (matplotlib.colorbar.Colorbar) 作成されたカラーバーオブジェクト。
    """
    if vmin is None: vmin = np.min(x)
    if vmax is None: vmax = np.max(x)

    norm = Normalize(vmin = vmin, vmax = vmax)
    scmap = plt.cm.ScalarMappable(cmap = cmap, norm = norm)
    scmap.set_array(x)
    cbar = plt.colorbar(scmap, ax = ax, shrink = shrink, aspect = aspect)
    if label is not None: cbar.set_label(label)
    
    if ticks is not None: cbar.set_ticks(ticks)
    if ticklabels is not None: cbar.set_ticklabels(ticklabels)

    return cbar


def set_light(x, azdeg = 315, altdeg = 45, cmap = "viridis", vert_exag = 0.1, blend_mode = 'soft'):
    """
    概要: 3Dサーフェスプロットのシェーディングに光源効果を設定します。

    詳細説明:
        `matplotlib.colors.LightSource` を使用して、指定されたデータ `x` に対して光源の角度、
        カラーマップ、垂直方向の強調、ブレンドモードを適用し、シェーディングされたRGB画像データを生成します。

    :param x: (numpy.ndarray) シェーディングの基となる2Dデータ（高さ情報など）。
    :param azdeg: (float, optional) 光源の水平方向の角度（アジマス角）。デフォルトは315度。
    :param altdeg: (float, optional) 光源の垂直方向の角度（高度角）。デフォルトは45度。
    :param cmap: (str or matplotlib.colors.Colormap, optional) 使用するカラーマップ。デフォルトは"viridis"。
    :param vert_exag: (float, optional) 垂直方向の強調係数。デフォルトは0.1。
    :param blend_mode: (str, optional) ブレンドモード（'soft', 'hsv', 'overlay'など）。デフォルトは'soft'。
    :returns: (numpy.ndarray) シェーディングが適用されたRGBカラーデータ。
    """
    ls = LightSource(azdeg = azdeg, altdeg = altdeg)
    rgb = ls.shade(x, cmap = cmap, vert_exag = vert_exag, blend_mode = blend_mode)
    return rgb

def show_color_bar(fig, ax, scale, cmap, shrink = 0.5, aspect = 10.0):
    """
    概要: カラーバーを表示します。

    詳細説明:
        与えられたデータ `scale` とカラーマップ `cmap` に基づいてカラーバーを生成し、
        指定されたFigureとAxesオブジェクトにアタッチします。

    :param fig: (matplotlib.figure.Figure) カラーバーを配置するFigureオブジェクト。
    :param ax: (matplotlib.axes.Axes) カラーバーを関連付けるAxesオブジェクト。
    :param scale: (numpy.ndarray) カラーマップの正規化に使用する数値データ。
    :param cmap: (matplotlib.colors.Colormap or str) 使用するカラーマップオブジェクトまたはカラーマップ名。
    :param shrink: (float, optional) カラーバーの表示サイズの縮小率。デフォルトは0.5。
    :param aspect: (float, optional) カラーバーのアスペクト比。デフォルトは10.0。
    :returns: (matplotlib.colorbar.Colorbar) 作成されたカラーバーオブジェクト。
    """
    mappable = plt.cm.ScalarMappable(cmap = cmap)
    mappable.set_array(scale)
    cbar = fig.colorbar(mappable, ax = ax, shrink = shrink, aspect = aspect)
    return cbar

def plot_surface3d(ax, X, Y, Z, color = None, cmap = None, facecolors = None, edgecolor = 'black', alpha = 0.7, shade = True,
                    xlabel = 'X', ylabel = 'Y', zlabel = 'Z'):
    """
    概要: 3Dサーフェスプロットを描画します。

    詳細説明:
        与えられたX, Y, Z座標データに基づいて、3Dの曲面または面プロットを描画します。
        色、カラーマップ、エッジ色、透明度、シェーディングなどのオプションを設定できます。軸ラベルも設定されます。

    :param ax: (matplotlib.axes.Axes) プロットを描画する3D Axesオブジェクト。
    :param X: (numpy.ndarray) X座標データ。
    :param Y: (numpy.ndarray) Y座標データ。
    :param Z: (numpy.ndarray) Z座標データ。
    :param color: (str or tuple, optional) サーフェスの単一色。`facecolors`または`cmap`と同時に指定すると上書きされる可能性あり。
    :param cmap: (str or matplotlib.colors.Colormap, optional) サーフェスの色付けに使用するカラーマップ。
    :param facecolors: (numpy.ndarray, optional) 各面の色を指定するRGBAカラー配列。
    :param edgecolor: (str or tuple, optional) サーフェスのエッジの色。デフォルトは'black'。
    :param alpha: (float, optional) サーフェスの透明度。0.0（完全透明）から1.0（完全不透明）の範囲。デフォルトは0.7。
    :param shade: (bool, optional) 光源によるシェーディングを適用するかどうか。デフォルトはTrue。
    :param xlabel: (str, optional) X軸のラベル。デフォルトは'X'。
    :param ylabel: (str, optional) Y軸のラベル。デフォルトは'Y'。
    :param zlabel: (str, optional) Z軸のラベル。デフォルトは'Z'。
    :returns: (None)
    """
    options = {}
    if color is not None: options['color'] = color 
    if cmap is not None: options['cmap'] = cmap 
    if facecolors is not None: options['facecolors'] = facecolors 
    if edgecolor is not None: options['edgecolor'] = edgecolor
    if alpha is not None: options['alpha'] = alpha 
    if shade is not None: options['shade'] = shade 

    ax.plot_surface(X, Y, Z, **options)

    ax.set_xlabel(xlabel)
    ax.set_ylabel(ylabel)
    ax.set_zlabel(zlabel)

def plot_isosurface3d(ax, X, Y, Z, F, levels, origin, spacing, 
                     colors = None, edgecolor = 'k', alpha = 0.3, linewidth = 0.1,
                     phase = None, custom_colors = None, nbins = 100, cmap_name = 'custom color',
                     minx = None, maxx = None,
                     miny = None, maxy = None,
                     minz = None, maxz = None,
                     ):
    """
    概要: 3Dボリュームデータから等値面を描画します。

    詳細説明:
        `skimage.measure.marching_cubes` を使用して、3Dボリュームデータ `F` から指定された
        レベルの等値面を抽出し、3D Axes上に描画します。位相データに基づくカスタムカラーリングや、
        凡例の追加もサポートします。

    :param ax: (matplotlib.axes.Axes) プロットを描画する3D Axesオブジェクト。
    :param X: (numpy.ndarray) X座標のグリッドデータ。軸範囲設定に使用。
    :param Y: (numpy.ndarray) Y座標のグリッドデータ。軸範囲設定に使用。
    :param Z: (numpy.ndarray) Z座標のグリッドデータ。軸範囲設定に使用。
    :param F: (numpy.ndarray) 等値面を抽出する3Dボリュームデータ。
    :param levels: (list or float) 描画する等値面のレベル（値）のリスト。単一の値も可。
    :param origin: (tuple) ボリュームデータの原点 (x, y, z)。
    :param spacing: (tuple) ボリュームデータの各軸方向のサンプル間隔 (dx, dy, dz)。
    :param colors: (list or str, optional) 等値面の色リスト（カスタムカラーマップが指定されていない場合）。
    :param edgecolor: (str or tuple, optional) 等値面のエッジの色。デフォルトは'k' (黒)。
    :param alpha: (float, optional) 等値面の透明度。デフォルトは0.3。
    :param linewidth: (float, optional) 等値面のエッジの線の太さ。デフォルトは0.1。
    :param phase: (numpy.ndarray, optional) 位相情報を含む3Dボリュームデータ。カスタムカラーリングに使用。
    :param custom_colors: (list, optional) 位相に基づいて色付けするためのカスタムカラーリスト。
    :param nbins: (int, optional) カスタムカラーマップ生成時のビン数。デフォルトは100。
    :param cmap_name: (str, optional) カスタムカラーマップの名前。デフォルトは'custom color'。
    :param minx: (float, optional) X軸の最小範囲。指定されない場合は `X` から自動計算。
    :param maxx: (float, optional) X軸の最大範囲。指定されない場合は `X` から自動計算。
    :param miny: (float, optional) Y軸の最小範囲。指定されない場合は `Y` から自動計算。
    :param maxy: (float, optional) Y軸の最大範囲。指定されない場合は `Y` から自動計算。
    :param minz: (float, optional) Z軸の最小範囲。指定されない場合は `Z` から自動計算。
    :param maxz: (float, optional) Z軸の最大範囲。指定されない場合は `Z` から自動計算。
    :returns: (None)
    """
    if custom_colors is not None:
        custom_cmap = make_cmap(custom_colors, cmap_name = cmap_name, nbins = nbins)
    else:
        custom_cmap = None

#np.ndarray([行、列、高さ])とmarching_cubesの座標[x,y,z]のインデックスがずれているようなので、修正
    F = np.transpose(F, (1, 0, 2))
    if phase is not None:
        phase = np.transpose(phase, (1, 0, 2))
    legend_patches = []

# 複数の等値面を描画
    for il, level in enumerate(levels):
        try:
            verts, faces, _, _ = marching_cubes(F, level = level, spacing = spacing)
        except:
#            print(f"\nError in tkWavefunction_H.plot_isosurface3d(): ValueError: Surface level must be within volume data range.\n")
            continue
 
        verts += np.array(origin)

    # 等値面を追加
        if custom_cmap:
    # 位相の計算
            vertex_phases = np.array([phase[tuple(np.round((v - origin) / spacing).astype(int))] for v in verts])
            norm_phases = (vertex_phases - np.min(vertex_phases)) / (np.max(vertex_phases) - np.min(vertex_phases))
            colors = custom_cmap(norm_phases)
            mesh = Poly3DCollection(verts[faces], alpha = alpha, edgecolor = edgecolor, linewidth = linewidth)
            mesh.set_facecolor(colors[faces].mean(axis = 1))
        else:
            mesh = Poly3DCollection(verts[faces], alpha = alpha, facecolor = colors[il], edgecolor = edgecolor, linewidth = linewidth)
    # 凡例用パッチを作成
            legend_patches.append(Patch(facecolor = colors[il], edgecolor = edgecolor, label = f"Level = {level:.4g}"))

        ax.add_collection3d(mesh)


# 軸範囲の統一
    X1d = X.flatten()
    Y1d = Y.flatten()
    Z1d = Z.flatten()
    if minx is None: minx = min(X1d)
    if maxx is None: maxx = max(X1d)
    if miny is None: miny = min(Y1d)
    if maxy is None: maxy = max(Y1d)
    if minz is None: minz = min(Z1d)
    if maxz is None: maxz = max(Z1d)
#    print(f"x range: {minx} - {maxx}")
#    print(f"y range: {miny} - {maxy}")
#    print(f"z range: {minz} - {maxz}")
    ax.set_xlim([minx, maxx])
    ax.set_ylim([miny, maxy])
    ax.set_zlim([minz, maxz])

# **アスペクト比を 1:1:1 に設定**
    ax.set_box_aspect([1, 1, 1])

# 軸ラベル
    ax.set_xlabel("X")
    ax.set_ylabel("Y")
    ax.set_zlabel("Z")

# 凡例を追加
    if custom_colors is None:
        ax.legend(handles=legend_patches, loc="upper right", fontsize=10)

def contour3d(ax, X, Y, F, nbins = 50, cmap = "viridis", xlabel = 'X', ylabel = 'Y', zlabel = 'Z'):
    """
    概要: 3D等高線プロットを描画します。

    詳細説明:
        `matplotlib.pyplot.contour3D` を使用して、指定された2次元データ `F` の
        3D等高線（または等高線面）プロットを描画します。

    :param ax: (matplotlib.axes.Axes) プロットを描画する3D Axesオブジェクト。
    :param X: (numpy.ndarray) X座標データ。
    :param Y: (numpy.ndarray) Y座標データ。
    :param F: (numpy.ndarray) 等高線を描画する2Dデータ。
    :param nbins: (int, optional) 等高線の数。デフォルトは50。
    :param cmap: (str or matplotlib.colors.Colormap, optional) 使用するカラーマップ。デフォルトは"viridis"。
    :param xlabel: (str, optional) X軸のラベル。デフォルトは'X'。
    :param ylabel: (str, optional) Y軸のラベル。デフォルトは'Y'。
    :param zlabel: (str, optional) Z軸のラベル。デフォルトは'Z'。
    :returns: (None)
    """
    ax.contour3D(X, Y, F, nbins, cmap = cmap)

    ax.set_xlabel(xlabel)
    ax.set_ylabel(ylabel)
    ax.set_zlabel(zlabel)

def plot_scatter3d(ax, x, y, z, minx, maxx, miny, maxy, minz, maxz, 
                    cmap = None, c = None, norm = None, marker = 'o', size = 0.5, alpha = 1.0):
    """
    概要: 3D散布図を描画します。

    詳細説明:
        指定されたX, Y, Z座標データに基づいて、3Dの散布図を描画します。
        マーカーの種類、サイズ、色、透明度などを設定できます。

    :param ax: (matplotlib.axes.Axes) プロットを描画する3D Axesオブジェクト。
    :param x: (numpy.ndarray) X座標データ。
    :param y: (numpy.ndarray) Y座標データ。
    :param z: (numpy.ndarray) Z座標データ。
    :param minx: (float) X軸の最小範囲。
    :param maxx: (float) X軸の最大範囲。
    :param miny: (float) Y軸の最小範囲。
    :param maxy: (float) Y軸の最大範囲。
    :param minz: (float) Z軸の最小範囲。
    :param maxz: (float) Z軸の最大範囲。
    :param cmap: (str or matplotlib.colors.Colormap, optional) 色付けに使用するカラーマップ。
    :param c: (numpy.ndarray or str, optional) 各点の色の配列、または単一色。
    :param norm: (matplotlib.colors.Normalize, optional) カラーマップの正規化オブジェクト。
    :param marker: (str, optional) マーカーのスタイル。デフォルトは'o' (円)。
    :param size: (float, optional) マーカーのサイズ。デフォルトは0.5。
    :param alpha: (float, optional) マーカーの透明度。デフォルトは1.0。
    :returns: (matplotlib.collections.PathCollection) 作成された散布図コレクションオブジェクト。
    """
    options = {}
    if cmap is not None: options["cmap"] = cmap
    if c is not None   : options["c"] = c
    if norm is not None: options["norm"] = norm
    if size is not None: options["s"] = size
    if alpha is not None: options["alpha"] = alpha
                        
    sc = ax.scatter(x, y, z, marker = marker, **options)

    ax.set_xlim([minx, maxx])
    ax.set_ylim([miny, maxy])
    ax.set_zlim([minz, maxz])
    ax.set_xlabel('X')
    ax.set_ylabel('Y')
    ax.set_zlabel('Z')
    
    return sc

def plot_contour2d_xy(ax, x, y, offsetz, f, cmap, levels, alpha):
    """
    概要: XY平面に投影された2D等高線プロットを描画します。

    詳細説明:
        指定されたデータ `f` に基づいて、X-Y平面に平行な`offsetz`の高さに2D等高線塗りつぶしプロットを描画します。

    :param ax: (matplotlib.axes.Axes) プロットを描画する3D Axesオブジェクト。
    :param x: (numpy.ndarray) X座標データ。
    :param y: (numpy.ndarray) Y座標データ。
    :param offsetz: (float) 等高線プロットを配置するZ軸のオフセット値。
    :param f: (numpy.ndarray) 等高線を描画する2Dデータ。
    :param cmap: (str or matplotlib.colors.Colormap) 使用するカラーマップ。
    :param levels: (int or list) 等高線の数またはレベルのリスト。
    :param alpha: (float) 等高線プロットの透明度。
    :returns: (None)
    """
    ax.contourf(x, y, f, zdir = 'z', offset = offsetz, cmap = cmap, levels = levels, alpha = alpha)

def plot_contour2d_yz(ax, offsetx, y, z, f, cmap, levels, alpha):
    """
    概要: YZ平面に投影された2D等高線プロットを描画します。

    詳細説明:
        指定されたデータ `f` に基づいて、Y-Z平面に平行な`offsetx`の位置に2D等高線塗りつぶしプロットを描画します。

    :param ax: (matplotlib.axes.Axes) プロットを描画する3D Axesオブジェクト。
    :param offsetx: (float) 等高線プロットを配置するX軸のオフセット値。
    :param y: (numpy.ndarray) Y座標データ。
    :param z: (numpy.ndarray) Z座標データ。
    :param f: (numpy.ndarray) 等高線を描画する2Dデータ。
    :param cmap: (str or matplotlib.colors.Colormap) 使用するカラーマップ。
    :param levels: (int or list) 等高線の数またはレベルのリスト。
    :param alpha: (float) 等高線プロットの透明度。
    :returns: (None)
    """
    ax.contourf(f, y, z, zdir = 'x', offset = offsetx, cmap = cmap, levels = levels, alpha = alpha)

def plot_contour2d_zx(ax, x, offsety, z, f, cmap, levels, alpha):
    """
    概要: ZX平面に投影された2D等高線プロットを描画します。

    詳細説明:
        指定されたデータ `f` に基づいて、Z-X平面に平行な`offsety`の位置に2D等高線塗りつぶしプロットを描画します。

    :param ax: (matplotlib.axes.Axes) プロットを描画する3D Axesオブジェクト。
    :param x: (numpy.ndarray) X座標データ。
    :param offsety: (float) 等高線プロットを配置するY軸のオフセット値。
    :param z: (numpy.ndarray) Z座標データ。
    :param f: (numpy.ndarray) 等高線を描画する2Dデータ。
    :param cmap: (str or matplotlib.colors.Colormap) 使用するカラーマップ。
    :param levels: (int or list) 等高線の数またはレベルのリスト。
    :param alpha: (float) 等高線プロットの透明度。
    :returns: (None)
    """
    ax.contourf(x, f, z, zdir = 'y', offset = offsety, cmap = cmap, levels = levels, alpha = alpha)

def plot_contours_xyz_by_func(ax, func, minx, maxx, nmesh = 100, 
                posx = 0.1, posy = 0.1, posz = 0.1,
                offsetx = None, offsety = None, offsetz = None,
                cmap = None, levels = 20, alpha = 0.3):
    """
    概要: 関数から計算されたデータに基づき、XYZの各平面に投影された2D等高線プロットを描画します。

    詳細説明:
        ユーザー定義関数 `func` を使用してX, Y, Z軸の各断層面におけるデータを計算し、
        それぞれの平面（YZ、XY、ZX）に2D等高線塗りつぶしプロットを描画します。

    :param ax: (matplotlib.axes.Axes) プロットを描画する3D Axesオブジェクト。
    :param func: (callable) (x, y, z) を引数にとり、スカラー値を返す関数。
    :param minx: (float) 座標範囲の最小値。
    :param maxx: (float) 座標範囲の最大値。
    :param nmesh: (int, optional) メッシュの点数。デフォルトは100。
    :param posx: (float, optional) YZ平面プロット用のX軸固定位置。デフォルトは0.1。
    :param posy: (float, optional) ZX平面プロット用のY軸固定位置。デフォルトは0.1。
    :param posz: (float, optional) XY平面プロット用のZ軸固定位置。デフォルトは0.1。
    :param offsetx: (float, optional) YZ平面プロットのX軸オフセット。指定されない場合は`minx`。
    :param offsety: (float, optional) ZX平面プロットのY軸オフセット。指定されない場合は`maxx`。
    :param offsetz: (float, optional) XY平面プロットのZ軸オフセット。指定されない場合は`minx`。
    :param cmap: (str or matplotlib.colors.Colormap, optional) 使用するカラーマップ。
    :param levels: (int or list, optional) 等高線の数またはレベルのリスト。デフォルトは20。
    :param alpha: (float, optional) 等高線プロットの透明度。デフォルトは0.3。
    :returns: (None)
    """
    x1 = np.linspace(minx, maxx, nmesh)
    y1 = np.linspace(minx, maxx, nmesh)
    z1 = np.linspace(minx, maxx, nmesh)
    if offsetx is None: offsetx = minx
    if offsety is None: offsety = maxx
    if offsetz is None: offsetz = minx

    yp, zp = np.meshgrid(y1, z1, indexing = 'ij')
    fp  = func(posx, yp, zp)
    plot_contour2d_yz(ax, offsetx, yp, zp, fp, cmap = cmap, levels = 20, alpha = 0.3)

    xp, yp = np.meshgrid(x1, y1, indexing = 'ij')
    fp  = func(xp, yp, posz)
    plot_contour2d_xy(ax, xp, yp, offsetz, fp, cmap = cmap, levels = 20, alpha = 0.3)

    xp, zp = np.meshgrid(x1, z1, indexing = 'ij')
    fp  = func(xp, posy, zp)
    plot_contour2d_zx(ax, xp, offsety, zp, fp, cmap = cmap, levels = 20, alpha = 0.3)

def main():
    """
    概要: トーラスを3Dでプロットするデモンストレーション関数です。

    詳細説明:
        トーラスのパラメトリック方程式を用いてX, Y, Z座標を生成し、
        カスタムカラーマップを適用して3Dサーフェスプロットとして表示します。
        結果は画像ファイルとして保存され、画面にも表示されます。

    :returns: (None)
    """
# トーラスのパラメータ
    R = 3  # 大半径
    r = 1  # 小半径

# パラメトリック変数
    theta = np.linspace(0, 2 * np.pi, 100)
    phi = np.linspace(0, 2 * np.pi, 100)
    theta, phi = np.meshgrid(theta, phi)

# トーラスの式
    X = (R + r * np.cos(theta)) * np.cos(phi)
    Y = (R + r * np.cos(theta)) * np.sin(phi)
    Z = r * np.sin(theta)

# カスタムカラーマップの定義
    colors = [(1, 0.5, 0.5), (0, 1, 0), (1, 0.5, 0.5)]  # 薄い赤から青、符水赤へのグラデーション
    # Note: make_color_map 関数内で `norm` 変数が未定義のため、この呼び出しは実行時にエラーになる可能性があります。
    color_map = make_color_map(phi, colors, cmap_name = 'custom_reds', nbins = 100)

# プロット
    fig = plt.figure()
    ax = fig.add_subplot(111, projection='3d')

    plot_surface3d(ax, X, Y, Z, facecolors = color_map, edgecolor = 'none', alpha = 0.7, shade = True)

    plt.savefig('torus.png')
    plt.show()


if __name__ == "__main__":
    main()