嘿,我很难用 Plotly 创建自定义热图。
我使用下面的代码来生成自定义 Plotly 热图。我正在使用 create_annotated_heatmap 函数来生成绘图。
from plotly import exceptions, optional_imports
import plotly.colors as clrs
from plotly.figure_factory import utils
from plotly.graph_objs import graph_objs
from plotly.validators.heatmap import ColorscaleValidator
# Optional imports, may be None for users that only use our core functionality.
np = optional_imports.get_module("numpy")
def validate_annotated_heatmap(z, x, y, annotation_text):
"""
Annotated-heatmap-specific validations
Check that if a text matrix is supplied, it has the same
dimensions as the z matrix.
See FigureFactory.create_annotated_heatmap() for params
:raises: (PlotlyError) If z and text matrices do not have the same
dimensions.
"""
if annotation_text is not None and isinstance(annotation_text, list):
utils.validate_equal_length(z, annotation_text)
for lst in range(len(z)):
if len(z[lst]) != len(annotation_text[lst]):
raise exceptions.PlotlyError(
"z and text should have the " "same dimensions"
)
if x:
if len(x) != len(z[0]):
raise exceptions.PlotlyError(
"oops, the x list that you "
"provided does not match the "
"width of your z matrix "
)
if y:
if len(y) != len(z):
raise exceptions.PlotlyError(
"oops, the y list that you "
"provided does not match the "
"length of your z matrix "
)
def annotated_heatmap_trace_and_layout(
z,
x=None,
y=None,
annotation_text=None,
colorscale="Plasma",
font_colors=None,
showscale=False,
reversescale=False,
**kwargs
):
# Avoiding mutables in the call signature
font_colors = font_colors if font_colors is not None else []
validate_annotated_heatmap(z, x, y, annotation_text)
# validate colorscale
colorscale_validator = ColorscaleValidator()
colorscale = colorscale_validator.validate_coerce(colorscale)
annotations = _AnnotatedHeatmap(
z, x, y, annotation_text, colorscale, font_colors, reversescale, **kwargs
).make_annotations()
if x or y:
trace = dict(
type="heatmap",
z=z,
x=x,
y=y,
colorscale=colorscale,
showscale=showscale,
reversescale=reversescale,
**kwargs
)
layout = dict(
annotations=annotations,
xaxis=dict(ticks="",
dtick=1,
side="top",
gridcolor="rgb(0, 0, 0)"),
yaxis=dict(ticks="", dtick=1, ticksuffix=" "),
)
else:
trace = dict(
type="heatmap",
z=z,
colorscale=colorscale,
showscale=showscale,
reversescale=reversescale,
**kwargs
)
layout = dict(
annotations=annotations,
xaxis=dict(
ticks="",
side="top",
gridcolor="rgb(0, 0, 0)",
showticklabels=False
),
yaxis=dict(ticks="", ticksuffix=" ", showticklabels=False),
)
return trace, layout
def create_annotated_heatmap(
z,
x=None,
y=None,
annotation_text=None,
colorscale="Plasma",
font_colors=None,
showscale=False,
reversescale=False,
**kwargs
):
trace, layout = annotated_heatmap_trace_and_layout(z, x, y, annotation_text, colorscale, font_colors, showscale, reversescale, **kwargs)
data = [trace]
return graph_objs.Figure(data=data, layout=layout)
def to_rgb_color_list(color_str, default):
if "rgb" in color_str:
return [int(v) for v in color_str.strip("rgb()").split(",")]
elif "#" in color_str:
return clrs.hex_to_rgb(color_str)
else:
return default
def should_use_black_text(background_color):
return (
background_color[0] * 0.299
+ background_color[1] * 0.587
+ background_color[2] * 0.114
) > 186
class _AnnotatedHeatmap(object):
"""
Refer to TraceFactory.create_annotated_heatmap() for docstring
"""
def __init__(
self, z, x, y, annotation_text, colorscale, font_colors, reversescale, **kwargs
):
self.z = z
if x:
self.x = x
else:
self.x = range(len(z[0]))
if y:
self.y = y
else:
self.y = range(len(z))
if annotation_text is not None:
self.annotation_text = annotation_text
else:
self.annotation_text = self.z
self.colorscale = colorscale
self.reversescale = reversescale
self.font_colors = font_colors
self.kwargs = kwargs
def get_text_color(self):
"""
Get font color for annotations.
The annotated heatmap can feature two text colors: min_text_color and
max_text_color. The min_text_color is applied to annotations for
heatmap values < (max_value - min_value)/2. The user can define these
two colors. Otherwise the colors are defined logically as black or
white depending on the heatmap's colorscale.
:rtype (string, string) min_text_color, max_text_color: text
color for annotations for heatmap values <
(max_value - min_value)/2 and text color for annotations for
heatmap values >= (max_value - min_value)/2
"""
# Plotly colorscales ranging from a lighter shade to a darker shade
colorscales = [
"Greys",
"Greens",
"Blues",
"YIGnBu",
"YIOrRd",
"RdBu",
"Picnic",
"Jet",
"Hot",
"Blackbody",
"Earth",
"Electric",
"Viridis",
"Cividis",
]
# Plotly colorscales ranging from a darker shade to a lighter shade
colorscales_reverse = ["Reds"]
white = "#FFFFFF"
black = "#000000"
if self.font_colors:
min_text_color = self.font_colors[0]
max_text_color = self.font_colors[-1]
elif self.colorscale in colorscales and self.reversescale:
min_text_color = black
max_text_color = white
elif self.colorscale in colorscales:
min_text_color = white
max_text_color = black
elif self.colorscale in colorscales_reverse and self.reversescale:
min_text_color = white
max_text_color = black
elif self.colorscale in colorscales_reverse:
min_text_color = black
max_text_color = white
elif isinstance(self.colorscale, list):
min_col = to_rgb_color_list(self.colorscale[0][1], [255, 255, 255])
max_col = to_rgb_color_list(self.colorscale[-1][1], [255, 255, 255])
# swap min/max colors if reverse scale
if self.reversescale:
min_col, max_col = max_col, min_col
if should_use_black_text(min_col):
min_text_color = black
else:
min_text_color = white
if should_use_black_text(max_col):
max_text_color = black
else:
max_text_color = white
else:
min_text_color = black
max_text_color = black
return min_text_color, max_text_color
def get_z_mid(self):
"""
Get the mid value of z matrix
:rtype (float) z_avg: average val from z matrix
"""
if 'zmid' in self.kwargs:
return self.kwargs['zmid']
if np and isinstance(self.z, np.ndarray):
z_min = np.amin(self.z)
z_max = np.amax(self.z)
else:
z_min = min([v for row in self.z for v in row])
z_max = max([v for row in self.z for v in row])
z_mid = (z_max + z_min) / 2
return z_mid
def make_annotations(self):
"""
Get annotations for each cell of the heatmap with graph_objs.Annotation
:rtype (list[dict]) annotations: list of annotations for each cell of
the heatmap
"""
min_text_color, max_text_color = _AnnotatedHeatmap.get_text_color(self)
z_mid = _AnnotatedHeatmap.get_z_mid(self)
annotations = []
for n, row in enumerate(self.z):
for m, val in enumerate(row):
font_color = min_text_color if val < z_mid else max_text_color
annotations.append(
graph_objs.layout.Annotation(
text=str(self.annotation_text[n][m]),
x=self.x[m],
y=self.y[n],
xref="x1",
yref="y1",
font=dict(color=font_color),
showarrow=False,
)
)
return annotations
之后我准备我的x、y、z。我使用
fig
来称呼我的 fig.add_trace
。
x = ['uBQlnFPT', 'KngLzueS', 'QdEFfXmA', 'NMykaKna', 'htTJpYZw', 'QiIbsLNJ', 'ObqTHrzi', 'IvoETTxO', 'UXljefBc', 'gBbjnMss',
'zZYNxvhB']
y = ['kRzZhZd', 'yybgaGj', 'MeDGGoI', 'RDBkKpq', 'KPZGesp', 'NoIxUYD', 'vvcQJcu', 'nfuyjJx', 'vtdVONv', 'BDrOWby', 'SSDkBSD', 'aYXHtLJ', 'xrkMHye', 'HYCtaLD', 'bKwSDdd', 'COmFvEm', 'lsnaNmS', 'LEybfTZ', 'JrBOAdC', 'LWPfXBS', 'hmYHIUA', 'fOyooId', 'LyJSIlF', 'aFLMJEa']
z = [[2.3, 2.5, np.nan, 2.1, 2.0, np.nan, 2.4, np.nan, 2.6, np.nan, 1.9],
[2.5, 2.9, np.nan, np.nan, 2.3, np.nan, 2.8, np.nan, 2.7, 2.4, 2.2],
[2.2, np.nan, np.nan, np.nan, np.nan, np.nan, 2.1, np.nan, np.nan, np.nan, np.nan],
[3.0, 2.0, np.nan, 2.0, 2.5, np.nan, np.nan, 2.0, 2.0, 2.4, np.nan],
[2.1, 2.0, 2.3, np.nan, 2.0, 1.9, 2.1, 2.0, 2.2, 2.1, 2.4],
[2.2, 2.1, 2.4, 2.0, 2.3, 2.1, 2.2, 2.2, 2.3, 2.1, 2.2],
[2.1, np.nan, np.nan, 2.7, np.nan, np.nan, np.nan, 2.0, np.nan, np.nan, np.nan],
[2.0, 2.1, 2.1, 2.0, 2.0, 1.9, 2.2, 2.5, 2.1, 2.2, 2.0],
[2.0, 2.0, 2.6, 2.1, 2.7, 2.4, 2.0, 2.3, 2.1, 2.0, 1.9],
[1.9, 1.7, np.nan, 1.8, np.nan, 2.4, 2.0, 1.8, 1.9, 2.0, np.nan],
[2.4, np.nan, np.nan, np.nan, np.nan, np.nan, 2.1, 2.3, 2.4, 2.6, 2.3],
[2.1, 1.9, 1.8, 1.7, 2.0, 2.5, 2.1, np.nan, 2.2, 2.0, 2.1],
[2.6, 2.7, 2.5, 1.9, 2.3, 2.1, 2.3, np.nan, 2.4, 2.3, 2.4],
[2.5, 2.5, 2.2, 2.1, 2.2, 1.8, 2.5, np.nan, 2.7, 2.4, 2.6],
[2.4, np.nan, np.nan, 2.0, np.nan, np.nan, np.nan, 2.0, np.nan, np.nan, np.nan],
[2.3, 2.1, 2.0, 2.0, 2.3, 2.0, 2.1, np.nan, 2.3, 2.1, 2.0],
[2.1, np.nan, np.nan, 1.9, 1.8, np.nan, np.nan, 2.2, 2.1, 2.3, 1.8],
[2.4, np.nan, np.nan, np.nan, 1.5, np.nan, np.nan, 2.0, 2.6, 1.9, np.nan],
[2.0, 2.0, np.nan, np.nan, 2.0, np.nan, np.nan, 2.0, 2.3, 2.1, 2.0],
[2.5, 2.1, np.nan, np.nan, 2.1, 2.0, 2.2, np.nan, 2.0, 2.2, 2.1],
[2.2, 2.2, np.nan, np.nan, 2.5, 2.0, 2.1, 2.2, 2.2, 2.2, 2.2],
[2.3, 2.6, np.nan, np.nan, np.nan, 2.5, np.nan, np.nan, np.nan, np.nan, np.nan],
[1.8, np.nan, np.nan, np.nan, np.nan, np.nan, np.nan, np.nan, np.nan, np.nan, 2.0],
[3.0, 3.1, np.nan, 2.7, 2.8, np.nan, 2.6, 2.0, 2.0, 1.9, 1.8]]
# Initialize the figure
fig = go.Figure()
# Add trace for max date
fig.add_trace(
create_annotated_heatmap(
z=z,
x=x,
y=z,
colorscale='Courier',
reversescale=True,
zmid=1.0,
zauto=True,
font_colors=['#000000', '#FFFFFF'],
hoverongaps=False,
)
)
它引发了这个错误:
line 312, in raise_invalid_elements
raise ValueError(
ValueError:
Invalid element(s) received for the 'data' property of
Invalid elements include: [Figure({
'data': [{'colorscale': [[0.0, 'rgb(23, 28, 66)'], [0.09090909090909091,
'rgb(41, 58, 143)'], [0.18181818181818182, 'rgb(11,
102, 189)'], [0.2727272727272727, 'rgb(69, 144,
185)'], [0.36363636363636365, 'rgb(142, 181, 194)'],
[0.45454545454545453, 'rgb(210, 216, 219)'],
[0.5454545454545454, 'rgb(230, 210, 204)'],
[0.6363636363636364, 'rgb(213, 157, 137)'],
[0.7272727272727273, 'rgb(196, 101, 72)'],
[0.8181818181818182, 'rgb(172, 43, 36)'],
[0.9090909090909091, 'rgb(120, 14, 40)'], [1.0,
'rgb(60, 9, 17)']],
'hoverongaps': False,
'reversescale': True,
'showscale': False,
'type': 'heatmap',
'x': [uBQlnFPT, KngLzueS, QdEFfXmA, NMykaKna, htTJpYZw, QiIbsLNJ,
ObqTHrzi, IvoETTxO, UXljefBc, gBbjnMss, zZYNxvhB],
'y': [kRzZhZd, yybgaGj, MeDGGoI, RDBkKpq, KPZGesp, NoIxUYD, vvcQJcu,
nfuyjJx, vtdVONv, BDrOWby, SSDkBSD, aYXHtLJ, xrkMHye, HYCtaLD,
bKwSDdd, COmFvEm, lsnaNmS, LEybfTZ, JrBOAdC, LWPfXBS, hmYHIUA,
fOyooId, LyJSIlF, aFLMJEa],
'z': [[2.3, 2.5, nan, 2.1, 2.0, nan, 2.4, nan, 2.6, nan, 1.9], [2.5,
2.9, nan, nan, 2.3, nan, 2.8, nan, 2.7, 2.4, 2.2], [2.2, nan,
nan, nan, nan, nan, 2.1, nan, nan, nan, nan], [3.0, 2.0, nan,
2.0, 2.5, nan, nan, 2.0, 2.0, 2.4, nan], [2.1, 2.0, 2.3, nan,
2.0, 1.9, 2.1, 2.0, 2.2, 2.1, 2.4], [2.2, 2.1, 2.4, 2.0, 2.3,
2.1, 2.2, 2.2, 2.3, 2.1, 2.2], [2.1, nan, nan, 2.7, nan, nan,
nan, 2.0, nan, nan, nan], [2.0, 2.1, 2.1, 2.0, 2.0, 1.9, 2.2,
2.5, 2.1, 2.2, 2.0], [2.0, 2.0, 2.6, 2.1, 2.7, 2.4, 2.0, 2.3,
2.1, 2.0, 1.9], [1.9, 1.7, nan, 1.8, nan, 2.4, 2.0, 1.8, 1.9,
2.0, nan], [2.4, nan, nan, nan, nan, nan, 2.1, 2.3, 2.4, 2.6,
2.3], [2.1, 1.9, 1.8, 1.7, 2.0, 2.5, 2.1, nan, 2.2, 2.0, 2.1],
[2.6, 2.7, 2.5, 1.9, 2.3, 2.1, 2.3, nan, 2.4, 2.3, 2.4], [2.5,
2.5, 2.2, 2.1, 2.2, 1.8, 2.5, nan, 2.7, 2.4, 2.6], [2.4, nan,
nan, 2.0, nan, nan, nan, 2.0, nan, nan, nan], [2.3, 2.1, 2.0,
2.0, 2.3, 2.0, 2.1, nan, 2.3, 2.1, 2.0], [2.1, nan, nan, 1.9,
1.8, nan, nan, 2.2, 2.1, 2.3, 1.8], [2.4, nan, nan, nan, 1.5,
nan, nan, 2.0, 2.6, 1.9, nan], [2.0, 2.0, nan, nan, 2.0, nan,
nan, 2.0, 2.3, 2.1, 2.0], [2.5, 2.1, nan, nan, 2.1, 2.0, 2.2,
nan, 2.0, 2.2, 2.1], [2.2, 2.2, nan, nan, 2.5, 2.0, 2.1, 2.2,
2.2, 2.2, 2.2], [2.3, 2.6, nan, nan, nan, 2.5, nan, nan, nan,
nan, nan], [1.8, nan, nan, nan, nan, nan, nan, nan, nan, nan,
2.0], [3.0, 3.1, nan, 2.7, 2.8, nan, 2.6, 2.0, 2.0, 1.9, 1.8]],
'zauto': True,
'zmid': 1.0}],
'layout': {'annotations': [{'font': {'color': '#FFFFFF'},
'showarrow': False,
'text': '2.3',
'x': 'uBQlnFPT',
'xref': 'x',
'y': 'kRzZhZd',
'yref': 'y'},
{'font': {'color': '#FFFFFF'},
'showarrow': False,
'text': '2.5',
'x': 'KngLzueS',
'xref': 'x',
'y': 'kRzZhZd',
'yref': 'y'},
{'font': {'color': '#FFFFFF'},
'showarrow': False,
'text': 'nan',
'x': 'QdEFfXmA',
'xref': 'x',
'y': 'kRzZhZd',
'yref': 'y'},
{'font': {'color': '#FFFFFF'},
'showarrow': False,
'text': '2.1',
'x': 'NMykaKna',
'xref': 'x',
'y': 'kRzZhZd',
'yref': 'y'},
{'font': {'color': '#FFFFFF'},
'showarrow': False,
'text': '2.0',
'x': 'htTJpYZw',
'xref': 'x',
'y': 'kRzZhZd',
'yref': 'y'},
{'font': {'color': '#FFFFFF'},
'showarrow': False,
'text': 'nan',
'x': 'QiIbsLNJ',
'xref': 'x',
'y': 'kRzZhZd',
'yref': 'y'},
{'font': {'color': '#FFFFFF'},
'showarrow': False,
'text': '2.4',
'x': 'ObqTHrzi',
'xref': 'x',
'y': 'kRzZhZd',
'yref': 'y'},
{'font': {'color': '#FFFFFF'},
'showarrow': False,
'text': 'nan',
'x': 'IvoETTxO',
'xref': 'x',
'y': 'kRzZhZd',
'yref': 'y'},
{'font': {'color': '#FFFFFF'},
'showarrow': False,
'text': '2.6',
'x': 'UXljefBc',
'xref': 'x',
'y': 'kRzZhZd',
'yref': 'y'},
{'font': {'color': '#FFFFFF'},
'showarrow': False,
'text': 'nan',
'x': 'gBbjnMss',
'xref': 'x',
'y': 'kRzZhZd',
'yref': 'y'},
{'font': {'color': '#FFFFFF'},
'showarrow': False,
'text': '1.9',
'x': 'zZYNxvhB',
'xref': 'x',
'y': 'kRzZhZd',
'yref': 'y'},
{'font': {'color': '#FFFFFF'},
'showarrow': False,
'text': '2.5',
'x': 'uBQlnFPT',
'xref': 'x',
'y': 'yybgaGj',
'yref': 'y'},
{'font': {'color': '#FFFFFF'},
'showarrow': False,
'text': '2.9',
'x': 'KngLzueS',
'xref': 'x',
'y': 'yybgaGj',
'yref': 'y'},
{'font': {'color': '#FFFFFF'},
'showarrow': False,
'text': 'nan',
'x': 'QdEFfXmA',
'xref': 'x',
'y': 'yybgaGj',
'yref': 'y'},
{'font': {'color': '#FFFFFF'},
'showarrow': False,
'text': 'nan',
'x': 'NMykaKna',
'xref': 'x',
'y': 'yybgaGj',
'yref': 'y'},
{'font': {'color': '#FFFFFF'},
'showarrow': False,
'text': '2.3',
'x': 'htTJpYZw',
'xref': 'x',
'y': 'yybgaGj',
'yref': 'y'},
{'font': {'color': '#FFFFFF'},
'showarrow': False,
'text': 'nan',
'x': 'QiIbsLNJ',
'xref': 'x',
'y': 'yybgaGj',
'yref': 'y'},
{'font': {'color': '#FFFFFF'},
'showarrow': False,
'text': '2.8',
'x': 'ObqTHrzi',
'xref': 'x',
'y': 'yybgaGj',
'yref': 'y'},
{'font': {'color': '#FFFFFF'},
'showarrow': False,
'text': 'nan',
'x': 'IvoETTxO',
'xref': 'x',
'y': 'yybgaGj',
'yref': 'y'},
{'font': {'color': '#FFFFFF'},
'showarrow': False,
'text': '2.7',
'x': 'UXljefBc',
'xref': 'x',
'y': 'yybgaGj',
'yref': 'y'},
{'font': {'color': '#FFFFFF'},
'showarrow': False,
'text': '2.4',
'x': 'gBbjnMss',
'xref': 'x',
'y': 'yybgaGj',
'yref': 'y'},
{'font': {'color': '#FFFFFF'},
'showarrow': False,
'text': '2.2',
'x': 'zZYNxvhB',
'xref': 'x',
'y': 'yybgaGj',
'yref': 'y'},
{'font': {'color': '#FFFFFF'},
'showarrow': False,
'text': '2.2',
'x': 'uBQlnFPT',
'xref': 'x',
'y': 'MeDGGoI',
'yref': 'y'},
{'font': {'color': '#FFFFFF'},
'showarrow': False,
'text': 'nan',
'x': 'KngLzueS',
'xref': 'x',
'y': 'MeDGGoI',
'yref': 'y'},
{'font': {'color': '#FFFFFF'},
'showarrow': False,
'text': 'nan',
'x': 'QdEFfXmA',
'xref': 'x',
'y': 'MeDGGoI',
'yref': 'y'},
{'font': {'color': '#FFFFFF'},
我认为这与我们调用热图时使用
fig.add_trace
的事实有关。有什么建议如何解决问题吗?
测试你的代码,它似乎可以正常工作。第一个是一个小细节,但
Courier
不是 Plotly 中的色标。将其更改为有效的色标即可解决。第二点是您将一个完整的图形添加到另一个图形中:Plotly 文档。前进的方法有两种:将 create_annotated_heatmap
的结果添加为迹线或使用结果作为图形。
选项1:
import plotly.graph_objects as go
# Initialize the figure
fig = go.Figure()
# Add trace for max date
fig.add_trace(
create_annotated_heatmap(
z=z,
x=x,
y=z,
colorscale='agsunset',
reversescale=True,
zmid=1.0,
zauto=True,
font_colors=['#000000', '#FFFFFF'],
hoverongaps=False,
).data[0]
)
选项2:
import plotly.graph_objects as go
fig = create_annotated_heatmap(z=z,
x=x,
y=z,
colorscale='agsunset',
reversescale=True,
zmid=1.0,
zauto=True,
font_colors=['#000000', '#FFFFFF'],
hoverongaps=False,
)