如何使用Python Plotly库在点击图表空白区域时触发回调?
Plotly K线图检测空白区域点击的实现方案
核心思路
Plotly默认只响应数据点的点击,要检测空白区域,需要先捕获所有点击事件,再判断点击位置是否落在K线数据的范围内——若未命中任何K线,则判定为空白点击,执行后续操作。
方法一:Plotly.js(前端)实现
通过监听plotly_click事件获取点击坐标,遍历K线数据对比范围:
// 获取图表容器 const chart = document.getElementById('candlestick-chart'); // 监听点击事件 chart.on('plotly_click', function(eventData) { const clickX = eventData.points[0].x; // 点击的时间轴数值 const clickY = eventData.points[0].y; // 点击的价格轴数值 const candlestickData = eventData.data[0]; // K线数据系列 let isOnKline = false; // 遍历每根K线,判断点击位置是否落在K线范围内 for (let i = 0; i < candlestickData.x.length; i++) { const currX = candlestickData.x[i]; // 计算当前K线的时间边界(取相邻K线的中间值) const nextX = candlestickData.x[i+1] || currX + (currX - candlestickData.x[i-1]); const xMin = currX - (nextX - currX)/2; const xMax = currX + (nextX - currX)/2; // K线的价格边界是low到high const yMin = candlestickData.low[i]; const yMax = candlestickData.high[i]; if (clickX >= xMin && clickX <= xMax && clickY >= yMin && clickY <= yMax) { isOnKline = true; break; } } // 未命中任何K线,执行空白点击操作 if (!isOnKline) { console.log(`在价格${clickY}处添加提醒`); // 这里可以添加绘制水平线、弹出提醒框等逻辑 } });
方法二:Dash(Python)实现
Dash中clickData仅返回数据点点击信息,需添加透明散点层覆盖整个图表区域,确保任何位置点击都能触发事件,再判断是否命中K线:
import dash from dash import dcc, html, Input, Output, State import plotly.graph_objects as go import pandas as pd # 模拟K线数据 df = pd.DataFrame({ 'x': pd.date_range(start='2024-01-01', periods=10), 'open': [100, 102, 101, 105, 104, 106, 108, 107, 109, 110], 'high': [103, 104, 103, 106, 105, 107, 110, 109, 111, 112], 'low': [99, 101, 99, 103, 102, 104, 106, 105, 107, 108], 'close': [102, 101, 103, 104, 106, 107, 109, 108, 110, 111] }) app = dash.Dash(__name__) app.layout = html.Div([ dcc.Graph(id='candlestick-chart'), html.Div(id='alert-info') ]) @app.callback( Output('candlestick-chart', 'figure'), Input('candlestick-chart', 'clickData'), State('candlestick-chart', 'figure') ) def update_chart(clickData, fig): if clickData: click_x = clickData['points'][0]['x'] click_y = clickData['points'][0]['y'] candlestick_data = fig['data'][0] is_on_kline = False # 遍历K线判断点击位置 for i in range(len(candlestick_data['x'])): curr_x = pd.Timestamp(candlestick_data['x'][i]) next_x = pd.Timestamp(candlestick_data['x'][i+1]) if i+1 < len(candlestick_data['x']) else curr_x + pd.Timedelta(days=1) x_min = curr_x - (next_x - curr_x)/2 x_max = curr_x + (next_x - curr_x)/2 y_min = candlestick_data['low'][i] y_max = candlestick_data['high'][i] if pd.Timestamp(click_x) >= x_min and pd.Timestamp(click_x) <= x_max and click_y >= y_min and click_y <= y_max: is_on_kline = True break # 空白点击,添加价格提醒水平线 if not is_on_kline: fig['data'].append(go.Scatter( x=[fig['layout']['xaxis']['range'][0], fig['layout']['xaxis']['range'][1]], y=[click_y, click_y], mode='lines', line=dict(color='red', dash='dash'), name=f'提醒价格: {click_y}' )) else: # 初始化图表,添加透明散点层 fig = go.Figure(data=[ go.Candlestick( x=df['x'], open=df['open'], high=df['high'], low=df['low'], close=df['close'] ), go.Scatter( x=df['x'], y=[df['low'].min()] * len(df['x']), mode='markers', marker=dict(size=0, opacity=0), # 完全透明不可见 hoverinfo='none' ) ]) fig.update_layout(xaxis_rangeslider_visible=False) return fig @app.callback( Output('alert-info', 'children'), Input('candlestick-chart', 'clickData'), State('candlestick-chart', 'figure') ) def show_alert(clickData, fig): if clickData: click_y = clickData['points'][0]['y'] candlestick_data = fig['data'][0] is_on_kline = False for i in range(len(candlestick_data['x'])): curr_x = pd.Timestamp(candlestick_data['x'][i]) next_x = pd.Timestamp(candlestick_data['x'][i+1]) if i+1 < len(candlestick_data['x']) else curr_x + pd.Timedelta(days=1) x_min = curr_x - (next_x - curr_x)/2 x_max = curr_x + (next_x - curr_x)/2 y_min = candlestick_data['low'][i] y_max = candlestick_data['high'][i] if pd.Timestamp(clickData['points'][0]['x']) >= x_min and pd.Timestamp(clickData['points'][0]['x']) <= x_max and click_y >= y_min and click_y <= y_max: is_on_kline = True break if not is_on_kline: return f"已在价格 {click_y} 处添加提醒" return "" if __name__ == '__main__': app.run_server(debug=True)
注意事项
- 时间轴边界判断需匹配实际数据类型(时间戳、字符串等)
- 透明散点层的y值需覆盖图表完整价格范围,确保所有位置点击都能触发事件
- 若图表支持缩放,需在回调中获取当前x/y轴范围,调整判断逻辑
内容的提问来源于stack exchange,提问作者Wakeman
相关产品推荐
相关产品推荐

