Skip to content
Draft
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
94 changes: 94 additions & 0 deletions __tests__/unit/api/interaction.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,97 @@ describe('Clear EventEmitter', () => {
expect(emitter?.getEvents()['legend:filter']).toBeUndefined();
});
});

describe('BrushXFilter', () => {
it('should not change Y scale domain when filtering X axis.', async () => {
// @see https://github.com/antvis/G2/issues/7272
const chart = new Chart({
canvas: createNodeGCanvas(640, 480),
});

chart.options({
type: 'point',
data: [
{ x: 1, y: 10 },
{ x: 2, y: 50 },
{ x: 3, y: 30 },
{ x: 4, y: 80 },
{ x: 5, y: 20 },
{ x: 6, y: 60 },
{ x: 7, y: 40 },
{ x: 8, y: 90 },
{ x: 9, y: 15 },
{ x: 10, y: 70 },
],
encode: { x: 'x', y: 'y' },
interaction: { brushXFilter: true },
});

await chart.render();

const initialYDomain = chart.getScale().y.getOptions().domain;

// Emit brush:filter with an X selection to simulate brushXFilter.
const { emitter } = chart.getContext();
const xSelection = [3, 8];
emitter.emit('brush:filter', {
nativeEvent: false,
data: { selection: [xSelection, initialYDomain] },
});

// Wait for update to complete.
await new Promise((resolve) => setTimeout(resolve, 100));

const filteredYDomain = chart.getScale().y.getOptions().domain;

// Y scale domain should remain unchanged after brushXFilter.
expect(filteredYDomain).toEqual(initialYDomain);
});
});

describe('BrushYFilter', () => {
it('should not change X scale domain when filtering Y axis.', async () => {
// @see https://github.com/antvis/G2/issues/7272
const chart = new Chart({
canvas: createNodeGCanvas(640, 480),
});

chart.options({
type: 'point',
data: [
{ x: 1, y: 10 },
{ x: 2, y: 50 },
{ x: 3, y: 30 },
{ x: 4, y: 80 },
{ x: 5, y: 20 },
{ x: 6, y: 60 },
{ x: 7, y: 40 },
{ x: 8, y: 90 },
{ x: 9, y: 15 },
{ x: 10, y: 70 },
],
encode: { x: 'x', y: 'y' },
interaction: { brushYFilter: true },
});

await chart.render();

const initialXDomain = chart.getScale().x.getOptions().domain;

// Emit brush:filter with a Y selection to simulate brushYFilter.
const { emitter } = chart.getContext();
const ySelection = [20, 70];
emitter.emit('brush:filter', {
nativeEvent: false,
data: { selection: [initialXDomain, ySelection] },
});

// Wait for update to complete.
await new Promise((resolve) => setTimeout(resolve, 100));

const filteredXDomain = chart.getScale().x.getOptions().domain;

// X scale domain should remain unchanged after brushYFilter.
expect(filteredXDomain).toEqual(initialXDomain);
});
});
32 changes: 29 additions & 3 deletions src/interaction/brushFilter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,13 @@ export function brushFilter(
};
}

export function BrushFilter({ hideX = true, hideY = true, ...rest }) {
export function BrushFilter({
hideX = true,
hideY = true,
filterX = true,
filterY = true,
...rest
}) {
return (target, viewInstances, emitter) => {
const { container, view, options: viewOptions, update, setState } = target;
const plotArea = selectPlotArea(container);
Expand Down Expand Up @@ -112,6 +118,18 @@ export function BrushFilter({ hideX = true, hideY = true, ...rest }) {
// Update the domain of x and y scale to filter data.
const [domainX, domainY] = selection;

// Capture the current domains of the non-filtered axes from the view
// so they can be explicitly preserved (avoiding re-inference changes).
const { scale: currentScale } = newView;
const preservedDomainX =
!filterX && currentScale.x
? currentScale.x.getOptions().domain
: null;
const preservedDomainY =
!filterY && currentScale.y
? currentScale.y.getOptions().domain
: null;

setState('brushFilter', (options) => {
const { marks } = options;
const newMarks = marks.map((mark) =>
Expand All @@ -126,9 +144,17 @@ export function BrushFilter({ hideX = true, hideY = true, ...rest }) {
mark,
{
// Set nice to false to avoid modify domain.
// For filtered axes: use the brush selection domain.
// For non-filtered axes: explicitly preserve the current domain.
scale: {
x: { domain: domainX, nice: false },
y: { domain: domainY, nice: false },
...(filterX && { x: { domain: domainX, nice: false } }),
...(filterY && { y: { domain: domainY, nice: false } }),
...(preservedDomainX && {
x: { domain: preservedDomainX, nice: false },
}),
...(preservedDomainY && {
y: { domain: preservedDomainY, nice: false },
}),
},
},
),
Expand Down
3 changes: 3 additions & 0 deletions src/interaction/brushXFilter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import { brushXRegion } from './brushXHighlight';
export function BrushXFilter(options) {
return BrushFilter({
hideX: true,
hideY: false,
filterX: true,
filterY: false,
...options,
brushRegion: brushXRegion,
});
Expand Down
3 changes: 3 additions & 0 deletions src/interaction/brushYFilter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ import { brushYRegion } from './brushYHighlight';

export function BrushYFilter(options) {
return BrushFilter({
hideX: false,
hideY: true,
filterX: false,
filterY: true,
...options,
brushRegion: brushYRegion,
});
Expand Down
Loading