From 6e6dc6fa4c06e0986bbd366c4cbcfdf618e03ab4 Mon Sep 17 00:00:00 2001 From: heiyexing <496845051@qq.com> Date: Sat, 1 Jul 2023 11:45:46 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=8C=E5=96=84=20FlowLayer=20?= =?UTF-8?q?=E7=AE=AD=E5=A4=B4=E3=80=81=E7=BA=BF=E9=97=B4=E9=9A=94=E7=AD=89?= =?UTF-8?q?=E6=96=B0=E7=89=B9=E6=80=A7=20(#301)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 完善 FlowLayer 箭头、线间隔等新特性 * test: 修复 FlowLayer 单测报错问题 * test: 修复 FlowLayer 中线图层异步更新导致的单测失败 * docs: 修复 LineLayer shape 类型中 halfLine => flowline * refactor: 类型命名更正 StyleAttr => OptionStyleAttribute --------- Co-authored-by: yanxiong --- .../composite-layers/flow-layer/index.test.ts | 13 ++-- .../composite-layers/flow-layer/constants.ts | 24 ++++++++ .../flow-layer/data/build-index.ts | 1 + .../src/composite-layers/flow-layer/index.ts | 60 +++++++++++++------ .../flow-layer/types/index.ts | 18 +++++- .../flow-layer/utils/style.ts | 34 +++++++++-- .../src/core-layers/line-layer/types.ts | 16 +++-- .../src/core-layers/point-layer/types.ts | 6 +- packages/composite-layers/src/types/attr.ts | 13 +++- .../api/composite-layers/flow-layer.zh.md | 40 ++++++++++++- .../common/base-layers/line-layer/shape.zh.md | 2 +- .../composite/trafficFlow/demo/base.js | 6 ++ 12 files changed, 189 insertions(+), 44 deletions(-) diff --git a/packages/composite-layers/__tests__/unit/composite-layers/flow-layer/index.test.ts b/packages/composite-layers/__tests__/unit/composite-layers/flow-layer/index.test.ts index edbc7c4c9..7b4f0fa2d 100644 --- a/packages/composite-layers/__tests__/unit/composite-layers/flow-layer/index.test.ts +++ b/packages/composite-layers/__tests__/unit/composite-layers/flow-layer/index.test.ts @@ -73,8 +73,8 @@ describe('flow layer', () => { const dataProvider = new DataProvider(); it('layer', () => { - expect(layer.circleLayer.type).toBe('pointLayer'); - expect(layer.lineLayer.type).toBe('lineLayer'); + expect(layer.circleLayer?.type).toBe('pointLayer'); + expect(layer.lineLayer?.type).toBe('lineLayer'); }); it('data', () => { @@ -84,10 +84,9 @@ describe('flow layer', () => { }); it('style', () => { - expect(layer.circleLayer.options['style'].opacity).toBe(0.5); - expect(layer.circleLayer.options['style'].strokeWidth).toBe(2); - expect(layer.circleLayer.options['style'].stroke).toBe('#f00'); - - expect(layer.lineLayer.options['style'].opacity).toBe(0.7); + layer.update({}); + expect(layer.circleLayer?.options['style'].opacity).toBe(0.5); + expect(layer.circleLayer?.options['style'].strokeWidth).toBe(2); + expect(layer.circleLayer?.options['style'].stroke).toBe('#f00'); }); }); diff --git a/packages/composite-layers/src/composite-layers/flow-layer/constants.ts b/packages/composite-layers/src/composite-layers/flow-layer/constants.ts index 3e5e19aae..df7886765 100644 --- a/packages/composite-layers/src/composite-layers/flow-layer/constants.ts +++ b/packages/composite-layers/src/composite-layers/flow-layer/constants.ts @@ -1,5 +1,27 @@ +import { LineLayerOptions } from '../../core-layers/line-layer/types'; +import { PointLayerOptions } from '../../core-layers/point-layer/types'; import { FlowLayerOptions } from './types'; +export const EMPTY_CIRCLE_LAYER_SOURCE: PointLayerOptions['source'] = { + data: [], + parser: { + type: 'json', + x: 'lng', + y: 'lat', + }, +}; + +export const EMPTY_LINE_LAYER_SOURCE: LineLayerOptions['source'] = { + data: [], + parser: { + type: 'json', + x: 'fromLng', + y: 'fromLat', + x1: 'toLng', + y1: 'toLat', + }, +}; + export const DEFAULT_OPTIONS: FlowLayerOptions = { source: { data: [], @@ -33,6 +55,8 @@ export const DEFAULT_OPTIONS: FlowLayerOptions = { field: 'weight', value: [1, 16], }, + lineStroke: '#000', + lineStrokeWidth: 1, fadeOpacityEnabled: true, fadeOpacityAmount: 0, }; diff --git a/packages/composite-layers/src/composite-layers/flow-layer/data/build-index.ts b/packages/composite-layers/src/composite-layers/flow-layer/data/build-index.ts index da7871c9f..eae6635b1 100644 --- a/packages/composite-layers/src/composite-layers/flow-layer/data/build-index.ts +++ b/packages/composite-layers/src/composite-layers/flow-layer/data/build-index.ts @@ -100,6 +100,7 @@ export function buildIndex(clusterLevels: ClusterLevel[]) { return { zoomList, + clusterIdMap, getAppropriateLevel, getMapLocations, getLocationIdsFromCluster, diff --git a/packages/composite-layers/src/composite-layers/flow-layer/index.ts b/packages/composite-layers/src/composite-layers/flow-layer/index.ts index ae59dcd8f..21e248d10 100644 --- a/packages/composite-layers/src/composite-layers/flow-layer/index.ts +++ b/packages/composite-layers/src/composite-layers/flow-layer/index.ts @@ -6,10 +6,10 @@ import { PointLayerOptions } from '../../core-layers/point-layer/types'; import { CompositeLayer } from '../../core/composite-layer'; import { OriginMouseLayerEventList } from '../../core/constants'; import { ICoreLayer, Scene } from '../../types'; -import { DEFAULT_OPTIONS } from './constants'; +import { DEFAULT_OPTIONS, EMPTY_CIRCLE_LAYER_SOURCE, EMPTY_LINE_LAYER_SOURCE } from './constants'; import { DataProvider } from './data'; import { FlowDataProviderState, FlowLayerOptions, MapStatus } from './types'; -import { getColorAttribute, getOpacityColorAttribute, getSizeAttribute } from './utils'; +import { getColorAttribute, getLineOffsetsAttribute, getOpacityColorAttribute, getSizeAttribute } from './utils'; export class FlowLayer extends CompositeLayer { /** @@ -32,15 +32,15 @@ export class FlowLayer extends CompositeLayer { public dataProviderState!: FlowDataProviderState; protected get layer() { - return this.lineLayer; + return this.lineLayer!; } public get circleLayer() { - return this.subLayers.getLayer('circleLayer')!; + return this.subLayers?.getLayer('circleLayer'); } public get lineLayer() { - return this.subLayers.getLayer('lineLayer')!; + return this.subLayers?.getLayer('lineLayer'); } /** @@ -52,15 +52,15 @@ export class FlowLayer extends CompositeLayer { protected createSubLayers(): ICoreLayer[] { const circleLayer = new PointLayer({ - ...this.getCircleLayerOptions(), id: 'circleLayer', name: 'circleLayer', + source: EMPTY_CIRCLE_LAYER_SOURCE, }); const lineLayer = new LineLayer({ - ...this.getLineLayerOptions(), id: 'lineLayer', name: 'lineLayer', + source: EMPTY_LINE_LAYER_SOURCE, }); OriginMouseLayerEventList.forEach((eventName) => { @@ -87,8 +87,11 @@ export class FlowLayer extends CompositeLayer { protected updateSubLayers() { this.updateClusterState(); - this.circleLayer.update(this.getCircleLayerOptions()); - this.lineLayer.update(this.getLineLayerOptions()); + this.circleLayer?.update(this.getCircleLayerOptions()); + // 保证 lineLayer 获取到的 scale 方法是最新的 + requestAnimationFrame(() => { + this.lineLayer?.update(this.getLineLayerOptions()); + }); } protected onMapChange = debounce( @@ -165,7 +168,20 @@ export class FlowLayer extends CompositeLayer { } protected getLineLayerOptions(): LineLayerOptions { - const { minZoom, maxZoom, zIndex, visible, blend, pickingBuffer, lineOpacity: opacity } = this.options; + const { + minZoom, + maxZoom, + zIndex, + visible, + blend, + pickingBuffer, + lineOpacity, + lineWidth, + lineColor, + lineStroke, + lineStrokeOpacity, + lineStrokeWidth, + } = this.options; const options: LineLayerOptions = { source: { data: [], @@ -177,7 +193,7 @@ export class FlowLayer extends CompositeLayer { y1: 'toLat', }, }, - shape: 'halfLine', + shape: 'flowline', minZoom, maxZoom, zIndex, @@ -185,9 +201,11 @@ export class FlowLayer extends CompositeLayer { blend, pickingBuffer, style: { - borderColor: '#000', - borderWidth: 1, - opacity, + gapWidth: lineStrokeWidth, + stroke: lineStroke, + strokeWidth: lineStrokeWidth, + strokeOpacity: lineStrokeOpacity, + opacity: lineOpacity, }, }; if (this.dataProvider && this.scene) { @@ -196,13 +214,21 @@ export class FlowLayer extends CompositeLayer { this.options.source, this.dataProviderState ); + + options.source.data = this.dataProvider.getFilterFlows(this.options.source, this.dataProviderState); + options.size = getSizeAttribute(lineWidth!, flowWeightRange); + options.color = getColorAttribute(lineColor!, flowWeightRange); + if (this.options.fadeOpacityEnabled && options.style) { options.style.opacity = getOpacityColorAttribute(filterFlowWeightRange, this.options.fadeOpacityAmount!); } - options.source.data = this.dataProvider.getFilterFlows(this.options.source, this.dataProviderState); - options.size = getSizeAttribute(this.options.lineWidth!, flowWeightRange); - options.color = getColorAttribute(this.options.lineColor!, flowWeightRange); + + if (this.circleLayer && options.style) { + const clusterIndex = this.dataProvider.getClusterIndex(this.options.source, this.dataProviderState); + options.style.offsets = getLineOffsetsAttribute(clusterIndex, this.circleLayer as PointLayer); + } } + return options; } } diff --git a/packages/composite-layers/src/composite-layers/flow-layer/types/index.ts b/packages/composite-layers/src/composite-layers/flow-layer/types/index.ts index 678d554eb..f94c8e073 100644 --- a/packages/composite-layers/src/composite-layers/flow-layer/types/index.ts +++ b/packages/composite-layers/src/composite-layers/flow-layer/types/index.ts @@ -217,9 +217,25 @@ export interface FlowLayerOptions extends CompositeLayerOptions, Partial { + return { + field: 'weight', + value: (weight: any) => { return scaleFunc.map(weight) * ratio; }, - ]; + }; +} + +export function getLineOffsetsAttribute( + clusterIndex: ReturnType, + circleLayer: PointLayer +): LineLayerStyleOptions['offsets'] { + const circleLayerSizeAttribute = circleLayer.options.size; + if (typeof circleLayerSizeAttribute === 'number') { + return [circleLayerSizeAttribute, circleLayerSizeAttribute]; + } else { + const sizeScale = circleLayer?.layer?.getScale('size'); + return { + field: 'fromId*toId', + value: (fromId, toId) => { + const fromCluster = clusterIndex.clusterIdMap.get(fromId); + const toCluster = clusterIndex.clusterIdMap.get(toId); + const fromOffset = fromCluster ? sizeScale(fromCluster.weight) : 0; + const toOffset = toCluster ? sizeScale(toCluster.weight) : 0; + return [fromOffset, toOffset] as [number, number]; + }, + }; + } } diff --git a/packages/composite-layers/src/core-layers/line-layer/types.ts b/packages/composite-layers/src/core-layers/line-layer/types.ts index 1ea0aa588..9b39a559a 100644 --- a/packages/composite-layers/src/core-layers/line-layer/types.ts +++ b/packages/composite-layers/src/core-layers/line-layer/types.ts @@ -1,12 +1,12 @@ import { CoreLayerOptions } from '../../core/core-layer'; -import { ShapeAttr } from '../../types'; +import { OptionStyleAttribute, ShapeAttr } from '../../types'; /** * 线图层 图形形状 */ export type ArcLineShape = 'arc' | 'arc3d' | 'greatcircle'; -export type LineShape = 'line' | 'halfLine' | ArcLineShape; +export type LineShape = 'line' | 'flowline' | ArcLineShape; /** * 线图层 线类型 @@ -21,7 +21,7 @@ export enum LineStyleType { */ export type LineLayerStyleOptions = { // 透明度 - opacity?: number | [string, (data: any) => number] | [string, [number, number]]; + opacity?: OptionStyleAttribute; // 线类型 lineType?: keyof typeof LineStyleType; // 虚线间隔 @@ -43,9 +43,15 @@ export type LineLayerStyleOptions = { // 纹理混合方式 textureBlend?: string; // 边框颜色 - borderColor?: string; + stroke?: OptionStyleAttribute; // 边框宽度 - borderWidth?: number; + strokeWidth?: OptionStyleAttribute; + // 边框透明度 + strokeOpacity?: number; + // 线间隙 + gapWidth?: OptionStyleAttribute; + // 线头尾偏移量 + offsets?: OptionStyleAttribute<[number, number]>; }; /** diff --git a/packages/composite-layers/src/core-layers/point-layer/types.ts b/packages/composite-layers/src/core-layers/point-layer/types.ts index bd3585b58..544802fb3 100644 --- a/packages/composite-layers/src/core-layers/point-layer/types.ts +++ b/packages/composite-layers/src/core-layers/point-layer/types.ts @@ -1,5 +1,5 @@ import { CoreLayerOptions } from '../../core/core-layer'; -import { ShapeAttr } from '../../types'; +import { OptionStyleAttribute, ShapeAttr } from '../../types'; /** * 点图层 图层样式 @@ -8,7 +8,7 @@ export type PointLayerStyleOptions = { /** * 透明度 */ - opacity?: number | [string, (data: any) => number] | [string, [number, number]]; + opacity?: OptionStyleAttribute; /** * 描边宽度 */ @@ -62,7 +62,7 @@ export type AnchorType = */ export type PointTextLayerStyleOptions = { /* 透明度 */ - opacity?: number | [string, (data: any) => number] | [string, [number, number]]; + opacity?: OptionStyleAttribute; /* 文本相对锚点的位置 */ textAnchor?: AnchorType; /* 文本相对锚点的偏移量 */ diff --git a/packages/composite-layers/src/types/attr.ts b/packages/composite-layers/src/types/attr.ts index fd4457663..55e0cb12f 100644 --- a/packages/composite-layers/src/types/attr.ts +++ b/packages/composite-layers/src/types/attr.ts @@ -1,5 +1,5 @@ -import type { IAnimateOption, IActiveOption } from '@antv/l7'; -import { ScaleConfig, ISourceCFG } from './common'; +import type { IActiveOption, IAnimateOption } from '@antv/l7'; +import { ISourceCFG, ScaleConfig } from './common'; export type Callback = (data: Record) => T | T[]; @@ -84,6 +84,15 @@ export type FilterAttr = { value: Callback; }; +/** Style 中的配置项 */ +export type OptionStyleAttribute = + | T + | { + field: string; + value: T[] | ((...params: any[]) => T); + } + | [string, T[] | ((...params: any[]) => T)]; + /** * 数据配置 */ diff --git a/website/docs/api/composite-layers/flow-layer.zh.md b/website/docs/api/composite-layers/flow-layer.zh.md index 4fd0a1890..a3182a3a1 100644 --- a/website/docs/api/composite-layers/flow-layer.zh.md +++ b/website/docs/api/composite-layers/flow-layer.zh.md @@ -301,7 +301,7 @@ order: 4 `string|string[]|Function` optional -客流点填充颜色值映射值。 +客流线填充颜色值映射值。 ```js { @@ -332,7 +332,7 @@ order: 4 `number` optional -客流点透明度 +客流线透明度 ```js { @@ -340,6 +340,42 @@ order: 4 } ``` +### `options.`lineStroke + +`string` optional default: `'#000'` + +客流线边框颜色 + +```js +{ + lineStroke: '#000'; +} +``` + +### `options.`lineStrokeWidth + +`number` optional default: `1` + +客流线边框宽度 + +```js +{ + lineStrokeWidth: 1; +} +``` + +### `options.`lineStrokeOpacity + +`number` optional + +客流线边框透明度 + +```js +{ + lineStrokeOpacity: 1; +} +``` + ### options.fadeOpacityEnabled `boolean` optional default: `true` diff --git a/website/docs/common/base-layers/line-layer/shape.zh.md b/website/docs/common/base-layers/line-layer/shape.zh.md index 572010663..4176f5163 100644 --- a/website/docs/common/base-layers/line-layer/shape.zh.md +++ b/website/docs/common/base-layers/line-layer/shape.zh.md @@ -4,7 +4,7 @@ 除直线外还支持 2D 与 3D 弧线及大圆航线: -- halfLine +- flowline - arc - arc3d - greatcircle diff --git a/website/examples/composite/trafficFlow/demo/base.js b/website/examples/composite/trafficFlow/demo/base.js index 0bd2564ad..c83622541 100644 --- a/website/examples/composite/trafficFlow/demo/base.js +++ b/website/examples/composite/trafficFlow/demo/base.js @@ -59,6 +59,9 @@ scene.on('loaded', async () => { maxTopFlowNum: 5000, flowColor1: '#2a5674', flowColor2: '#d1eeea', + lineStroke: '#000', + lineStrokeWidth: 1, + lineStrokeOpacity: 1, visible: true, }; @@ -68,6 +71,9 @@ scene.on('loaded', async () => { gui.addColor(initialOptions, 'flowColor1'); gui.addColor(initialOptions, 'flowColor2'); gui.add(initialOptions, 'visible'); + gui.addColor(initialOptions, 'lineStroke'); + gui.add(initialOptions, 'lineStrokeWidth', 1, 10, 1); + gui.add(initialOptions, 'lineStrokeOpacity', 0, 1, 0.01); gui.onChange(({ object: { flowColor1, flowColor2, ...options } }) => { options.lineColor = {