PyQtGraph の3Dサーフェス/メッシュプロットでは,高さに応じて色を変えることができます。
具体的には,GLSurfacePlotItem もしくは GLMeshItem のコンストラクタのキーワード引数 としてshader='heightColor'
を指定します.
n_x, n_y = 128, 128 xy_init = 1.0, 4.0 x_range = -10, 10 y_range = -10, 10 x = np.linspace(x_range[0], x_range[1], n_x) y = np.linspace(y_range[0], y_range[1], n_y) rad = 0.5 x_grid, y_grid = np.meshgrid(y, x) z = 3 * np.exp(-((x_grid-xy_init[0])**2.0)*rad**2) \ * np.exp(-((y_grid-xy_init[1])**2.0)*rad**2) p = gl.GLSurfacePlotItem(x=x, y=y, z=z, shader='heightColor')
ここで注意点として,GLSurfacePlotItem, GLMeshItem にはcolor
, colors
といったキーワード引数がありますが,shader='heightColor'
を指定した場合,それらの引数による色の指定は無効になります。
代替方法として,p.shader()['colorMap']
に1次元9要素の配列をセットすることで,3Dデータの高さ(z軸の値)に応じて色を制御できます。
p.shader()['colorMap'] = np.array([ 0.0, 0.0, 1.0, # red 0.4, 1.5, 1.0, # green 1.5, 1.5, 1.0]) # blue
この指定方法について調査したところ,pyqtgraph.opengl.shaders
に以下のように書かれていました.
pyqtgraph.opengl.shaders.py
## colors fragments by z-value. ## This is useful for coloring surface plots by height. ## This shader uses a uniform called "colorMap" to determine how to map the colors: ## red = pow(z * colorMap[0] + colorMap[1], colorMap[2]) ## green = pow(z * colorMap[3] + colorMap[4], colorMap[5]) ## blue = pow(z * colorMap[6] + colorMap[7], colorMap[8]) ## (set the values like this: shader['uniformMap'] = array([...]) ShaderProgram('heightColor', [ VertexShader(""" varying vec4 pos; void main() { gl_FrontColor = gl_Color; gl_BackColor = gl_Color; pos = gl_Vertex; gl_Position = ftransform(); } """), FragmentShader(""" uniform float colorMap[9]; varying vec4 pos; //out vec4 gl_FragColor; // only needed for later glsl versions //in vec4 gl_Color; void main() { vec4 color = gl_Color; color.x = colorMap[0] * (pos.z + colorMap[1]); if (colorMap[2] != 1.0) color.x = pow(color.x, colorMap[2]); color.x = color.x < 0. ? 0. : (color.x > 1. ? 1. : color.x); color.y = colorMap[3] * (pos.z + colorMap[4]); if (colorMap[5] != 1.0) color.y = pow(color.y, colorMap[5]); color.y = color.y < 0. ? 0. : (color.y > 1. ? 1. : color.y); color.z = colorMap[6] * (pos.z + colorMap[7]); if (colorMap[8] != 1.0) color.z = pow(color.z, colorMap[8]); color.z = color.z < 0. ? 0. : (color.z > 1. ? 1. : color.z); color.w = 1.0; gl_FragColor = color; } """), ], uniforms={'colorMap': [1, 1, 1, 1, 0.5, 1, 1, 0, 1]}),
すなわち、与えた z
,つまり高さの値に対して,
- red = pow(z * colorMap[0] + colorMap[1], colorMap[2])
- green = pow(z * colorMap[3] + colorMap[4], colorMap[5])
- blue = pow(z * colorMap[6] + colorMap[7], colorMap[8])
となります。ただし,それぞれ最終的に 0.0未満だと0.0に、1.0以上だと1.0に設定されます。
従って、最終的にred, green, blueの値は0.0~1.0の範囲の値となります。
つまり,プロットするz
の値の範囲に合わせて colorMap
を設定すればOKです。
例えば,緑色単色で,zの値の範囲にGreenの0~1の範囲を線形に割り当てるとすると,以下のようになります.
p.shader()['colorMap'] = np.array([ 0.0, 0.0, 1.0, # red 1.0/(np.max(z)-np.min(z)), -np.min(z), 1.0, # green 0.0, 0.0, 1.0]) # blue
ただ、今回のデータz
は小さい値が多いため暗く見えます。そう言う場合は,例えば他の色を土台として使うと見やすくなるかもしれません。
p.shader()['colorMap'] = np.array([ 0.0, 0.0, 1.0, # red 1.0/(np.max(z)-np.min(z)), -np.min(z), 0.5, # green 1.0, np.max(z), 1.0]) # blue
応用例として,リアルタイムプロットやアニメーションで色を変更することもできます。