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
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"changes": [
{
"comment": "feat: support dimension hover state for mark point target symbol",
"type": "minor",
"packageName": "@visactor/vchart"
}
],
"packageName": "@visactor/vchart",
"email": "lixuef1313@163.com"
}
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ export type IMarkerSymbol = IMarkerRef & {
} & Partial<IMarkerState<Omit<ISymbolMarkSpec, 'visible'>>>;
export type MarkerStyleCallback<T> = (markerData: DataView, context: IMarkerAttributeContext) => T;
export type MarkerStateCallback<T> = (markerData: DataView, context: IMarkerAttributeContext) => T;
export type MarkerStateValue = 'hover' | 'hover_reverse' | 'selected' | 'selected_reverse';
export type MarkerStateValue = 'hover' | 'hover_reverse' | 'dimension_hover' | 'dimension_hover_reverse' | 'selected' | 'selected_reverse';
export type IMarkerState<T> = {
style?: T | MarkerStyleCallback<T>;
state?: Record<MarkerStateValue, T | MarkerStateCallback<T>>;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
import type { IMarkPoint, IMarkPointSpec } from './interface';
import { MarkPoint as MarkPointComponent } from '@visactor/vrender-components';
import { BaseMarker } from '../base-marker';
import type { DimensionEventParams } from '../../../event/events/dimension/interface';
import type { IGroup } from '@visactor/vrender-core';
export declare abstract class BaseMarkPoint extends BaseMarker<IMarkPointSpec> implements IMarkPoint {
static specKey: string;
specKey: string;
layoutZIndex: number;
protected _markerComponent: MarkPointComponent;
protected abstract _computePointsAttr(): any;
protected initEvent(): void;
protected _handleDimensionHover: (params: DimensionEventParams) => void;
private _getTargetSymbolGraphic;
private _isDimensionHoverTarget;
static _getMarkerCoordinateType(markerSpec: any): string;
protected _createMarkerComponent(): IGroup;
protected _markerLayout(): void;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import type { ISpec } from '../../../../src';

export const targetSymbolHoverSpec: ISpec = {
type: 'line',
title: {
text: 'MarkPoint targetSymbol 的 dimension_hover 状态'
},
data: [
{
id: 'target-symbol-hover-data',
values: [
{ month: '1月', value: 28 },
{ month: '2月', value: 46 },
{ month: '3月', value: 66 },
{ month: '4月', value: 51 },
{ month: '5月', value: 74 },
{ month: '6月', value: 62 }
]
}
],
xField: 'month',
yField: 'value',
axes: [{ orient: 'bottom' }, { orient: 'left', grid: { visible: true } }],
tooltip: {
visible: false
},
point: {
style: {
size: 8
}
},
markPoint: [
{
coordinate: { month: '3月', value: 66 },
itemContent: {
type: 'text',
offsetX: 42,
offsetY: -72,
autoRotate: false,
style: {
text: '悬停到对应 x 轴区域:targetSymbol 变透明;移出后恢复',
fill: '#d94801',
fontSize: 14
}
},
itemLine: {
startSymbol: { visible: false },
line: {
style: {
stroke: '#d94801',
lineWidth: 2
}
}
},
targetSymbol: {
visible: true,
size: 32,
style: {
symbolType: 'circle',
fill: '#fff7ed',
stroke: '#ea580c',
lineWidth: 4
},
state: {
dimension_hover: {
opacity: 0.001
}
}
}
},
{
coordinate: { month: '5月', value: 74 },
itemContent: {
type: 'text',
offsetX: -42,
offsetY: -72,
autoRotate: false,
style: {
text: '仅命中当前 x 值的 targetSymbol',
fill: '#0f766e',
fontSize: 14
}
},
itemLine: {
startSymbol: { visible: false },
line: {
style: {
stroke: '#0f766e',
lineWidth: 2
}
}
},
targetSymbol: {
visible: true,
size: 32,
style: {
symbolType: 'circle',
fill: '#f0fdfa',
stroke: '#0f766e',
lineWidth: 4
},
state: {
dimension_hover: {
opacity: 0.001
}
}
}
}
]
};
Original file line number Diff line number Diff line change
Expand Up @@ -100,4 +100,64 @@ describe('MarkPoint Style', () => {
expect(itemContent.style.textConfig).toEqual(richText);
expect(itemContent.style.text).toBeUndefined();
});

it('should apply targetSymbol dimension_hover state for the matching x value', () => {
const spec = {
coordinate: {
month: '3月',
value: 66
},
itemContent: {
type: 'text',
text: {
text: 'Hello'
}
},
itemLine: {},
targetSymbol: {
visible: true,
state: {
dimension_hover: {
opacity: 0.001
}
}
}
};
const ctx: any = {
getChart: () => ({
getTheme: () => ({})
})
};

const markPoint = new TestMarkPoint(spec as any, ctx);
const targetSymbol = {
addState: jest.fn(),
removeState: jest.fn()
};
(markPoint as any)._markerComponent = {
find: () => targetSymbol
};
(markPoint as any)._markerData = {
latestData: [
{
x: ['3月'],
y: [66]
}
]
};

(markPoint as any)._handleDimensionHover({
action: 'enter',
dimensionInfo: [{ value: '3月', data: [] }]
});

expect(targetSymbol.addState).toHaveBeenCalledWith('dimension_hover', true);

(markPoint as any)._handleDimensionHover({
action: 'leave',
dimensionInfo: [{ value: '3月', data: [] }]
});

expect(targetSymbol.removeState).toHaveBeenCalledWith('dimension_hover');
});
});
8 changes: 7 additions & 1 deletion packages/vchart/src/component/marker/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,13 @@ export type MarkerStateCallback<T> = (
*/
context: IMarkerAttributeContext
) => T;
export type MarkerStateValue = 'hover' | 'hover_reverse' | 'selected' | 'selected_reverse';
export type MarkerStateValue =
| 'hover'
| 'hover_reverse'
| 'dimension_hover'
| 'dimension_hover_reverse'
| 'selected'
| 'selected_reverse';
export type IMarkerState<T> = {
/** 默认样式设置 */
style?: T | MarkerStyleCallback<T>;
Expand Down
60 changes: 58 additions & 2 deletions packages/vchart/src/component/marker/mark-point/base-mark-point.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@ import {
import type { MarkPointAttrs } from '@visactor/vrender-components';
// eslint-disable-next-line no-duplicate-imports
import { MarkPoint as MarkPointComponent } from '@visactor/vrender-components';
import { isValid, merge } from '@visactor/vutils';
import { array, isValid, merge } from '@visactor/vutils';
import { transformToGraphic } from '../../../util/style';
import { BaseMarker } from '../base-marker';
import { LayoutZIndex } from '../../../constant/layout';
import type { IGroup } from '@visactor/vrender-core';
import { STATE_VALUE_ENUM } from '../../../compile/mark/interface';
import { DimensionEventEnum, type DimensionEventParams } from '../../../event/events/dimension/interface';
import type { IGraphic, IGroup } from '@visactor/vrender-core';
import type { IMarkerLabelSpec, IMarkerLabelWithoutRefSpec } from '../interface';

const isRichTextStyle = (style: any) => style?.type === 'rich';
Expand Down Expand Up @@ -44,6 +46,60 @@ export abstract class BaseMarkPoint extends BaseMarker<IMarkPointSpec> implement

protected abstract _computePointsAttr(): any;

protected initEvent() {
super.initEvent();

const targetSymbolState = this._spec.targetSymbol?.state;
if (
this.coordinateType === 'cartesian' &&
(targetSymbolState?.dimension_hover || targetSymbolState?.dimension_hover_reverse)
) {
this.event.on(DimensionEventEnum.dimensionHover, this._handleDimensionHover);
}
}

protected _handleDimensionHover = (params: DimensionEventParams) => {
const targetSymbol = this._getTargetSymbolGraphic();
if (!targetSymbol) {
return;
}

switch (params.action) {
case 'enter': {
targetSymbol.removeState(STATE_VALUE_ENUM.STATE_DIMENSION_HOVER);
targetSymbol.removeState(STATE_VALUE_ENUM.STATE_DIMENSION_HOVER_REVERSE);

if (this._isDimensionHoverTarget(params)) {
targetSymbol.addState(STATE_VALUE_ENUM.STATE_DIMENSION_HOVER, true);
} else if (this._spec.targetSymbol?.state?.dimension_hover_reverse) {
targetSymbol.addState(STATE_VALUE_ENUM.STATE_DIMENSION_HOVER_REVERSE, true);
}
break;
}
case 'leave':
targetSymbol.removeState(STATE_VALUE_ENUM.STATE_DIMENSION_HOVER);
targetSymbol.removeState(STATE_VALUE_ENUM.STATE_DIMENSION_HOVER_REVERSE);
break;
default:
break;
}
};

private _getTargetSymbolGraphic() {
return this._markerComponent?.find(
node => node.name === 'mark-point-targetItem',
true
) as unknown as IGraphic | null;
}

private _isDimensionHoverTarget(params: DimensionEventParams) {
const latestData = this._markerData?.latestData;
const markerData = latestData?.[0]?.latestData ?? latestData;
const markerXValues = array(markerData?.[0]?.x).filter(isValid);

return markerXValues.some(value => params.dimensionInfo.some(dimension => dimension.value === value));
}

static _getMarkerCoordinateType(markerSpec: any): string {
const { doPolarProcess, doGeoProcess } = getMarkPointProcessInfo(markerSpec);
if (markerSpec.coordinateType === 'polar' || doPolarProcess) {
Expand Down
Loading