Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions common/changes/@visactor/vtable/fix-datazoom_2026-02-10-08-35.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"changes": [
{
"comment": "fix: fix datazoom version update\n\n",
"type": "none",
"packageName": "@visactor/vtable"
}
],
"packageName": "@visactor/vtable",
"email": "laijingkang@kuaishou.com"
}
19 changes: 18 additions & 1 deletion docs/assets/demo/en/gantt/gantt-datazoom.md
Original file line number Diff line number Diff line change
Expand Up @@ -891,10 +891,27 @@ function createZoomControls(ganttInstance) {

radio.onchange = () => {
if (radio.checked) {
// Switch to middle state of corresponding level
// 🎯 Zoom with view center as anchor point
// 1. Get the time point corresponding to the current view center
const scrollLeft = ganttInstance.stateManager.scrollLeft;
const viewportWidth = ganttInstance.tableNoFrameWidth;
const centerScrollPos = scrollLeft + viewportWidth / 2;

const currentMsPerPixel = ganttInstance.getCurrentMillisecondsPerPixel();
const centerTime = ganttInstance.parsedOptions._minDateTime + centerScrollPos * currentMsPerPixel;

// 2. Switch to corresponding level
ganttInstance.zoomScaleManager?.setZoomPosition({
levelNum: index
});

// 3. Adjust scroll position to keep center time point at view center
const newMsPerPixel = ganttInstance.getCurrentMillisecondsPerPixel();
const newCenterScrollPos = (centerTime - ganttInstance.parsedOptions._minDateTime) / newMsPerPixel;
const newScrollLeft = newCenterScrollPos - viewportWidth / 2;

ganttInstance.stateManager.setScrollLeft(Math.max(0, newScrollLeft));

updateStatusDisplay();
}
};
Expand Down
19 changes: 18 additions & 1 deletion docs/assets/demo/en/gantt/gantt-zoom.md
Original file line number Diff line number Diff line change
Expand Up @@ -911,10 +911,27 @@ function createZoomControls(ganttInstance) {

radio.onchange = () => {
if (radio.checked) {
// Switch to the middle state of the corresponding level
// 🎯 Zoom with view center as anchor point
// 1. Get the time point corresponding to the current view center
const scrollLeft = ganttInstance.stateManager.scrollLeft;
const viewportWidth = ganttInstance.tableNoFrameWidth;
const centerScrollPos = scrollLeft + viewportWidth / 2;

const currentMsPerPixel = ganttInstance.getCurrentMillisecondsPerPixel();
const centerTime = ganttInstance.parsedOptions._minDateTime + centerScrollPos * currentMsPerPixel;

// 2. Switch to corresponding level
ganttInstance.zoomScaleManager?.setZoomPosition({
levelNum: index
});

// 3. Adjust scroll position to keep center time point at view center
const newMsPerPixel = ganttInstance.getCurrentMillisecondsPerPixel();
const newCenterScrollPos = (centerTime - ganttInstance.parsedOptions._minDateTime) / newMsPerPixel;
const newScrollLeft = newCenterScrollPos - viewportWidth / 2;

ganttInstance.stateManager.setScrollLeft(Math.max(0, newScrollLeft));

updateStatusDisplay();
}
};
Expand Down
19 changes: 18 additions & 1 deletion docs/assets/demo/zh/gantt/gantt-datazoom.md
Original file line number Diff line number Diff line change
Expand Up @@ -892,10 +892,27 @@ function createZoomControls(ganttInstance) {

radio.onchange = () => {
if (radio.checked) {
// 切换到对应级别的中间状态
// 🎯 以视图中心为缩放中心
// 1. 获取当前视图中心对应的时间点
const scrollLeft = ganttInstance.stateManager.scrollLeft;
const viewportWidth = ganttInstance.tableNoFrameWidth;
const centerScrollPos = scrollLeft + viewportWidth / 2;

const currentMsPerPixel = ganttInstance.getCurrentMillisecondsPerPixel();
const centerTime = ganttInstance.parsedOptions._minDateTime + centerScrollPos * currentMsPerPixel;

// 2. 切换到对应级别
ganttInstance.zoomScaleManager?.setZoomPosition({
levelNum: index
});

// 3. 调整滚动位置,使中心时间点保持在视图中心
const newMsPerPixel = ganttInstance.getCurrentMillisecondsPerPixel();
const newCenterScrollPos = (centerTime - ganttInstance.parsedOptions._minDateTime) / newMsPerPixel;
const newScrollLeft = newCenterScrollPos - viewportWidth / 2;

ganttInstance.stateManager.setScrollLeft(Math.max(0, newScrollLeft));

updateStatusDisplay();
}
};
Expand Down
19 changes: 18 additions & 1 deletion docs/assets/demo/zh/gantt/gantt-zoom.md
Original file line number Diff line number Diff line change
Expand Up @@ -882,10 +882,27 @@ function createZoomControls(ganttInstance) {

radio.onchange = () => {
if (radio.checked) {
// 切换到对应级别的中间状态
// 🎯 以视图中心为缩放中心
// 1. 获取当前视图中心对应的时间点
const scrollLeft = ganttInstance.stateManager.scrollLeft;
const viewportWidth = ganttInstance.tableNoFrameWidth;
const centerScrollPos = scrollLeft + viewportWidth / 2;

const currentMsPerPixel = ganttInstance.getCurrentMillisecondsPerPixel();
const centerTime = ganttInstance.parsedOptions._minDateTime + centerScrollPos * currentMsPerPixel;

// 2. 切换到对应级别
ganttInstance.zoomScaleManager?.setZoomPosition({
levelNum: index
});

// 3. 调整滚动位置,使中心时间点保持在视图中心
const newMsPerPixel = ganttInstance.getCurrentMillisecondsPerPixel();
const newCenterScrollPos = (centerTime - ganttInstance.parsedOptions._minDateTime) / newMsPerPixel;
const newScrollLeft = newCenterScrollPos - viewportWidth / 2;

ganttInstance.stateManager.setScrollLeft(Math.max(0, newScrollLeft));

updateStatusDisplay();
}
};
Expand Down
19 changes: 18 additions & 1 deletion packages/vtable-gantt/examples/gantt/gantt-zoom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1502,10 +1502,27 @@ function createZoomControls(ganttInstance: Gantt) {

radio.onchange = () => {
if (radio.checked) {
// 切换到对应级别的中间状态
// 🎯 以视图中心为缩放中心
// 1. 获取当前视图中心对应的时间点
const scrollLeft = ganttInstance.stateManager.scrollLeft;
const viewportWidth = ganttInstance.tableNoFrameWidth;
const centerScrollPos = scrollLeft + viewportWidth / 2;

const currentMsPerPixel = ganttInstance.getCurrentMillisecondsPerPixel();
const centerTime = ganttInstance.parsedOptions._minDateTime + centerScrollPos * currentMsPerPixel;

// 2. 切换到对应级别
ganttInstance.zoomScaleManager?.setZoomPosition({
levelNum: index
});

// 3. 调整滚动位置,使中心时间点保持在视图中心
const newMsPerPixel = ganttInstance.getCurrentMillisecondsPerPixel();
const newCenterScrollPos = (centerTime - ganttInstance.parsedOptions._minDateTime) / newMsPerPixel;
const newScrollLeft = newCenterScrollPos - viewportWidth / 2;

ganttInstance.stateManager.setScrollLeft(Math.max(0, newScrollLeft));

updateStatusDisplay();
}
};
Expand Down
98 changes: 74 additions & 24 deletions packages/vtable-gantt/src/zoom-scale/DataZoomIntegration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export class DataZoomIntegration {
private isUpdatingFromDataZoom = false;
private isUpdatingFromGantt = false;
private lastDataZoomState = { start: 0.2, end: 0.5 };
private lastSpanLimits: { minSpan: number; maxSpan: number } | null = null;
private cleanupCallbacks: (() => void)[] = [];
private resizeTimeout: NodeJS.Timeout | null = null;
private isInitializing = true;
Expand All @@ -47,7 +48,33 @@ export class DataZoomIntegration {
this.gantt = gantt;
this.initializeDataZoom(config);
this.setupEventListeners();
this.updateDataZoomLimits();

// 延迟初始化 limits 和同步位置,等待 Gantt 完全就绪
setTimeout(() => {
this.updateDataZoomLimits();
this.syncDataZoomToGanttView();
}, 0);
}

/**
* 同步 DataZoom 位置到 Gantt 当前视图
* 根据 Gantt 的实际滚动位置和视口大小,计算并设置 DataZoom 的 start/end
*/
private syncDataZoomToGanttView(): void {
try {
const scrollLeft = this.gantt.stateManager?.scrollLeft || 0;
const totalWidth = this.gantt.getAllDateColsWidth?.() || 0;
const viewportWidth = this.gantt.tableNoFrameWidth || 0;

if (totalWidth > 0 && viewportWidth > 0) {
const start = Math.max(0, Math.min(1, scrollLeft / totalWidth));
const end = Math.max(0, Math.min(1, (scrollLeft + viewportWidth) / totalWidth));

this.dataZoomAxis.setStartAndEnd(start, end);
}
} catch (error) {
console.warn('⚠️ 无法同步 DataZoom 位置:', error);
}
}

/**
Expand Down Expand Up @@ -236,15 +263,14 @@ export class DataZoomIntegration {
if (start !== undefined && end !== undefined && !isNaN(start) && !isNaN(end)) {
this.applyDataZoomRangeToGantt(start, end);
}

setTimeout(() => {
this.isUpdatingFromDataZoom = false;
}, 50);
this.updateDataZoomLimits();
this.isUpdatingFromDataZoom = false;
};

this.dataZoomAxis.addEventListener('change', dataZoomChangeHandler);
this.dataZoomAxis.addEventListener('dataZoomChange', dataZoomChangeHandler);

this.cleanupCallbacks.push(() => {
this.dataZoomAxis.removeEventListener('change', dataZoomChangeHandler);
this.dataZoomAxis.removeEventListener('dataZoomChange', dataZoomChangeHandler);
});

// Gantt 滚动时同步到 DataZoom
Expand All @@ -262,10 +288,8 @@ export class DataZoomIntegration {

this.stage.render();

setTimeout(() => {
this.dataZoomAxis.setAttribute('disableTriggerEvent', false);
this.isUpdatingFromGantt = false;
}, 10);
this.dataZoomAxis.setAttribute('disableTriggerEvent', false);
this.isUpdatingFromGantt = false;
};

this.gantt.addEventListener('scroll', ganttScrollHandler);
Expand All @@ -277,9 +301,9 @@ export class DataZoomIntegration {
const windowResizeHandler = () => {
clearTimeout(this.resizeTimeout);
this.resizeTimeout = setTimeout(() => {
this.updateResponsive();
this.updateDataZoomLimits();
}, 50);
this.updateResponsive();
}, 70);
};

window.addEventListener('resize', windowResizeHandler);
Expand All @@ -292,13 +316,20 @@ export class DataZoomIntegration {

// 监听 Gantt 的缩放事件,重新计算限制并同步 DataZoom 位置
const ganttZoomHandler = () => {
setTimeout(() => {
this.updateDataZoomLimits();
// 同步 DataZoom 位置到当前 Gantt 视图
if (!this.isUpdatingFromDataZoom) {
this.syncToDataZoom();
}
}, 50);
if (this.isUpdatingFromDataZoom) {
return;
}

this.isUpdatingFromGantt = true;

this.updateDataZoomLimits();

// 同步 DataZoom 位置到当前 Gantt 视图
if (!this.isUpdatingFromDataZoom) {
this.syncToDataZoom();
}

this.isUpdatingFromGantt = false;
};

this.gantt.addEventListener('zoom', ganttZoomHandler);
Expand Down Expand Up @@ -353,11 +384,32 @@ export class DataZoomIntegration {
private updateDataZoomLimits(): void {
const limits = this.calculateDataZoomLimits();

// 只在值改变时才更新,避免无效的 setAttributes 调用
const hasChanged =
!this.lastSpanLimits ||
limits.minRangeRatio !== this.lastSpanLimits.minSpan ||
limits.maxRangeRatio !== this.lastSpanLimits.maxSpan;

if (!hasChanged) {
return;
}
// 使用 lastDataZoomState 记录的当前位置
const start = this.lastDataZoomState.start;
const end = this.lastDataZoomState.end;

this.dataZoomAxis.setAttributes({
minSpan: limits.minRangeRatio,
maxSpan: limits.maxRangeRatio
maxSpan: limits.maxRangeRatio,
start,
end
});

// 更新缓存
this.lastSpanLimits = {
minSpan: limits.minRangeRatio,
maxSpan: limits.maxRangeRatio
};

this.stage.render();
}

Expand Down Expand Up @@ -536,9 +588,7 @@ export class DataZoomIntegration {
this.updatePosition(defaultX, 0);

// 重新同步 DataZoom 状态,因为视图宽度变化会影响时间范围的显示比例
setTimeout(() => {
this.syncToDataZoom();
}, 10);
this.syncToDataZoom();
}

/**
Expand Down
Loading