fix(web): 自动刷新保留 K 线视窗;威科夫与图表增量更新
自动刷新改用 tail update 与 scrollToPosition 恢复视窗,避免 setData 后跳到最右;拆分 chart_tv 模块并扩展 analyze/recent API。同步威科夫分析、pipeline 增量构建及相关策略与配置。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,239 @@
|
||||
/* chart_tv_finalize.js — time sync / bindSync / view restore / tooltip */
|
||||
|
||||
function chartTvFinalize(ctx) {
|
||||
var symbol = ctx.symbol;
|
||||
var timeframe = ctx.timeframe;
|
||||
var symbolConfig = ctx.symbolConfig;
|
||||
var useSubSubPeriod = ctx.useSubSubPeriod;
|
||||
var useElementPeriod = ctx.useElementPeriod;
|
||||
var klinePeriodLabel = ctx.klinePeriodLabel;
|
||||
var candles = ctx.candles;
|
||||
var klineDataSource = ctx.klineDataSource;
|
||||
var container = ctx.container;
|
||||
var showMacd = ctx.showMacd;
|
||||
var showOriginalKline = ctx.showOriginalKline;
|
||||
var mainChartContainer = ctx.mainChartContainer;
|
||||
var volumeChartContainer = ctx.volumeChartContainer;
|
||||
var atrChartContainer = ctx.atrChartContainer;
|
||||
var macdChartContainer = ctx.macdChartContainer;
|
||||
var chanMacdChartContainer = ctx.chanMacdChartContainer;
|
||||
var mainChart = ctx.mainChart;
|
||||
var volumeChart = ctx.volumeChart;
|
||||
var atrChart = ctx.atrChart;
|
||||
var macdChart = ctx.macdChart;
|
||||
var chanMacdChart = ctx.chanMacdChart;
|
||||
var createChartOptions = ctx.createChartOptions;
|
||||
// 同步所有图表的时间轴配置
|
||||
const hasPendingRestoreView = !!window._pendingRestoreView;
|
||||
const pendingView = window._pendingRestoreView;
|
||||
const syncTimeScaleSettings = () => {
|
||||
const baseOptions = {
|
||||
timeVisible: true,
|
||||
secondsVisible: false,
|
||||
borderColor: '#ddd',
|
||||
lockVisibleTimeRangeOnResize: true,
|
||||
// 关键:确保所有图表边缘行为完全一致
|
||||
fixLeftEdge: false,
|
||||
fixRightEdge: false,
|
||||
// 确保时间刻度行为一致
|
||||
ticksVisible: true,
|
||||
minimumHeight: 0,
|
||||
};
|
||||
// 有待恢复视图时不要先写 barSpacing/rightOffset(会钉右缘导致图往右偏),
|
||||
// 交给后面 setVisibleRange 一次锁定位置+缩放。
|
||||
if (!pendingView) {
|
||||
baseOptions.barSpacing = symbolConfig.type === 'a_stock' ? 6 : 10;
|
||||
baseOptions.rightOffset = 12;
|
||||
}
|
||||
|
||||
console.log('🔧 同步时间轴设置:', baseOptions);
|
||||
|
||||
// 应用相同的设置到所有图表
|
||||
mainChart.timeScale().applyOptions(baseOptions);
|
||||
volumeChart.timeScale().applyOptions(baseOptions);
|
||||
atrChart.timeScale().applyOptions(baseOptions);
|
||||
if (showMacd && macdChart) {
|
||||
macdChart.timeScale().applyOptions(baseOptions);
|
||||
}
|
||||
};
|
||||
|
||||
// 首先同步时间轴设置
|
||||
syncTimeScaleSettings();
|
||||
|
||||
// 仅在没有待恢复视图时,设置默认可见范围
|
||||
const totalBars = candles ? candles.length : 0;
|
||||
const visibleBarsCount = 200;
|
||||
const allChartsNow = [mainChart, volumeChart, atrChart]
|
||||
.concat(showMacd && macdChart ? [macdChart] : [])
|
||||
.concat(showMacd && chanMacdChart ? [chanMacdChart] : []);
|
||||
if (hasPendingRestoreView && pendingView) {
|
||||
restoreChartViewState(allChartsNow, pendingView, { preferTime: true });
|
||||
} else {
|
||||
// 显示最近 200 根K线而非全部挤压(避免K线过多时重叠)
|
||||
if (totalBars > visibleBarsCount) {
|
||||
const rangeFrom = totalBars - visibleBarsCount;
|
||||
const rangeTo = totalBars + 12;
|
||||
mainChart.timeScale().setVisibleLogicalRange({ from: rangeFrom, to: rangeTo });
|
||||
} else {
|
||||
mainChart.timeScale().fitContent();
|
||||
}
|
||||
}
|
||||
|
||||
// 立即同步其他图表到主图表的范围(无 pending 时)
|
||||
setTimeout(() => {
|
||||
if (window._pendingRestoreView) {
|
||||
restoreChartViewState(allChartsNow, window._pendingRestoreView, { preferTime: true });
|
||||
return;
|
||||
}
|
||||
const logRange = mainChart.timeScale().getVisibleLogicalRange();
|
||||
if (logRange) {
|
||||
console.log('🔧 同步可见范围:', logRange);
|
||||
volumeChart.timeScale().setVisibleLogicalRange(logRange);
|
||||
atrChart.timeScale().setVisibleLogicalRange(logRange);
|
||||
if (showMacd && macdChart) {
|
||||
macdChart.timeScale().setVisibleLogicalRange(logRange);
|
||||
}
|
||||
if (showMacd && chanMacdChart) {
|
||||
chanMacdChart.timeScale().setVisibleLogicalRange(logRange);
|
||||
}
|
||||
console.log('🔧 时间轴同步完成');
|
||||
}
|
||||
}, 50);
|
||||
|
||||
// 保存图表对象
|
||||
tvWidget.mainChart = mainChart;
|
||||
tvWidget.volumeChart = volumeChart;
|
||||
tvWidget.atrChart = atrChart;
|
||||
tvWidget.macdChart = macdChart;
|
||||
tvWidget.chanMacdChart = chanMacdChart;
|
||||
tvWidget.state.isInitialized = true;
|
||||
// 注册窗口卸载时释放资源,避免GPU内存泄漏
|
||||
window.onbeforeunload = function() {
|
||||
try {
|
||||
if (tvWidget && tvWidget.state && tvWidget.state.isInitialized) {
|
||||
if (tvWidget.mainChart && typeof tvWidget.mainChart.remove === 'function') tvWidget.mainChart.remove();
|
||||
if (tvWidget.volumeChart && typeof tvWidget.volumeChart.remove === 'function') tvWidget.volumeChart.remove();
|
||||
if (tvWidget.macdChart && typeof tvWidget.macdChart.remove === 'function') tvWidget.macdChart.remove();
|
||||
if (tvWidget.chanMacdChart && typeof tvWidget.chanMacdChart.remove === 'function') tvWidget.chanMacdChart.remove();
|
||||
if (tvWidget.atrChart && typeof tvWidget.atrChart.remove === 'function') tvWidget.atrChart.remove();
|
||||
}
|
||||
} catch (e) {}
|
||||
};
|
||||
|
||||
// 初始化默认均线/布林带配置(仅在首次初始化时)
|
||||
if (!hasInitializedDefaultMAs && movingAverages.length === 0) {
|
||||
console.log('初始化默认均线与布林带指标');
|
||||
|
||||
if (typeof maIdCounter !== 'number' || !Number.isFinite(maIdCounter)) {
|
||||
maIdCounter = 0;
|
||||
}
|
||||
if (typeof bbIdCounter !== 'number' || !Number.isFinite(bbIdCounter)) {
|
||||
bbIdCounter = 0;
|
||||
}
|
||||
|
||||
const defaultMAs = [
|
||||
{ type: 'EMA', length: 26, color: '#FF8C00', name: 'EMA26', visible: false }, // 橙色
|
||||
{ type: 'EMA', length: 52, color: '#000000', name: 'EMA52', visible: true }, // 黑色 · 默认开
|
||||
{ type: 'SMA', length: 30, color: '#1E90FF', name: 'MA30', visible: true }, // 蓝色 · 默认开
|
||||
{ type: 'SMA', length: 250, color: '#800080', name: 'MA250', visible: true } // 紫色 · 默认开
|
||||
];
|
||||
|
||||
defaultMAs.forEach(ma => {
|
||||
const config = {
|
||||
id: ++maIdCounter,
|
||||
type: ma.type,
|
||||
length: ma.length,
|
||||
source: 'close',
|
||||
smoothType: 'none',
|
||||
smoothLength: 3,
|
||||
lineWidth: 1, // 1px线宽
|
||||
lineStyle: 0, // 实线
|
||||
color: ma.color,
|
||||
visible: ma.visible
|
||||
};
|
||||
|
||||
movingAverages.push(config);
|
||||
console.log(`添加默认${ma.name}:`, ma.color);
|
||||
});
|
||||
|
||||
if (bollingerBands.length === 0) {
|
||||
const defaultBB = {
|
||||
id: ++bbIdCounter,
|
||||
type: 'Bollinger Bands',
|
||||
length: 20,
|
||||
upperMultiplier: 2,
|
||||
lowerMultiplier: 2,
|
||||
source: 'close',
|
||||
lineWidth: 1,
|
||||
lineStyle: 0,
|
||||
upperColor: '#ff6b6b',
|
||||
middleColor: '#ffffff',
|
||||
lowerColor: '#ff6b6b',
|
||||
visible: false
|
||||
};
|
||||
bollingerBands.push(defaultBB);
|
||||
console.log('添加默认布林带: BB(20, 2, 2)');
|
||||
}
|
||||
|
||||
console.log('默认指标配置完成,当前均线数量', movingAverages.length, '布林带数量', bollingerBands.length);
|
||||
hasInitializedDefaultMAs = true;
|
||||
}
|
||||
|
||||
// 添加均线到图表
|
||||
addMovingAveragesToChart(candles);
|
||||
|
||||
// 添加布林带到图表
|
||||
addBollingerBandsToChart(candles);
|
||||
|
||||
// 更新技术指标面板显示
|
||||
updateIndicatorPanel();
|
||||
|
||||
// 绑定同步事件
|
||||
bindSyncEvents(mainChartContainer, volumeChartContainer, atrChartContainer, macdChartContainer, chanMacdChartContainer, mainChart, volumeChart, atrChart, macdChart, chanMacdChart, showMacd);
|
||||
|
||||
// 最终确保所有图表时间轴对齐(同时恢复刷新前保存的缩放/位置)
|
||||
setTimeout(() => {
|
||||
const allCharts = [mainChart, volumeChart, atrChart];
|
||||
if (showMacd && macdChart) allCharts.push(macdChart);
|
||||
if (showMacd && chanMacdChart) allCharts.push(chanMacdChart);
|
||||
|
||||
// 检查是否有待恢复的视图(缩放 + 位置)
|
||||
const pending = window._pendingRestoreView;
|
||||
window._pendingRestoreView = null;
|
||||
|
||||
if (pending) {
|
||||
// 恢复刷新前的缩放和位置(时间范围优先,避免数据滑动后逻辑索引错位)
|
||||
console.log('📌 恢复图表视图:', JSON.stringify(pending));
|
||||
restoreChartViewState(allCharts, pending, { preferTime: true });
|
||||
} else {
|
||||
// 无保存视图,正常同步主图到子图
|
||||
const visibleRange = mainChart.timeScale().getVisibleRange();
|
||||
if (visibleRange) {
|
||||
console.log('🔧 最终同步可见范围:', visibleRange);
|
||||
[volumeChart, atrChart].concat(
|
||||
showMacd && macdChart ? [macdChart] : [],
|
||||
showMacd && chanMacdChart ? [chanMacdChart] : []
|
||||
).forEach(c => {
|
||||
try { c.timeScale().setVisibleRange(visibleRange); } catch(e) {}
|
||||
});
|
||||
}
|
||||
}
|
||||
console.log('🔧 最终时间轴对齐完成');
|
||||
}, 150);
|
||||
// 只有在时间输入框都为空时才设置图表默认时间范围
|
||||
if (!$('#start_time').val() && !$('#end_time').val()) {
|
||||
setDefaultTimeRange();
|
||||
}
|
||||
|
||||
// 添加买卖点提示
|
||||
// 初始化 tooltip 与 U 显示状态
|
||||
window.showUOnMain = $('#toggleUOnMain').is(':checked');
|
||||
window.showUOnElement = $('#toggleUOnElement').is(':checked');
|
||||
setupTooltip(mainChart, [], [], mainChartContainer, volumeChartContainer, atrChartContainer, macdChartContainer, chanMacdChartContainer, volumeChart, atrChart, macdChart, chanMacdChart, showMacd);
|
||||
|
||||
// 更新EMA52显示
|
||||
if (currentData) {
|
||||
updateEMA52Display(currentData);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user