Skip to content

Feat/convert value to position #221

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jul 19, 2023
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@visactor/vchart",
"comment": "feat: add `convertValueToPosition` api for vchart",
"type": "patch"
}
],
"packageName": "@visactor/vchart"
}
36 changes: 34 additions & 2 deletions packages/vchart/__tests__/unit/core/vchart.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -416,7 +416,7 @@ describe('VChart', () => {
});
});

describe('convertDatumToPosition', () => {
describe('convertDatumToPosition and convertValueToPosition', () => {
let canvasDom: HTMLCanvasElement;
let vchart: VChart;
beforeEach(() => {
Expand Down Expand Up @@ -534,7 +534,17 @@ describe('VChart', () => {
lineWidth: 1
}
}
}
},
axes: [
{
orient: 'bottom',
id: 'bottom'
},
{
orient: 'left',
id: 'left'
}
]
},
{
renderCanvas: canvasDom,
Expand All @@ -550,6 +560,26 @@ describe('VChart', () => {
}) as IPoint;
expect(point.x).toBe(mark.attribute.x);
expect(point.y).toBe(mark.attribute.y);

const point2 = vchart.convertDatumToPosition(
{
State: 'WY',
年龄段: '小于5岁',
人口数量: 25635
},
{
seriesIndex: 0
},
true
) as IPoint;
expect(point2.x).toBe(mark.attribute.x + vchart.getChart()?.getAllSeries()[0].getLayoutStartPoint().x);
expect(point2.y).toBe(mark.attribute.y + vchart.getChart()?.getAllSeries()[0].getLayoutStartPoint().y);

const value1 = vchart.convertValueToPosition('WY', { axisId: 'bottom' });
expect(value1).toBe(mark.attribute.x);

const value2 = vchart.convertValueToPosition(0, { axisId: 'left' });
expect(value2).toBe(370);
});

it('should convert correctly in funnel chart', () => {
Expand Down Expand Up @@ -617,6 +647,8 @@ describe('VChart', () => {

expect(point.x).toBe(centerX);
expect(point.y).toBe(centerY);

expect(vchart.convertValueToPosition(['Step2', 80], { seriesId: 'funnel' })).toEqual({ x: centerX, y: centerY });
});

it('should convert correctly in pie chart', () => {
Expand Down
36 changes: 35 additions & 1 deletion packages/vchart/src/core/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,29 @@ import type { Stage } from '@visactor/vrender';
export type DataLinkSeries = {
/**
* 关联的系列 id
* the binding series id
*/
seriesId?: StringOrNumber;
/**
* 关联的系列索引
* the binding series index
*/
seriesIndex?: number;
};

export type DataLinkAxis = {
/**
* 关联的轴 id,目前仅支持直角坐标轴
* the binding axis id
*/
axisId?: StringOrNumber;
/**
* 关联的轴索引,目前仅支持直角坐标轴
* the binding axis index
*/
axisIndex?: number;
};

export interface IVChart {
readonly id: number;

Expand Down Expand Up @@ -317,9 +332,28 @@ export interface IVChart {
* Convert the data to coordinate position
* @param datum the datum to convert
* @param dataLinkInfo the data link info, could be seriesId or seriesIndex, default is { seriesIndex: 0 }
* @param isRelativeToCanvas 是否相对画布坐标,默认为 false Whether relative to canvas coordinates, default is false
* @returns
*/
convertDatumToPosition: (datum: Datum, dataLinkInfo?: DataLinkSeries) => IPoint | null;
convertDatumToPosition: (datum: Datum, dataLinkInfo?: DataLinkSeries, isRelativeToCanvas?: boolean) => IPoint | null;

/**
* Convert the value to coordinate position
* @param value number | [number, number], the value to convert
* @param dataLinkInfo the data link info, could be seriesId,seriesIndex,axisId,axisIndex
* @param isRelativeToCanvas 是否相对画布坐标,默认为 false Whether relative to canvas coordinates, default is false
* returns
*/
convertValueToPosition: ((
value: StringOrNumber,
dataLinkInfo: DataLinkAxis,
isRelativeToCanvas?: boolean
) => number | null) &
((
value: [StringOrNumber, StringOrNumber],
dataLinkInfo: DataLinkSeries,
isRelativeToCanvas?: boolean
) => IPoint | null);
}

export interface IGlobalConfig {
Expand Down
95 changes: 88 additions & 7 deletions packages/vchart/src/core/vchart.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { series } from './../theme/buildin-theme/light/series/index';
import type { ISeries } from '../series/interface/series';
import { arrayParser } from '../data/parser/array';
import type { ILayoutConstructor, LayoutCallBack } from '../layout/interface';
Expand Down Expand Up @@ -26,7 +27,8 @@ import {
isTrueBrowser,
warn,
error,
specTransform
specTransform,
convertPoint
} from '../util';
import { Factory } from './factory';
import { Event } from '../event/event';
Expand Down Expand Up @@ -64,8 +66,9 @@ import { getCanvasDataURL, URLToImage } from '../util/image';
import { ChartEvent, DEFAULT_CHART_HEIGHT, DEFAULT_CHART_WIDTH } from '../constant';
// eslint-disable-next-line no-duplicate-imports
import { getContainerSize, isArray, isEmpty } from '@visactor/vutils';
import type { DataLinkSeries, IGlobalConfig, IVChart } from './interface';
import type { DataLinkAxis, DataLinkSeries, IGlobalConfig, IVChart } from './interface';
import { InstanceManager } from './instance-manager';
import type { IAxis } from '../component/axis';

export class VChart implements IVChart {
readonly id = createID();
Expand Down Expand Up @@ -1072,13 +1075,20 @@ export class VChart implements IVChart {
return this._chart?.setDimensionIndex(value, opt);
}

// TODO: 后续需要考虑滚动场景
/**
* Convert the data to coordinate position
* @param datum the datum to convert
* @param dataLinkInfo the data link info, could be seriesId or seriesIndex, default is { seriesIndex: 0 }
* Convert the data corresponding to the graph into coordinates
* 将图形对应的数据转换为坐标,该数据需要从传入图表的数据集中获取,如果数据不存在数据集中,可以使用 `convertValueToPosition` 方法
* @param datum 要转化的数据 the datum(from data source)to convert
* @param dataLinkInfo 数据的绑定信息,the data link info, could be seriesId or seriesIndex, default is { seriesIndex: 0 }
* @param isRelativeToCanvas 是否相对画布坐标 Whether relative to canvas coordinates
* @returns
*/
convertDatumToPosition(datum: Datum, dataLinkInfo: DataLinkSeries = {}): IPoint | null {
convertDatumToPosition(
datum: Datum,
dataLinkInfo: DataLinkSeries = {},
isRelativeToCanvas: boolean = false
): IPoint | null {
if (!this._chart) {
return null;
}
Expand All @@ -1100,11 +1110,82 @@ export class VChart implements IVChart {
.getViewData()
// eslint-disable-next-line eqeqeq
.latestData.find((viewDatum: Datum) => keys.every(k => viewDatum[k] == datum[k]));
const seriesLayoutStartPoint = series.getLayoutStartPoint();
let point: IPoint;
if (handledDatum) {
return series.dataToPosition(handledDatum);
point = series.dataToPosition(handledDatum);
} else {
point = series.dataToPosition(datum);
}
return convertPoint(point, seriesLayoutStartPoint, isRelativeToCanvas);
}

return null;
}

// TODO: 1. 后续需要考虑滚动场景 2. 极坐标场景支持
convertValueToPosition(
value: StringOrNumber,
dataLinkInfo: DataLinkAxis,
isRelativeToCanvas?: boolean
): number | null;
convertValueToPosition(
value: [StringOrNumber, StringOrNumber],
dataLinkInfo: DataLinkSeries,
isRelativeToCanvas?: boolean
): IPoint | null;
convertValueToPosition(
value: StringOrNumber | [StringOrNumber, StringOrNumber],
dataLinkInfo: DataLinkAxis | DataLinkSeries,
isRelativeToCanvas: boolean = false
): number | IPoint | null {
if (!this._chart || isNil(value) || isEmpty(dataLinkInfo)) {
return null;
}

if (!isArray(value)) {
// 如果单个值,则默认使用 axis 绑定信息
const { axisId, axisIndex } = dataLinkInfo as DataLinkAxis;
let axis;
if (isValid(axisId)) {
axis = this._chart.getComponentsByKey('axes').find(s => s.userId === axisId);
} else if (isValid(axisIndex)) {
axis = this._chart.getComponentsByKey('axes')?.[axisIndex];
}
if (!axis) {
warn('Please check whether the `axisId` or `axisIndex` is set!');
return null;
}

const pointValue = (axis as IAxis)?.valueToPosition(value);
if (isRelativeToCanvas) {
const axisLayoutStartPoint = axis.getLayoutStartPoint();
const axisOrient = (axis as IAxis).orient;
return (
pointValue +
(axisOrient === 'bottom' || axisOrient === 'top' ? axisLayoutStartPoint.x : axisLayoutStartPoint.y)
);
}

return pointValue;
}
const { seriesId, seriesIndex } = dataLinkInfo as DataLinkSeries;
let series;
if (isValid(seriesId)) {
series = this._chart.getSeriesInUserId(seriesId);
} else if (isValid(seriesIndex)) {
series = this._chart.getSeriesInIndex([seriesIndex])?.[0];
}

if (!series) {
warn('Please check whether the `seriesId` or `seriesIndex` is set!');
return null;
}

return convertPoint(
(series as ISeries).valueToPosition(value[0], value[1]),
series.getLayoutStartPoint(),
isRelativeToCanvas
);
}
}
5 changes: 4 additions & 1 deletion packages/vchart/src/series/base/base-series.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ import type {
ISeriesSpec,
IExtensionMarkSpec,
IExtensionGroupMarkSpec,
EnableMarkType
EnableMarkType,
StringOrNumber
} from '../../typings';
import { BaseModel } from '../../model/base-model';
// eslint-disable-next-line no-duplicate-imports
Expand Down Expand Up @@ -536,6 +537,8 @@ export abstract class BaseSeries<T extends ISeriesSpec> extends BaseModel implem
abstract dataToPositionX(data: Datum): number;
/** 数据到 y 坐标点的映射 */
abstract dataToPositionY(data: Datum): number;
/** 数据到坐标点的映射 */
abstract valueToPosition(value1: any, value2?: any): IPoint;
abstract initMark(): void;
abstract initMarkStyle(): void;

Expand Down
15 changes: 7 additions & 8 deletions packages/vchart/src/series/cartesian/cartesian.ts
Original file line number Diff line number Diff line change
Expand Up @@ -277,14 +277,13 @@ export abstract class CartesianSeries<T extends ICartesianSeriesSpec = ICartesia
this._markAttributeContext.valueToY = this.valueToPositionY.bind(this);
this._markAttributeContext.xBandwidth = (depth: number = 0) => this.getXAxisHelper().getBandwidth?.(depth) ?? 0;
this._markAttributeContext.yBandwidth = (depth: number = 0) => this.getYAxisHelper().getBandwidth?.(depth) ?? 0;
this._markAttributeContext.valueToPosition = (
valueX: StringOrNumber | StringOrNumber[],
valueY: StringOrNumber | StringOrNumber[]
) => {
return {
x: this.valueToPositionX(valueX),
y: this.valueToPositionY(valueY)
};
this._markAttributeContext.valueToPosition = this.valueToPosition.bind(this);
}

valueToPosition(xValue: StringOrNumber | StringOrNumber[], yValue: StringOrNumber | StringOrNumber[]) {
return {
x: this.valueToPositionX(xValue),
y: this.valueToPositionY(yValue)
};
}

Expand Down
7 changes: 7 additions & 0 deletions packages/vchart/src/series/geo/geo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,13 @@ export abstract class GeoSeries<T extends IGeoSeriesSpec = IGeoSeriesSpec> exten
return dataToLatitude(lonValue);
}

valueToPosition(lonValue: number, latValue: number): IPoint {
return {
x: this.dataToLongitude(lonValue),
y: this.dataToLatitude(latValue)
};
}

positionToData(p: IPoint) {
// TODO
}
Expand Down
11 changes: 10 additions & 1 deletion packages/vchart/src/series/interface/series.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import type { IAxisHelper } from '../../component/axis/cartesian/interface';
import type { IPolarAxisHelper } from '../../component/axis/polar/interface';
import type { ISeriesSeriesInfo, ISeriesStackData, ISeriesUpdateDataOption } from './common';
import type { ISeriesTooltipHelper } from './tooltip-helper';
import type { IInvalidType, Datum, DirectionType } from '../../typings';
import type { IInvalidType, Datum, DirectionType, StringOrNumber } from '../../typings';
import type { StateValueType } from '../../compile/mark';
import type { StatisticOperations } from '../../data/transforms/dimension-statistics';
import type { IGroupMark } from '../../mark/group';
Expand Down Expand Up @@ -161,6 +161,7 @@ export interface ISeries extends IModel, ILayoutItem {
dataToPositionX: (datum: Datum) => number | null;
dataToPositionY: (datum: Datum) => number | null;
dataToPositionZ?: (datum: Datum) => number | null;
valueToPosition: (value1: any, value2?: any) => IPoint;

getColorAttribute: () => { scale: IBaseScale; field: string };
getDefaultColorDomain: () => any[];
Expand Down Expand Up @@ -215,6 +216,8 @@ export interface ICartesianSeries extends ISeries {

dataToPositionX1: (datum: Datum) => number | null;
dataToPositionY1: (datum: Datum) => number | null;

valueToPosition: (value1: any, value2: any) => IPoint;
}

export interface IPolarSeries extends ISeries {
Expand Down Expand Up @@ -242,6 +245,8 @@ export interface IPolarSeries extends ISeries {
// 轴
radiusAxisHelper: IPolarAxisHelper;
angleAxisHelper: IPolarAxisHelper;

valueToPosition: (value1: any, value2: any) => IPoint;
}

export interface IGeoSeries extends ISeries {
Expand All @@ -262,6 +267,8 @@ export interface IGeoSeries extends ISeries {

getCoordinateHelper: () => IGeoCoordinateHelper;
setCoordinateHelper: (helper: IGeoCoordinateHelper) => void;

valueToPosition: (value1: any, value2: any) => IPoint;
}

// 收拢扇区标签形式依赖的 api
Expand All @@ -278,4 +285,6 @@ export interface IArcSeries extends IPolarSeries {
export interface IFunnelSeries extends ISeries {
getPoints: (datum: any) => IPoint[];
getCategoryField: () => string;

valueToPosition: (value: any) => IPoint;
}
Loading