Hover information solely on the x and y axes, like tradingview

oaF1I

Similar to the TradingView chart above, how can I ensure that the x-axis and y-axis always display the current mouse hover position values when using spikemode=‘across’ and spikesnap=‘cursor’ in Plotly? This is particularly useful for financial chart analysis, similar to the effect I achieved using Dash’s callback function. However, it would be preferable if Plotly could support this natively. Below is a simple code snippet demonstrating how to achieve this functionality using Dash (for illustrative purposes only).

import dash
from dash import dcc, html, Input, Output, State
import plotly.graph_objects as go
import numpy as np

app = dash.Dash(__name__)

# 生成示例数据
x = np.linspace(0, 10, 100)
y = np.sin(x)

fig = go.Figure(go.Scatter(x=x, y=y))
fig.update_layout(
    yaxis=dict(
        side="right",  # Y 轴在右侧
        spikemode="across",  # 横线横穿到 Y 轴
        spikesnap="cursor",  # 吸附到鼠标
        spikethickness=1,
        spikecolor="#999",
        showticklabels=True,  # 隐藏默认刻度标签
        # ticks=""  # 隐藏刻度线
    ),
    hoverdistance=100,
    spikedistance=1000,
    showlegend=True,
    hovermode='xy',
)

app.layout = html.Div([
    dcc.Graph(id="graph", figure=fig),
    dcc.Store(id="mouse-data")  # 存储鼠标位置
])


@app.callback(
    Output("graph", "figure"),
    Input("graph", "hoverData"),
    State("graph", "figure")
)
def update_annotation(hover_data, fig):
    if not hover_data:
        # 无悬停时,移除注释
        fig["layout"].pop("annotations", None)
        return fig

    # 获取悬停点的 Y 值
    y_val = hover_data["points"][0]["y"]

    # 添加动态注释(右侧 Y 轴对应位置)
    fig["layout"]["annotations"] = [{
        "x": 1,  # 绘图区右侧边缘(x 轴比例 0~1)
        "y": y_val,
        "xref": "paper",  # x 基于绘图区比例
        "yref": "y",  # y 基于 Y 轴数值
        "text": f"{y_val:.2f}",  # 显示 Y 值(保留2位小数)
        "showarrow": False,
        "xanchor": "left",  # 文本左对齐(紧贴 Y 轴)
        "bgcolor": "white",  # 白色背景
        "bordercolor": "#ccc",  # 边框颜色
        "borderwidth": 1,  # 边框宽度
        "borderpad": 6,  # 边框内边距
    }]
    return fig


if __name__ == "__main__":
    app.run(debug=True, port=8051)