Skip to content

Commit

Permalink
feat(interaction): support continuous legend filter (#5356)
Browse files Browse the repository at this point in the history
  • Loading branch information
pearmini authored Aug 2, 2023
1 parent 64137e1 commit 5f866cb
Show file tree
Hide file tree
Showing 46 changed files with 228 additions and 80 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified __tests__/integration/snapshots/static/aaplLineMissingDataTrial.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified __tests__/integration/snapshots/static/barleyLineTrail.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified __tests__/integration/snapshots/static/bodyPointScatterPlot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified __tests__/integration/snapshots/static/cars3LineRadar.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified __tests__/integration/snapshots/static/commitsPointGrouped.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified __tests__/integration/snapshots/static/disastersPointBubble.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified __tests__/integration/snapshots/static/haleChoroplethWorld.png
Binary file modified __tests__/integration/snapshots/static/moviesPointBin.png
Binary file modified __tests__/integration/snapshots/static/moviesRectBin.png
Binary file modified __tests__/integration/snapshots/static/moviesRectBinOpacity.png
Binary file modified __tests__/integration/snapshots/static/settleWeatherCellLineXY.png
Binary file modified __tests__/integration/snapshots/static/stocksLineSeriesGradient.png
Binary file modified __tests__/integration/snapshots/static/unemploymentChoropleth.png
54 changes: 54 additions & 0 deletions __tests__/plots/interaction/commits-point-legend-filter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { G2Spec } from '../../../src';
import { CONTINUOUS_LEGEND_CLASS_NAME } from '../../../src/interaction/legendFilter';

export function commitsPointLegendFilter(): G2Spec {
return {
type: 'point',
height: 300,
inset: 10,
padding: 'auto',
frame: true,
data: {
type: 'fetch',
value: 'data/commits.csv',
},
encode: {
x: (d) => d.time.getUTCHours(),
y: (d) => d.time.getUTCDay(),
size: 'count',
shape: 'point',
color: 'count',
},
transform: [
{ type: 'group', size: 'sum', color: 'sum' },
{ type: 'sortY' },
],
scale: {
y: { type: 'point' },
x: { tickCount: 24 },
color: { palette: 'rdBu' },
},
axis: {
x: { title: 'time (hours)' },
y: { title: 'time (day)', grid: true },
},
};
}

export function dispatchValueChange(legend, values) {
legend.setSelection(...values);
}

commitsPointLegendFilter.steps = ({ canvas }) => {
const { document } = canvas;
const [legend] = document.getElementsByClassName(
CONTINUOUS_LEGEND_CLASS_NAME,
);
return [
{
changeState: () => {
dispatchValueChange(legend, [20, 60]);
},
},
];
};
2 changes: 2 additions & 0 deletions __tests__/plots/interaction/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,5 @@ export { weatherLineLegendMark } from './weather-line-legend-mark';
export { countriesAnnotationSliderFilter } from './countries-annotation-slider-filter';
export { unemploymentAreaLegendFilterPages } from './unemployment-area-legend-filter-pages';
export { mockAreaSliderFilterLabel } from './mock-area-slider-filter-label';
export { commitsPointLegendFilter } from './commits-point-legend-filter';
export { settleWeatherLegendFilter } from './seattle-weather-legend-filter';
45 changes: 45 additions & 0 deletions __tests__/plots/interaction/seattle-weather-legend-filter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { G2Spec } from '../../../src';
import { CONTINUOUS_LEGEND_CLASS_NAME } from '../../../src/interaction/legendFilter';
import { dispatchValueChange } from './commits-point-legend-filter';

export function settleWeatherLegendFilter(): G2Spec {
return {
type: 'cell',
height: 330,
data: {
type: 'fetch',
value: 'data/seattle-weather.csv',
},
encode: {
x: (d) => new Date(d.date).getUTCDate(),
y: (d) => new Date(d.date).getUTCMonth(),
color: 'temp_max',
},
transform: [{ type: 'group', color: 'max' }],
scale: {
x: { compare: (a, b) => +a - +b },
y: { compare: (a, b) => +a - +b },
},
legend: { color: { layout: { justifyContent: 'flex-start' } } },
style: { inset: 0.5 },
};
}

settleWeatherLegendFilter.steps = ({ canvas }) => {
const { document } = canvas;
const [legend] = document.getElementsByClassName(
CONTINUOUS_LEGEND_CLASS_NAME,
);
return [
{
changeState: () => {
dispatchValueChange(legend, [10, 30]);
},
},
{
changeState: () => {
dispatchValueChange(legend, [5, 36.4]);
},
},
];
};
6 changes: 0 additions & 6 deletions __tests__/plots/static/commits-point-grouped.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,5 @@ export function commitsPointGrouped(): G2Spec {
x: { title: 'time (hours)' },
y: { title: 'time (day)', grid: true },
},
// viewStyle: {
// viewFill: '#4e79a7',
// plotFill: '#f28e2c',
// mainFill: '#e15759',
// contentFill: '#76b7b2',
// },
};
}
19 changes: 5 additions & 14 deletions __tests__/plots/static/seattle-weather-cell-grouped.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,17 @@ export function settleWeatherCellGrouped(): G2Spec {
type: 'fetch',
value: 'data/seattle-weather.csv',
},
transform: [{ type: 'group', color: 'max' }],
encode: {
x: (d) => new Date(d.date).getUTCDate(),
y: (d) => new Date(d.date).getUTCMonth(),
color: 'temp_max',
},
legend: {
color: {
layout: {
justifyContent: 'flex-start',
},
},
},
style: {
inset: 0.5,
},
transform: [{ type: 'group', color: 'max' }],
scale: {
color: {
palette: 'gnBu',
},
x: { compare: (a, b) => +a - +b },
y: { compare: (a, b) => +a - +b },
},
legend: { color: { layout: { justifyContent: 'flex-start' } } },
style: { inset: 0.5 },
};
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
"@antv/g": "^5.18.6",
"@antv/g-canvas": "^1.11.5",
"@antv/g-plugin-dragndrop": "^1.8.3",
"@antv/gui": "0.5.0-alpha.26",
"@antv/gui": "0.5.0-alpha.28",
"@antv/path-util": "^3.0.1",
"@antv/scale": "^0.4.7",
"@antv/util": "^3.3.2",
Expand Down
14 changes: 9 additions & 5 deletions src/component/legendContinuous.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { DisplayObject, parseColor } from '@antv/g';
import { Continuous } from '@antv/gui';
import { Quantile, Quantize, Threshold } from '@antv/scale';
import { Constant, Quantile, Quantize, Threshold } from '@antv/scale';
import { format } from 'd3-format';
import type {
FlexLayout,
Expand Down Expand Up @@ -43,12 +43,9 @@ type Config = {
color: string[];
data: any[];
labelFilter?: (datum: any, index: number) => boolean;
domain?: [number, number];
};

function calculateFinalSize(size: number, defaultSize: number): number {
return Math.min(size, defaultSize);
}

function updateShapeDimensions(
shape: Shape,
finalSize: number,
Expand Down Expand Up @@ -154,8 +151,14 @@ function getLinearConfig(

const scale = colorScale || createColorScale(definedScale, defaultColor);
const [min, max] = rangeOf(scale);
const [domainMin, domainMax] = rangeOf(
[colorScale, sizeScale, opacityScale]
.filter((d) => d !== undefined)
.find((d) => !(d instanceof Constant)),
);
return {
...shape,
domain: [domainMin, domainMax],
data: scale.getTicks().map((value) => ({ value })),
color: new Array(length).fill(0).map((d, i) => {
const value = ((max - min) / (length - 1)) * i + min;
Expand Down Expand Up @@ -269,6 +272,7 @@ export const LegendContinuous: GCC<LegendContinuousOptions> = (options) => {

layoutWrapper.appendChild(
new Continuous({
className: 'legend-continuous',
style: finalStyle,
}),
);
Expand Down
145 changes: 96 additions & 49 deletions src/interaction/legendFilter.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { DisplayObject } from '@antv/g';
import { deepMix } from '@antv/util';
import { deepMix, throttle } from '@antv/util';
import { subObject } from '../utils/helper';
import { useState, setCursor, restoreCursor } from './utils';

export const CATEGORY_LEGEND_CLASS_NAME = 'legend-category';

export const CONTINUOUS_LEGEND_CLASS_NAME = 'legend-continuous';

export const LEGEND_ITEMS_CLASS_NAME = 'items-item';

export const LEGEND_MAKER_CLASS_NAME = 'legend-category-item-marker';
Expand All @@ -27,6 +29,10 @@ export function legendsOf(root) {
return root.getElementsByClassName(CATEGORY_LEGEND_CLASS_NAME);
}

export function legendsContinuousOf(root) {
return root.getElementsByClassName(CONTINUOUS_LEGEND_CLASS_NAME);
}

export function dataOf(root) {
// legend -> layout -> container
let parent = root.parentNode;
Expand All @@ -44,7 +50,7 @@ export function attributesOf(root) {
return child.attributes;
}

function legendFilter(
function legendFilterOrdinal(
root: DisplayObject,
{
legends, // given the root of chart returns legends to be manipulated
Expand Down Expand Up @@ -175,61 +181,102 @@ function legendFilter(
};
}

function legendFilterContinuous(_, { legend, filter, emitter, channel }) {
const onValueChange = ({ detail: { value } }) => {
filter(value);
emitter.emit({
nativeEvent: true,
data: {
channel,
values: value,
},
});
};
legend.addEventListener('valuechange', onValueChange);
return () => {
legend.removeEventListener('valuechange', onValueChange);
};
}

export function LegendFilter() {
return (context, _, emitter) => {
const { container, view, options: viewOptions, update } = context;

const legends = legendsOf(container);

const filter = async (channel, value) => {
const { scale } = view;
const { [channel]: scaleOrdinal } = scale;
const { marks } = viewOptions;
// Add filter transform for every marks,
// which will skip for mark without color channel.
const newMarks = marks.map((mark) => {
if (mark.type === 'legends') return mark;
const { transform = [] } = mark;
const newTransform = [
{ type: 'filter', [channel]: value },
...transform,
];
return deepMix({}, mark, {
transform: newTransform,
//Set domain of scale to preserve legends.
scale: {
[channel]: {
domain: scaleOrdinal.getOptions().domain,
},
},
legend: {
[channel]: { preserve: true },
},
const legends = [
...legendsOf(container),
...legendsContinuousOf(container),
];

const filter = throttle(
async (channel, value, ordinal: boolean, channels) => {
const { marks } = viewOptions;
// Add filter transform for every marks,
// which will skip for mark without color channel.
const newMarks = marks.map((mark) => {
if (mark.type === 'legends') return mark;

// Inset after aggregate transform, such as group, and bin.
const { transform = [] } = mark;
const index = transform.findIndex(
({ type }) => type.startsWith('group') || type.startsWith('bin'),
);
const newTransform = [...transform];
newTransform.splice(index + 1, 0, {
type: 'filter',
[channel]: { value, ordinal },
});

// Set domain of scale to preserve encoding.
const newScale = Object.fromEntries(
channels.map((channel) => [
channel,
{ domain: view.scale[channel].getOptions().domain },
]),
);

return deepMix({}, mark, {
transform: newTransform,
scale: newScale,
...(!ordinal && { animate: false }),
legend: { [channel]: { preserve: true } },
});
});
});
const newOptions = {
...viewOptions,
marks: newMarks,
};
return update(newOptions);
};
const newOptions = {
...viewOptions,
marks: newMarks,
};
await update(newOptions);
},
50,
{ trailing: true },
);

const removes = legends.map((legend) => {
const { name: channel, domain } = dataOf(legend).scales[0];
return legendFilter(container, {
legends: itemsOf,
marker: markerOf,
label: labelOf,
datum: (d) => {
const { __data__: datum } = d;
const { index } = datum;
return domain[index];
},
filter: (value) => filter(channel, value),
state: legend.attributes.state,
channel,
emitter,
});
const channels = dataOf(legend).scales.map((d) => d.name);
if (legend.className === CATEGORY_LEGEND_CLASS_NAME) {
return legendFilterOrdinal(container, {
legends: itemsOf,
marker: markerOf,
label: labelOf,
datum: (d) => {
const { __data__: datum } = d;
const { index } = datum;
return domain[index];
},
filter: (value) => filter(channel, value, true, channels),
state: legend.attributes.state,
channel,
emitter,
});
} else {
return legendFilterContinuous(container, {
legend,
filter: (value) => filter(channel, value, false, channels),
emitter,
channel,
});
}
});
return () => {
removes.forEach((remove) => remove());
Expand Down
Loading

0 comments on commit 5f866cb

Please sign in to comment.