Skip to content
Open
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
6 changes: 4 additions & 2 deletions .cursor/rules/specify-rules.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
Auto-generated from all feature plans. Last updated: 2026-01-15

## Active Technologies
- TypeScript 4.x+ (Project uses TS) + `@visactor/vchart` (Core logic), `@visactor/react-vchart` (React wrapper) (007-fix-datazoom-react)
- N/A (In-memory chart state) (007-fix-datazoom-react)

- TypeScript/React 18 + @visactor/react-vchart, @visactor/vchar (001-react-vchart-demo)

Expand All @@ -24,11 +26,11 @@ npm test && npm run lint
TypeScript 4.9.5: Follow standard conventions

## Recent Changes
- 007-fix-datazoom-react: Added TypeScript 4.x+ (Project uses TS) + `@visactor/vchart` (Core logic), `@visactor/react-vchart` (React wrapper)
- 007-fix-datazoom-react: Added [if applicable, e.g., PostgreSQL, CoreData, files or N/A]

- 001-react-vchart-demo: Added TypeScript/React 18 + @visactor/react-vchart, @visactor/vchar
- 001-react-vchart-demo: Added [if applicable, e.g., PostgreSQL, CoreData, files or N/A]

- 001-scrollbar-wheel-step: Added TypeScript 4.9.5 + @visactor/vchart, @visactor/vrender-components (~1.0.37), @visactor/vutils

<!-- MANUAL ADDITIONS START -->
<!-- MANUAL ADDITIONS END -->
27 changes: 27 additions & 0 deletions .trae/documents/plan_20260214_031224.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Implementation Plan: Fix DataZoom Value Update on Data Change in React

**Branch**: `007-fix-datazoom-react`
**Spec**: [specs/007-fix-datazoom-react/spec.md](../spec.md)

## Summary
I have planned the fix for Issue #4185 where DataZoom fails to update in React when data changes. The plan involves detecting data view changes in the core `DataFilterBaseComponent` and rebinding listeners, ensuring the component stays in sync with the latest data.

## Phase 1: Setup (Reproduction)
- [ ] Initialize reproduction in `packages/react-vchart/demo/src/BugReproduction.tsx`
- [ ] Update demo entry point to run reproduction

## Phase 3: User Story 1 (Core Fix)
- [ ] Implement `_collectDataInfo` helper to gather data view references
- [ ] Add state tracking for `_currentDataCollection` and bound listeners
- [ ] Update `_initData` to initialize tracking
- [ ] Implement robust `onDataUpdate` logic:
- Detect if underlying DataViews have changed
- Unbind old listeners / Bind new listeners
- Re-run data transforms to update domain
- **Crucial**: Apply explicit `start`/`end` from spec if present (resetting range as per user config)

## Phase N: Polish
- [ ] Clean up reproduction code
- [ ] Restore demo entry point

I am ready to proceed with the implementation. Please confirm.
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"changes": [
{
"packageName": "@visactor/react-vchart",
"comment": "fix: ensure DataZoom updates when data source changes (Issue #4185)",
"type": "patch"
}
],
"packageName": "@visactor/react-vchart",
"email": "test@example.com"
}
11 changes: 11 additions & 0 deletions common/changes/@visactor/vchart/fix-datazoom-update_20250209.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"changes": [
{
"packageName": "@visactor/vchart",
"comment": "fix: ensure DataZoom updates when data source changes (Issue #4185)",
"type": "patch"
}
],
"packageName": "@visactor/vchart",
"email": "test@example.com"
}
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,9 @@ export abstract class DataFilterBaseComponent<T extends IDataFilterComponentSpec

protected _filterMode!: IFilterMode;

protected _handleDataCollectionChangeBound!: any;
protected _currentDataCollection: any[] = [];

/*** start: public function ***/
/**
* 外部可以通过此方法强制改变datazoom的start和end,达到聚焦定位的效果
Expand Down Expand Up @@ -333,6 +336,7 @@ export abstract class DataFilterBaseComponent<T extends IDataFilterComponentSpec

/*** start: component lifecycle ***/
created() {
this._handleDataCollectionChangeBound = this._handleDataCollectionChange.bind(this);
super.created();
this._setAxisFromSpec();
this._setRegionsFromSpec();
Expand Down Expand Up @@ -535,14 +539,14 @@ export abstract class DataFilterBaseComponent<T extends IDataFilterComponentSpec
/*** end: set attributes & bind related axis and region ***/

/*** start: data change and reset view ***/
protected _initData() {
protected _collectDataInfo() {
const dataCollection: any[] = [];
const seriesCollection: any[] = [];
const stateFields: string[] = [];
const valueFields: string[] = [];
let isCategoryState: boolean;
if (this._relatedAxisComponent) {
const originalStateFields = {};
const originalStateFields: any = {};
eachSeries(
this._regions,
s => {
Expand All @@ -551,25 +555,25 @@ export abstract class DataFilterBaseComponent<T extends IDataFilterComponentSpec
s.coordinate === 'cartesian'
? (s as ICartesianSeries).getXAxisHelper()
: s.coordinate === 'polar'
? (s as IPolarSeries).angleAxisHelper
: null;
? (s as IPolarSeries).angleAxisHelper
: null;
const yAxisHelper =
s.coordinate === 'cartesian'
? (s as ICartesianSeries).getYAxisHelper()
: s.coordinate === 'polar'
? (s as IPolarSeries).radiusAxisHelper
: null;
? (s as IPolarSeries).radiusAxisHelper
: null;
if (!xAxisHelper || !yAxisHelper) {
return;
}
const stateAxisHelper =
xAxisHelper.getAxisId() === this._relatedAxisComponent.id
? xAxisHelper
: yAxisHelper.getAxisId() === this._relatedAxisComponent.id
? yAxisHelper
: this._isHorizontal
? xAxisHelper
: yAxisHelper;
? yAxisHelper
: this._isHorizontal
? xAxisHelper
: yAxisHelper;
const valueAxisHelper = stateAxisHelper === xAxisHelper ? yAxisHelper : xAxisHelper;
const isValidateValueAxis = isContinuous(valueAxisHelper.getScale(0).type);
const isValidateStateAxis = isContinuous(stateAxisHelper.getScale(0).type);
Expand Down Expand Up @@ -632,6 +636,11 @@ export abstract class DataFilterBaseComponent<T extends IDataFilterComponentSpec
}
);
}
return { dataCollection, seriesCollection, stateFields, valueFields, isCategoryState };
}

protected _initData() {
const { dataCollection, seriesCollection, stateFields, valueFields, isCategoryState } = this._collectDataInfo();
const { dataSet } = this._option;
registerDataSetInstanceParser(dataSet, 'dataview', dataViewParser);
registerDataSetInstanceTransform(dataSet, 'dataFilterComputeDomain', dataFilterComputeDomain);
Expand Down Expand Up @@ -659,7 +668,8 @@ export abstract class DataFilterBaseComponent<T extends IDataFilterComponentSpec
// todo 似乎没必要创建
this._data = new CompilableData(this._option, data);
data.reRunAllTransform();
dataSet.multipleDataViewAddListener(dataCollection, 'change', this._handleDataCollectionChange.bind(this));
this._currentDataCollection = dataCollection;
dataSet.multipleDataViewAddListener(dataCollection, 'change', this._handleDataCollectionChangeBound);
}
protected _addTransformToSeries() {
if (!this._relatedAxisComponent || this._filterMode !== 'axis') {
Expand Down Expand Up @@ -706,10 +716,59 @@ export abstract class DataFilterBaseComponent<T extends IDataFilterComponentSpec
);
}
}

// 数据更新流程
onDataUpdate(): void {
const { dataCollection, seriesCollection, stateFields, valueFields, isCategoryState } = this._collectDataInfo();

// 1. 重新注册事件
if (
this._currentDataCollection.length !== dataCollection.length ||
this._currentDataCollection.some((dv, i) => dv !== dataCollection[i])
) {
this._currentDataCollection.forEach(dv => {
dv?.target?.removeListener('change', this._handleDataCollectionChangeBound);
});
this._currentDataCollection = dataCollection;
const { dataSet } = this._option;
dataSet.multipleDataViewAddListener(this._currentDataCollection, 'change', this._handleDataCollectionChangeBound);
}

// 2. 执行数据更新
this._data.getDataView().transform(
{
type: 'dataFilterComputeDomain',
options: {
input: {
dataCollection: dataCollection,
seriesCollection,
stateFields,
valueFields,
isCategoryState
},
output: {
stateField: this._stateField,
valueField: this._valueField
}
}
},
false
);
this._data.getDataView().reRunAllTransform();

// 3. 重新计算domain
const domain = this._computeDomainOfStateScale(isContinuous(this._stateScale.type));
this._stateScale.domain(domain, false);

// 4. 重新计算start和end
// 如果 spec 中有配置 start/end,使用 spec 中的配置
// 否则保持当前的 start/end
if (isValid(this._spec.start) || isValid(this._spec.end)) {
this._setStateFromSpec();
}
this._handleChange(this._start, this._end, true);

// 5. 重新布局
// auto 模式下需要重新布局
if (this._spec.auto && !isEqual(this._domainCache, domain)) {
this._domainCache = domain;
Expand Down Expand Up @@ -747,13 +806,13 @@ export abstract class DataFilterBaseComponent<T extends IDataFilterComponentSpec
start = this._spec.start
? this._spec.start
: this._spec.startValue
? dataToStatePoint(this._spec.startValue, this._stateScale, this._isHorizontal)
: 0;
? dataToStatePoint(this._spec.startValue, this._stateScale, this._isHorizontal)
: 0;
end = this._spec.end
? this._spec.end
: this._spec.endValue
? dataToStatePoint(this._spec.endValue, this._stateScale, this._isHorizontal)
: 1;
? dataToStatePoint(this._spec.endValue, this._stateScale, this._isHorizontal)
: 1;
}
this._start = Math.max(0, Math.min(1, start));
this._end = Math.max(0, Math.min(1, end));
Expand Down
34 changes: 34 additions & 0 deletions specs/007-fix-datazoom-react/checklists/requirements.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Specification Quality Checklist: Fix DataZoom Value Update on Data Change in React

**Purpose**: Validate specification completeness and quality before proceeding to planning
**Created**: 2025-02-09
**Feature**: [Link to spec.md](../spec.md)

## Content Quality

- [x] No implementation details (languages, frameworks, APIs)
- [x] Focused on user value and business needs
- [x] Written for non-technical stakeholders
- [x] All mandatory sections completed

## Requirement Completeness

- [x] No [NEEDS CLARIFICATION] markers remain
- [x] Requirements are testable and unambiguous
- [x] Success criteria are measurable
- [x] Success criteria are technology-agnostic (no implementation details)
- [x] All acceptance scenarios are defined
- [x] Edge cases are identified
- [x] Scope is clearly bounded
- [x] Dependencies and assumptions identified

## Feature Readiness

- [x] All functional requirements have clear acceptance criteria
- [x] User scenarios cover primary flows
- [x] Feature meets measurable outcomes defined in Success Criteria
- [x] No implementation details leak into specification

## Notes

- Spec is ready for implementation.
69 changes: 69 additions & 0 deletions specs/007-fix-datazoom-react/plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Implementation Plan: Fix DataZoom Value Update on Data Change in React

**Branch**: `007-fix-datazoom-react` | **Date**: 2025-02-09 | **Spec**: [specs/007-fix-datazoom-react/spec.md](../spec.md)
**Input**: Feature specification from `specs/007-fix-datazoom-react/spec.md`

## Summary

The `DataZoom` component in ReactVChart currently fails to update when the chart data source changes (Issue #4185). The primary requirement is to ensure `DataZoom` detects data updates, unbinds from old DataViews, binds to new ones, and correctly re-renders its preview and applies any explicit range configurations (start/end) from the spec.

## Technical Context

**Language/Version**: TypeScript 4.x+ (Project uses TS)
**Primary Dependencies**: `@visactor/vchart` (Core logic), `@visactor/react-vchart` (React wrapper)
**Storage**: N/A (In-memory chart state)
**Testing**: Jest (Unit tests), Manual verification via demo
**Target Platform**: Web (React environment)
**Project Type**: Monorepo (Rush.js managed)
**Performance Goals**: Update should happen within a single render cycle; no noticeable lag (<16ms ideal for interaction, update <100ms).
**Constraints**: Must maintain backward compatibility; must not introduce memory leaks (listener cleanup).
**Scale/Scope**: Affects all VChart users using DataZoom in React with dynamic data.

## Constitution Check

*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*

- [x] **Quality First**: Fix addresses a functional bug and prevents memory leaks (listener cleanup).
- [x] **User Experience-Driven**: Ensures UI reflects data state accurately.
- [x] **Specification-Driven Development**: Follows SDD process (Spec -> Plan -> Tasks).
- [x] **Openness & Collaboration**: Code will be reviewed via PR.
- [x] **Monorepo/Rush Governance**: Changes will be in `packages/vchart` (core logic) and `packages/react-vchart` (if needed for prop handling, though core fix likely sufficient).
- [x] **TypeScript**: Strict typing required.
- [x] **Testing**: Unit tests required for new logic.

## Project Structure

### Documentation (this feature)

```text
specs/007-fix-datazoom-react/
├── plan.md # This file
├── research.md # Phase 0 output
├── data-model.md # Phase 1 output (N/A for this fix)
├── quickstart.md # Phase 1 output (N/A)
├── contracts/ # Phase 1 output (N/A)
└── tasks.md # Phase 2 output
```

### Source Code (repository root)

```text
packages/
├── vchart/
│ └── src/
│ └── component/
│ └── data-zoom/
│ └── data-filter-base-component.ts # Core logic modification target
└── react-vchart/
└── demo/
└── src/
└── BugReproduction.tsx # Verification demo
```

**Structure Decision**: Modification is primarily in `@visactor/vchart` core component logic to robustly handle data view changes.

## Complexity Tracking

| Violation | Why Needed | Simpler Alternative Rejected Because |
|-----------|------------|-------------------------------------|
| None | | |
Loading
Loading