feat: scale 支持根据字段和颜色指定

This commit is contained in:
thinkinggis 2020-02-18 23:23:28 +08:00
parent 5936f9aa47
commit c19df8344f
14 changed files with 138 additions and 65 deletions

View File

@ -22,6 +22,7 @@ import {
IScale, IScale,
IScaleOptions, IScaleOptions,
IStyleAttributeService, IStyleAttributeService,
ScaleAttributeType,
StyleAttrField, StyleAttrField,
StyleAttributeOption, StyleAttributeOption,
Triangulation, Triangulation,
@ -118,7 +119,7 @@ export interface ILayer {
Partial<IModelInitializationOptions>, Partial<IModelInitializationOptions>,
): IModel; ): IModel;
init(): ILayer; init(): ILayer;
scale(field: string | IScaleOptions, cfg?: IScale): ILayer; scale(field: string | number | IScaleOptions, cfg?: IScale): ILayer;
size(field: StyleAttrField, value?: StyleAttributeOption): ILayer; size(field: StyleAttrField, value?: StyleAttributeOption): ILayer;
color(field: StyleAttrField, value?: StyleAttributeOption): ILayer; color(field: StyleAttrField, value?: StyleAttributeOption): ILayer;
shape(field: StyleAttrField, value?: StyleAttributeOption): ILayer; shape(field: StyleAttrField, value?: StyleAttributeOption): ILayer;
@ -139,6 +140,7 @@ export interface ILayer {
style(options: unknown): ILayer; style(options: unknown): ILayer;
hide(): ILayer; hide(): ILayer;
show(): ILayer; show(): ILayer;
getLegendItems(name: string): any;
setIndex(index: number): ILayer; setIndex(index: number): ILayer;
isVisible(): boolean; isVisible(): boolean;
setMaxZoom(min: number): ILayer; setMaxZoom(min: number): ILayer;

View File

@ -35,8 +35,11 @@ export type ScaleTypeName =
| 'quantize' | 'quantize'
| 'threshold' | 'threshold'
| 'cat'; | 'cat';
export type ScaleAttributeType = 'color' | 'size' | 'shape';
export interface IScale { export interface IScale {
type: ScaleTypeName; type: ScaleTypeName;
field?: string;
ticks?: any[]; ticks?: any[];
nice?: boolean; nice?: boolean;
format?: () => any; format?: () => any;
@ -49,6 +52,7 @@ export enum StyleScaleType {
} }
export interface IScaleOption { export interface IScaleOption {
field?: string; field?: string;
attr?: ScaleAttributeType;
type: ScaleTypeName; type: ScaleTypeName;
ticks?: any[]; ticks?: any[];
nice?: boolean; nice?: boolean;
@ -115,6 +119,11 @@ type CallBack = (...args: any[]) => any;
export type StyleAttributeField = string | string[] | number[]; export type StyleAttributeField = string | string[] | number[];
export type StyleAttributeOption = string | number | boolean | any[] | CallBack; export type StyleAttributeOption = string | number | boolean | any[] | CallBack;
export type StyleAttrField = string | string[] | number | number[]; export type StyleAttrField = string | string[] | number | number[];
export interface IAttributeScale {
field: string | number;
func: unknown;
option: IScaleOption | undefined;
}
export interface IStyleAttributeInitializationOptions { export interface IStyleAttributeInitializationOptions {
name: string; name: string;
@ -125,10 +134,7 @@ export interface IStyleAttributeInitializationOptions {
names: string[] | number[]; names: string[] | number[];
type: StyleScaleType; type: StyleScaleType;
callback?: (...args: any[]) => []; callback?: (...args: any[]) => [];
scalers?: Array<{ scalers?: IAttributeScale[];
field: string | number;
func: unknown;
}>;
}; };
descriptor: IVertexAttributeDescriptor; descriptor: IVertexAttributeDescriptor;
} }
@ -186,6 +192,7 @@ export interface IStyleAttributeService {
): void; ): void;
getLayerStyleAttributes(): IStyleAttribute[] | undefined; getLayerStyleAttributes(): IStyleAttribute[] | undefined;
getLayerStyleAttribute(attributeName: string): IStyleAttribute | undefined; getLayerStyleAttribute(attributeName: string): IStyleAttribute | undefined;
getLayerAttributeScale(attributeName: string): any;
createAttributesAndIndices( createAttributesAndIndices(
encodedFeatures: IEncodeFeature[], encodedFeatures: IEncodeFeature[],
triangulation?: Triangulation, triangulation?: Triangulation,

View File

@ -1,5 +1,7 @@
import { isNil } from 'lodash'; import { isNil } from 'lodash';
import { import {
IAttributeScale,
IScaleOption,
IStyleAttribute, IStyleAttribute,
StyleScaleType, StyleScaleType,
} from '../layer/IStyleAttributeService'; } from '../layer/IStyleAttributeService';
@ -22,10 +24,7 @@ export default class StyleAttribute implements IStyleAttribute {
field: string | string[]; field: string | string[];
values: unknown[]; values: unknown[];
callback?: (...args: any[]) => []; callback?: (...args: any[]) => [];
scalers?: Array<{ scalers?: IAttributeScale[];
field: string;
func: unknown;
}>;
}; };
public descriptor: IVertexAttributeDescriptor; public descriptor: IVertexAttributeDescriptor;
public featureBufferLayout: Array<{ public featureBufferLayout: Array<{

View File

@ -7,6 +7,7 @@ import { IRendererService } from '../renderer/IRendererService';
import { IParseDataItem } from '../source/ISourceService'; import { IParseDataItem } from '../source/ISourceService';
import { ILayer } from './ILayerService'; import { ILayer } from './ILayerService';
import { import {
IAttributeScale,
IEncodeFeature, IEncodeFeature,
IStyleAttribute, IStyleAttribute,
IStyleAttributeInitializationOptions, IStyleAttributeInitializationOptions,
@ -108,6 +109,15 @@ export default class StyleAttributeService implements IStyleAttributeService {
); );
} }
public getLayerAttributeScale(name: string) {
const attribute = this.getLayerStyleAttribute(name);
const scale = attribute?.scale?.scalers as IAttributeScale[];
if (scale && scale[0]) {
return scale[0].func;
}
return null;
}
public updateAttributeByFeatureRange( public updateAttributeByFeatureRange(
attributeName: string, attributeName: string,
features: IEncodeFeature[], features: IEncodeFeature[],

View File

@ -31,6 +31,7 @@ import {
IStyleAttributeService, IStyleAttributeService,
IStyleAttributeUpdateOptions, IStyleAttributeUpdateOptions,
lazyInject, lazyInject,
ScaleAttributeType,
ScaleTypeName, ScaleTypeName,
ScaleTypes, ScaleTypes,
StyleAttributeField, StyleAttributeField,
@ -461,7 +462,7 @@ export default class BaseLayer<ChildLayerStyleOptions = {}> extends EventEmitter
} }
return this; return this;
} }
public scale(field: ScaleTypeName | IScaleOptions, cfg: IScale) { public scale(field: string | IScaleOptions, cfg: IScale) {
if (isObject(field)) { if (isObject(field)) {
this.scaleOptions = { this.scaleOptions = {
...this.scaleOptions, ...this.scaleOptions,
@ -748,6 +749,30 @@ export default class BaseLayer<ChildLayerStyleOptions = {}> extends EventEmitter
} }
return this.configSchema; return this.configSchema;
} }
public getLegendItems(name: string) {
const scale = this.styleAttributeService.getLayerAttributeScale(name);
if (scale) {
if (scale.ticks) {
const items = scale.ticks().map((item: any) => {
return {
value: item,
[name]: scale(item),
};
});
return items;
} else if (scale.invertExtent) {
const items = scale.range().map((item: any) => {
return {
value: scale.invertExtent(item),
[name]: item,
};
});
return items;
}
} else {
return [];
}
}
public pick({ x, y }: { x: number; y: number }) { public pick({ x, y }: { x: number; y: number }) {
this.interactionService.triggerHover({ x, y }); this.interactionService.triggerHover({ x, y });

View File

@ -64,7 +64,7 @@ export default class FeatureScalePlugin implements ILayerPlugin {
this.caculateScalesForAttributes(attributes || [], dataArray); this.caculateScalesForAttributes(attributes || [], dataArray);
}); });
// 检测数据是否需要更新 // 检测数据是否需要更新
layer.hooks.beforeRenderData.tap('FeatureScalePlugin', (flag) => { layer.hooks.beforeRenderData.tap('FeatureScalePlugin', (flag) => {
if (flag) { if (flag) {
this.scaleOptions = layer.getScaleOptions(); this.scaleOptions = layer.getScaleOptions();
@ -146,6 +146,7 @@ export default class FeatureScalePlugin implements ILayerPlugin {
return { return {
field: scale.field, field: scale.field,
func: scale.scale, func: scale.scale,
option: scale.option,
}; };
}); });
@ -160,13 +161,17 @@ export default class FeatureScalePlugin implements ILayerPlugin {
) { ) {
const scalekey = [field, attribute.name].join('_'); const scalekey = [field, attribute.name].join('_');
const values = attribute.scale?.values; const values = attribute.scale?.values;
if (this.scaleCache[scalekey]) { // if (this.scaleCache[scalekey]) {
return this.scaleCache[scalekey]; // return this.scaleCache[scalekey];
} // }
const styleScale = this.createScale(field, values, dataArray); const styleScale = this.createScale(
this.scaleCache[scalekey] = styleScale; field,
attribute.name,
return this.scaleCache[scalekey]; values,
dataArray,
);
// this.scaleCache[scalekey] = styleScale;
return styleScale;
} }
/** /**
@ -188,11 +193,15 @@ export default class FeatureScalePlugin implements ILayerPlugin {
private createScale( private createScale(
field: string | number, field: string | number,
name: string,
values: unknown[] | string | undefined, values: unknown[] | string | undefined,
data?: IParseDataItem[], data?: IParseDataItem[],
): IStyleScale { ): IStyleScale {
// 首先查找全局默认配置例如 color // scale 支持根据视觉通道和字段
const scaleOption: IScale | undefined = this.scaleOptions[field]; const scaleOption: IScale | undefined =
this.scaleOptions[name] && this.scaleOptions[name].field === field
? this.scaleOptions[name]
: this.scaleOptions[field];
const styleScale: IStyleScale = { const styleScale: IStyleScale = {
field, field,
scale: undefined, scale: undefined,

View File

@ -1,5 +1,5 @@
import { IMapConfig, Scene } from '@antv/l7'; import { IMapConfig, Scene } from '@antv/l7';
// @ts-ignore
// tslint:disable-next-line:no-submodule-imports // tslint:disable-next-line:no-submodule-imports
import GaodeMap from '@antv/l7-maps/lib/amap'; import GaodeMap from '@antv/l7-maps/lib/amap';
import React, { createElement, createRef, useEffect, useState } from 'react'; import React, { createElement, createRef, useEffect, useState } from 'react';

View File

@ -11,8 +11,9 @@ export default React.memo(function Chart(props: ILayerProps) {
const { layer, color } = props; const { layer, color } = props;
useEffect(() => { useEffect(() => {
color.field color.field
? layer.color(color.field as StyleAttrField, color.values) ? layer.color(color.field as StyleAttrField, color.value)
: layer.color(color.value as StyleAttrField); : layer.color(color.value as StyleAttrField);
}, [color.value, color.field, JSON.stringify(color.values)]); }, [color.field, color.scale, JSON.stringify(color.value)]);
return null; return null;
}); });

View File

@ -1,12 +1,12 @@
import { ILayer, LineLayer, PointLayer, PolygonLayer, Scene } from '@antv/l7'; import { ILayer, LineLayer, PointLayer, PolygonLayer, Scene } from '@antv/l7';
import * as React from 'react'; import * as React from 'react';
import { useSceneValue } from '../SceneContext'; import { useSceneValue } from '../SceneContext';
import { Color, ILayerProps, Scales, Shape, Size, Source, Style } from './'; import { Color, ILayerProps, Scale, Shape, Size, Source, Style } from './';
const { useEffect, useState } = React; const { useEffect, useState } = React;
export default function BaseLayer(type: string, props: ILayerProps) { export default function BaseLayer(type: string, props: ILayerProps) {
const { source, color, shape, style, size, scales, options } = props; const { source, color, shape, style, size, scale, options } = props;
const mapScene = (useSceneValue() as unknown) as Scene; const mapScene = (useSceneValue() as unknown) as Scene;
const [layer, setLayer] = useState(); const [layer, setLayer] = useState();
if (!layer) { if (!layer) {
@ -41,7 +41,7 @@ export default function BaseLayer(type: string, props: ILayerProps) {
return ( return (
<> <>
<Source layer={layer} source={source} /> <Source layer={layer} source={source} />
{scales && <Scales layer={layer} scales={scales} />} {scale && <Scale layer={layer} scale={scale} />}
<Color layer={layer} color={color} /> <Color layer={layer} color={color} />
{size && <Size layer={layer} size={size} />} {size && <Size layer={layer} size={size} />}
<Shape layer={layer} shape={shape} /> <Shape layer={layer} shape={shape} />

View File

@ -5,14 +5,14 @@ import { IScaleAttributeOptions } from './';
const { useEffect } = React; const { useEffect } = React;
interface ILayerProps { interface ILayerProps {
layer: ILayer; layer: ILayer;
scales: Partial<IScaleAttributeOptions>; scale: Partial<IScaleAttributeOptions>;
} }
export default React.memo(function Chart(props: ILayerProps) { export default React.memo(function Chart(props: ILayerProps) {
const { layer, scales } = props; const { layer, scale } = props;
useEffect(() => { useEffect(() => {
scales.field scale.value
? layer.scale(scales.field as string, scales.value as IScale) ? layer.scale(scale.field as string, scale.value as IScale)
: layer.scale(scales.values as IScaleOptions); : layer.scale(scale.field as IScaleOptions);
}, [scales.value, scales.field, JSON.stringify(scales.values)]); }, [scale.value, scale.field]);
return null; return null;
}); });

View File

@ -13,6 +13,6 @@ export default React.memo(function Chart(props: ILayerProps) {
size.field size.field
? layer.size(size.field, size.values) ? layer.size(size.field, size.values)
: layer.size(size.value as StyleAttrField); : layer.size(size.value as StyleAttrField);
}, [size.field, size.value, JSON.stringify(size.values)]); }, [size.field, size.value, size.scale, size.values]);
return null; return null;
}); });

View File

@ -1,6 +1,11 @@
import { IScale, IScaleOptions, ISourceCFG } from '@antv/l7'; import {
IScale,
IScaleOptions,
ISourceCFG,
ScaleAttributeType,
} from '@antv/l7';
import Color from './Color'; import Color from './Color';
import Scales from './Scales'; import Scale from './Scale';
import Shape from './Shape'; import Shape from './Shape';
import Size from './Size'; import Size from './Size';
import Source from './Source'; import Source from './Source';
@ -9,13 +14,18 @@ export interface IAttributeOptions {
field: string; field: string;
value: string | number; value: string | number;
values: string[] | number[] | string; values: string[] | number[] | string;
scale?: string;
} }
export interface IScaleAttributeOptions { export interface IScaleAttributeOptions {
field: string; field: string | IScaleOptions;
value: IScale; value: IScale;
values: IScaleOptions; values: IScaleOptions;
} }
export interface IScaleOption {
[key: string]: IScaleAttributeOptions;
}
export interface IStyleOptions { export interface IStyleOptions {
opacity: number; opacity: number;
[key: string]: any; [key: string]: any;
@ -30,9 +40,9 @@ export interface ILayerProps {
source: ISourceOptions; source: ISourceOptions;
color: Partial<IAttributeOptions>; color: Partial<IAttributeOptions>;
shape: Partial<IAttributeOptions>; shape: Partial<IAttributeOptions>;
scales?: Partial<IScaleAttributeOptions>; scale?: Partial<IScaleAttributeOptions>;
size?: Partial<IAttributeOptions>; size?: Partial<IAttributeOptions>;
style?: Partial<IStyleOptions>; style?: Partial<IStyleOptions>;
} }
export { Source, Size, Color, Shape, Style, Scales }; export { Source, Size, Color, Shape, Style, Scale };

View File

@ -1,5 +1,5 @@
import { IMapConfig, Scene } from '@antv/l7'; import { IMapConfig, Scene } from '@antv/l7';
// @ts-ignore
// tslint:disable-next-line:no-submodule-imports // tslint:disable-next-line:no-submodule-imports
import Mapbox from '@antv/l7-maps/lib/mapbox'; import Mapbox from '@antv/l7-maps/lib/mapbox';
import React, { createElement, createRef, useEffect, useState } from 'react'; import React, { createElement, createRef, useEffect, useState } from 'react';

View File

@ -19,40 +19,50 @@ export default class Point3D extends React.Component {
const scene = new Scene({ const scene = new Scene({
id: document.getElementById('map') as HTMLDivElement, id: document.getElementById('map') as HTMLDivElement,
map: new Mapbox({ map: new GaodeMap({
center: [120.19382669582967, 30.258134], center: [120.19382669582967, 30.258134],
pitch: 0, pitch: 0,
style: 'dark', style: 'dark',
zoom: 0, zoom: 0,
}), }),
}); });
// scene.on('loaded', () => {
const pointLayer = new PointLayer({})
.source(pointsData, {
cluster: false,
})
.shape('circle')
// .scale('point_count', {
// type: 'quantile',
// })
.size('mag', [5, 10, 15, 20, 25])
.animate(false)
.active(true)
.color('yellow')
.style({
opacity: 0.5,
strokeWidth: 1,
});
scene.addLayer(pointLayer);
scene.on('loaded', () => { scene.on('loaded', () => {
const newData = { const pointLayer = new PointLayer({})
type: 'FeatureCollection', .source(pointsData, {
features: pointsData.features.slice(0, 100), cluster: false,
}; })
pointLayer.setData(newData); .scale({
size: {
type: 'power',
field: 'mag',
},
color: {
type: 'linear',
field: 'mag',
},
})
.shape('circle')
.size('mag', [2, 8, 14, 20, 26, 32, 40])
.animate(false)
.active(true)
.color('mag', ['red', 'blue', 'yellow', 'green'])
.style({
opacity: 0.5,
strokeWidth: 1,
});
scene.addLayer(pointLayer);
const items = pointLayer.getLegendItems('color');
console.log(items);
console.log(pointLayer.getLegendItems('size'));
// scene.on('loaded', () => {
// const newData = {
// type: 'FeatureCollection',
// features: pointsData.features.slice(0, 100),
// };
// pointLayer.setData(newData);
// });
this.scene = scene;
}); });
this.scene = scene;
// });
} }
public render() { public render() {