使用 Plotly.JS 将两个不透明的热图放在一起

Bas*_*asj 5 javascript plotly plotly.js

我正在尝试将此答案移植到 100% Plotly.JS 解决方案。TL;DR:如何使用 Plotly.JS(无 Python)在每个顶部有两个带有不透明度滑块的热图?

解决方案的开始,但如何添加第二条迹线?

const z = [];
for (let i = 0; i < 500; i++)
  z.push(Array.from({ length: 600 }, () => Math.floor(Math.random() * 100)));
const data = [{ z: z, colorscale: "YlGnBu", type: "heatmap" }];
const steps = [];
for (let i = 0; i <= 100; i++)
  steps.push({ label: i + "%", execute: true, method: "restyle", args: [{ opacity: i / 100 }] });
const layout = { sliders: [{ name: "slider", steps: steps, active: 100 }] };
Plotly.newPlot("graph", data, layout);
Run Code Online (Sandbox Code Playgroud)
<script src="https://cdn.plot.ly/plotly-2.16.2.min.js"></script>
<div id="graph"></div>
Run Code Online (Sandbox Code Playgroud)


供参考:原始Python解决方案:

from PIL import Image
import plotly.graph_objects as go
import numpy as np
import scipy.misc

imgA = scipy.misc.face()
imgB = Image.fromarray(np.random.random(imgA.shape[:2])*255).convert('RGB')

fig = go.Figure([
    go.Image(name='raccoon', z=imgA, opacity=1), # trace 0
    go.Image(name='noise', z=imgB, opacity=0.5)  # trace 1
])

slider = {
    'active': 50,
    'currentvalue': {'prefix': 'Noise: '},
    'steps': [{
        'value': step/100,
        'label': f'{step}%',
        'visible': True,
        'execute': True,
        'method': 'restyle',
        'args': [{'opacity': step/100}, [1]]     # apply to trace [1] only
    } for step in range(101)]
}

fig.update_layout(sliders=[slider])
fig.show(renderer='browser')
Run Code Online (Sandbox Code Playgroud)

Eri*_*ult 2

data第二条跟踪也进入数组。需要注意的是,索引很重要:索引 1 处的迹线绘制在索引 0 处的迹线上方,依此类推。

对于滑块配置,它应该与 python 中的相同:每个步骤更改都会触发具有相同参数的相同“restyle”方法,即。Plotly.restyle(graphDiv, ...args),也就是说,args方法调用与签名匹配:

Plotly.restyle(graphDiv, update [, traceIndices])
Run Code Online (Sandbox Code Playgroud)

现在,最重要的是滑块应该针对哪个跟踪(traceIndices),即显式命名的跟踪的哪个索引或哪个名称(如果我没记错的话,默认是全部),但在这里它不会在之间发生变化Python 和 JavaScript。

这是一个完整的示例(在codepen.io上尝试一下):

Plotly.restyle(graphDiv, update [, traceIndices])
Run Code Online (Sandbox Code Playgroud)

图像与热图

热图仅适用于单通道数据(根据给定色标的各个值到颜色的映射)。

使用 rgb(或 rgba、rgba256、hsl、hsla)时,必须使用类型image。不同之处在于它z 必须是一个二维数组,其中每个元素都是代表一种颜色的 3 或 4 个数字的数组(应colormodel相应地设置)。

例如,设置一张由噪声组成的RGB图像作为背景层:

// Random z data
const w = {length: 600};
const h = {length: 400};
const z0 = Array.from(h, () => Array.from(w, () => Math.floor(Math.random() * 100)));
const z1 = Array.from(h, () => Array.from(w, () => Math.floor(Math.random() * 100)));

// Initial opacity for the trace 'above' 
const op_init = 0.5;

const data = [
  // Nb. Trace 1 drawn on top of trace 0
  {type: 'heatmap', z: z0, colorscale: 'Greys'},                    // trace 0
  {type: 'heatmap', z: z1, colorscale: 'Cividis', opacity: op_init} // trace 1
];

// Steps for the opacity slider
const steps = [];
const n_steps = 100; // number of steps above step 0
for (let i = 0; i <= n_steps; i++) {
  steps.push({
    label: i + '%',
    execute: true,
    method: 'restyle',
    args: [{
      opacity: i/n_steps
    }, [1]] // <- Nb. this applies only to trace 1
  });
}

const layout = {
  width: 600,
  sliders: [{
    steps: steps,
    active: Math.round(op_init * n_steps), // slider default matches op_init 
    pad: {t: 30},
    currentvalue: {prefix: 'opacity: '}
  }]
};

Plotly.newPlot('plot', data, layout);
Run Code Online (Sandbox Code Playgroud)

这是第二个例子,我们有一个 rgb[a] 图像(DOM 对象img)及其表示为一维 Uint8Array ( uint8Arr) 的像素数据,需要将其转换为 2d :

const z0 = Array.from(h, () => Array.from(w, () => ['r', 'g', 'b'].map(() => Math.floor(Math.random() * 255)) ));

// ...

const data = [
  {type: 'image', z: z0, colormodel: 'rgb'},                        // trace 0
  {type: 'heatmap', z: z1, colorscale: 'Cividis', opacity: op_init} // trace 1
];
Run Code Online (Sandbox Code Playgroud)

铌。当您绘制图像时,y 轴会自动反转(除非另有指定,否则图像会上下颠倒)。这会影响热图 y 标签的方向,因为它们位于同一图上,但只有标签而不是数据。

以下是布局设置,确保两条迹线共享相同的纵横比并且图像方向正确:

const z0 = [];
const nChannels = uint8Arr.length / img.width / img.height;
const chunkSize = uint8Arr.length / img.height;
const z0_model = nChannels === 4 ? 'rgba' : 'rgb';

for (let i = 0; i < uint8Arr.length; i += chunkSize) {
  const chunk = uint8Arr.slice(i, i + chunkSize);
  const row = [];
  for (let j = 0; j < chunk.length; j += nChannels)
    row.push(chunk.slice(j, j + nChannels));
  z0.push(row);
}

// ...

const data = [
  {type: 'image', z: z0, colormodel: z0_model},                     // trace 0
  {type: 'heatmap', z: z1, colorscale: 'Cividis', opacity: op_init} // trace 1
];
Run Code Online (Sandbox Code Playgroud)