diff --git a/.gitignore b/.gitignore
index 0b84735259..115b6ecf1f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -78,3 +78,4 @@ git_log.sh
node_modules/
packages/l7/package_bak.json
+stories/Test
diff --git a/docs/api/layer/pointlayer.zh.md b/docs/api/layer/pointlayer.zh.md
index 5804e506b9..5dc8c8f83a 100644
--- a/docs/api/layer/pointlayer.zh.md
+++ b/docs/api/layer/pointlayer.zh.md
@@ -25,6 +25,14 @@ shape 支持
```
+## source
+
+点数据类型,根据经纬点绘制图形,目前支持三种数据结构
+
+- [GeoJOSN]('../source/geojson/#point')
+- [CSV]()
+- [JSON](../source/json/#点数据)
+
**图片标注**
通过 `Scene.addImage()` 可以添加图片资源,
diff --git a/docs/api/source/csv.en.md b/docs/api/source/csv.en.md
new file mode 100644
index 0000000000..eed20c031c
--- /dev/null
+++ b/docs/api/source/csv.en.md
@@ -0,0 +1,53 @@
+---
+title: CSV
+order: 3
+---
+
+L7 支持 CSV 以逗号分隔的 CSV 数据加载。
+
+CSV 是文本数据结构,很难表达复杂的地理数据结构,因此 CSV 仅支持两种数据结构
+
+- 点数据 需要指定经度,纬度坐标
+- 线段,弧线数据 需要指定 起始点的 经度,纬度坐标
+
+## parser
+
+- type string 必选 json
+- x string 点数据表示 经度
+- y string 点数据表示 纬度
+- x1 string 经度
+- x2 string 纬度
+
+### 点数据通过 CSV 加载
+
+```javascript
+layer.source(data, {
+ parser: {
+ type: 'csv',
+ x: 'lng',
+ y: 'lat',
+ },
+});
+```
+
+[CSV 数据 demo 示例](../../../examples/point/bubble#scatter)
+
+### 线段弧线数据通过 CSV 加载
+
+```javascript
+layer.source(
+ data,
+ {
+ parser:{
+ type:'csv',
+ x:'lng1',
+ y:'lat1' ,
+ x1:'lng1',
+ y1:'lat2' ,
+ }
+ }
+})
+
+```
+
+[CSV 线段数据 demo 示例](../../../examples/gallery/basic#arcCircle)
diff --git a/docs/api/source/csv.zh.md b/docs/api/source/csv.zh.md
new file mode 100644
index 0000000000..eed20c031c
--- /dev/null
+++ b/docs/api/source/csv.zh.md
@@ -0,0 +1,53 @@
+---
+title: CSV
+order: 3
+---
+
+L7 支持 CSV 以逗号分隔的 CSV 数据加载。
+
+CSV 是文本数据结构,很难表达复杂的地理数据结构,因此 CSV 仅支持两种数据结构
+
+- 点数据 需要指定经度,纬度坐标
+- 线段,弧线数据 需要指定 起始点的 经度,纬度坐标
+
+## parser
+
+- type string 必选 json
+- x string 点数据表示 经度
+- y string 点数据表示 纬度
+- x1 string 经度
+- x2 string 纬度
+
+### 点数据通过 CSV 加载
+
+```javascript
+layer.source(data, {
+ parser: {
+ type: 'csv',
+ x: 'lng',
+ y: 'lat',
+ },
+});
+```
+
+[CSV 数据 demo 示例](../../../examples/point/bubble#scatter)
+
+### 线段弧线数据通过 CSV 加载
+
+```javascript
+layer.source(
+ data,
+ {
+ parser:{
+ type:'csv',
+ x:'lng1',
+ y:'lat1' ,
+ x1:'lng1',
+ y1:'lat2' ,
+ }
+ }
+})
+
+```
+
+[CSV 线段数据 demo 示例](../../../examples/gallery/basic#arcCircle)
diff --git a/docs/api/source/image.en.md b/docs/api/source/image.en.md
new file mode 100644
index 0000000000..153208b5b2
--- /dev/null
+++ b/docs/api/source/image.en.md
@@ -0,0 +1,25 @@
+---
+title: Image
+order: 4
+---
+
+Image 数据主要用于在地图根据经纬度范围添加图图片,不如一幅纸制地图扫描版你要放在地图显示。
+
+## parser
+
+- type: image
+- extent: 图像的经纬度范围 [minlng, minlat,maxLng, maxLat]
+
+根据图片的经纬度范围,将图片添加到地图上。
+
+```javascript
+layer.source(
+ 'https://gw.alipayobjects.com/zos/rmsportal/FnHFeFklTzKDdUESRNDv.jpg',
+ {
+ parser: {
+ type: 'image',
+ extent: [121.168, 30.2828, 121.384, 30.4219],
+ },
+ },
+);
+```
diff --git a/docs/api/source/image.zh.md b/docs/api/source/image.zh.md
new file mode 100644
index 0000000000..153208b5b2
--- /dev/null
+++ b/docs/api/source/image.zh.md
@@ -0,0 +1,25 @@
+---
+title: Image
+order: 4
+---
+
+Image 数据主要用于在地图根据经纬度范围添加图图片,不如一幅纸制地图扫描版你要放在地图显示。
+
+## parser
+
+- type: image
+- extent: 图像的经纬度范围 [minlng, minlat,maxLng, maxLat]
+
+根据图片的经纬度范围,将图片添加到地图上。
+
+```javascript
+layer.source(
+ 'https://gw.alipayobjects.com/zos/rmsportal/FnHFeFklTzKDdUESRNDv.jpg',
+ {
+ parser: {
+ type: 'image',
+ extent: [121.168, 30.2828, 121.384, 30.4219],
+ },
+ },
+);
+```
diff --git a/docs/api/source/json.en.md b/docs/api/source/json.en.md
index 2a112c3517..6d93a25a89 100644
--- a/docs/api/source/json.en.md
+++ b/docs/api/source/json.en.md
@@ -1,6 +1,6 @@
---
title: JSON
-order: 1
+order: 2
---
GeoJSON 虽然是通用的的地理数据格式,在具体使用场景中,数据服务人员可能并不熟悉 GeoJON,或者没有生成 GeoJON 的工具, 因此 L7 对数据定义了 Parser 的概念,你的数据可以是任何格式,使用指定数据对应的地理信息字段即可。
diff --git a/docs/api/source/json.zh.md b/docs/api/source/json.zh.md
index 2a112c3517..5ded5808b8 100644
--- a/docs/api/source/json.zh.md
+++ b/docs/api/source/json.zh.md
@@ -1,6 +1,6 @@
---
title: JSON
-order: 1
+order: 2
---
GeoJSON 虽然是通用的的地理数据格式,在具体使用场景中,数据服务人员可能并不熟悉 GeoJON,或者没有生成 GeoJON 的工具, 因此 L7 对数据定义了 Parser 的概念,你的数据可以是任何格式,使用指定数据对应的地理信息字段即可。
@@ -39,6 +39,8 @@ layer.source(data, {
});
```
+[JOSN 数据 demo 示例](../../../examples/gallery/basic)
+
### 通用解析方式
可也解析任意复杂的点,线面
diff --git a/docs/api/source/source.en.md b/docs/api/source/source.en.md
index 40289647b4..23d82db659 100644
--- a/docs/api/source/source.en.md
+++ b/docs/api/source/source.en.md
@@ -40,100 +40,25 @@ layer.source(data);
#### JSON
-[JSON 数据格式解析](../json)
+[JSON 数据格式解析](./json)
#### csv
-点,线数据配置项同 json 数据类型
+[CSV 数据格式解析](./csv)
-```javascript
-layer.source(data, {
- parser: {
- type: 'csv',
- x: 'lng1',
- y: 'lat1',
- x1: 'lng1',
- y1: 'lat2',
- },
-});
-```
-
-**栅格数据类型 **
+栅格数据类型
#### image
-根据图片的经纬度范围,将图片添加到地图上。 配置项
-
-- type: image
-- extent: 图像的经纬度范围 []
-
-```javascript
-layer.source(
- 'https://gw.alipayobjects.com/zos/rmsportal/FnHFeFklTzKDdUESRNDv.jpg',
- {
- parser: {
- type: 'image',
- extent: [121.168, 30.2828, 121.384, 30.4219],
- },
- },
-);
-```
-
-#### raster
-
-栅格数据类型,主要表示遥感数据类型 data 栅格数据的二维矩阵数据 parser 配置项
-
-- type raster
-- width 数据宽度二维矩阵 columns
-- height 数据高度
-- min 数据最大值
-- max 数据最小值
-- extent 经纬度范围
-
-```javascript
-source(values, {
- parser: {
- type: 'raster',
- width: n,
- height: m,
- min: 0,
- max: 8000,
- extent: [73.482190241, 3.82501784112, 135.106618732, 57.6300459963],
- },
-});
-```
+[Image 数据格式解析](./image)
### transforms
-目前支持三种数据处理方法 map,grid,hexagon transform 配置项
+目前支持两种热力图使用的数据处理方法 grid,hexagon transform 配置项
- type 数据处理类型
- tansform cfg 数据处理配置项
-#### map
-
-数据处理,支持自定义 callback 函数
-
-- callback:function 回调函数
-
-```javascript
-layer.source(data, {
- transforms: [
- {
- type: 'map',
- callback: function(item) {
- const [x, y] = item.coordinates;
- item.lat = item.lat * 1;
- item.lng = item.lng * 1;
- item.v = item.v * 1;
- item.coordinates = [x * 1, y * 1];
- return item;
- },
- },
- ],
-});
-```
-
#### grid
生成方格网布局,根据数据字段统计,主要在网格热力图中使用
@@ -163,4 +88,4 @@ layer.source(data, {
- type: 'hexagon',
- size: 网格半径
- field: 数据统计字段
-- method:聚合方法 count,max,min,sum,mean5 个统计维度
+- method:聚合方法 count,max,min,sum,mean 5 个统计维度
diff --git a/docs/api/source/source.zh.md b/docs/api/source/source.zh.md
index 89bef2766b..45b0f47246 100644
--- a/docs/api/source/source.zh.md
+++ b/docs/api/source/source.zh.md
@@ -7,6 +7,13 @@ order: 0
source 地理数据处理模块,主要包含数据解析(parser),和数据处理(transform);
+- data
+- option
+ - cluster **boolean** 是否聚合
+ - clusterOption 聚合配置项
+ - parser 数据解析配置
+ - transforms 数据处理配置
+
### parser
不同数据类型处理成统一数据格式。矢量数据包括 GeoJON, CSV,Json 等不同数据格式,栅格数据,包括 Raster,Image 数据。将来还会支持瓦片格式数据。
@@ -23,6 +30,14 @@ source 地理数据处理模块,主要包含数据解析(parser),和数据
## API
+### cluster 可选 可以只设置 cluster
+
+### clusterOption 可选
+
+- radius 聚合半径 **number** default 40
+- minZoom: 最小聚合缩放等级 **number** default 0
+- maxZoom: 最大聚合缩放等级 **number** default 16
+
### parser
**配置项**
@@ -40,100 +55,25 @@ layer.source(data);
#### JSON
-[JSON 数据格式解析](../json)
+[JSON 数据格式解析](./json)
#### csv
-点,线数据配置项同 json 数据类型
+[CSV 数据格式解析](./csv)
-```javascript
-layer.source(data, {
- parser: {
- type: 'csv',
- x: 'lng1',
- y: 'lat1',
- x1: 'lng1',
- y1: 'lat2',
- },
-});
-```
-
-**栅格数据类型 **
+栅格数据类型
#### image
-根据图片的经纬度范围,将图片添加到地图上。 配置项
-
-- type: image
-- extent: 图像的经纬度范围 []
-
-```javascript
-layer.source(
- 'https://gw.alipayobjects.com/zos/rmsportal/FnHFeFklTzKDdUESRNDv.jpg',
- {
- parser: {
- type: 'image',
- extent: [121.168, 30.2828, 121.384, 30.4219],
- },
- },
-);
-```
-
-#### raster
-
-栅格数据类型,主要表示遥感数据类型 data 栅格数据的二维矩阵数据 parser 配置项
-
-- type raster
-- width 数据宽度二维矩阵 columns
-- height 数据高度
-- min 数据最大值
-- max 数据最小值
-- extent 经纬度范围
-
-```javascript
-source(values, {
- parser: {
- type: 'raster',
- width: n,
- height: m,
- min: 0,
- max: 8000,
- extent: [73.482190241, 3.82501784112, 135.106618732, 57.6300459963],
- },
-});
-```
+[Image 数据格式解析](./image)
### transforms
-目前支持三种数据处理方法 map,grid,hexagon transform 配置项
+目前支持两种热力图使用的数据处理方法 grid,hexagon transform 配置项
- type 数据处理类型
- tansform cfg 数据处理配置项
-#### map
-
-数据处理,支持自定义 callback 函数
-
-- callback:function 回调函数
-
-```javascript
-layer.source(data, {
- transforms: [
- {
- type: 'map',
- callback: function(item) {
- const [x, y] = item.coordinates;
- item.lat = item.lat * 1;
- item.lng = item.lng * 1;
- item.v = item.v * 1;
- item.coordinates = [x * 1, y * 1];
- return item;
- },
- },
- ],
-});
-```
-
#### grid
生成方格网布局,根据数据字段统计,主要在网格热力图中使用
diff --git a/docs/tutorial/quickstart.zh.md b/docs/tutorial/quickstart.zh.md
index 7eaead8843..f8edaff8e0 100644
--- a/docs/tutorial/quickstart.zh.md
+++ b/docs/tutorial/quickstart.zh.md
@@ -15,8 +15,11 @@ Current version: ![L7 2.0版本号](https://badgen.net/npm/v/@antv/l7/beta)
Include the L7 JS JavaScript
of your HTML file.
+:warning: 如果需要引用第三方地图API,请确保在先引入第三方API,然后引入L7
+
```html
+
@@ -41,6 +44,7 @@ npm install --save @antv/l7-maps;
```
+
### 初始化地图
#### 使用 高德 底图
@@ -80,3 +84,76 @@ const scene = new Scene({
```
+### React中使用
+
+React 组件待开发,期待和大家共建l7-react 目前可以暂时以 Submodule 方式使用
+
+```
+import { Scene, PolygonLayer } from '@antv/l7';
+import { AMap } from '@antv/l7-maps';
+import * as React from 'react';
+
+export default class AMapExample extends React.Component {
+ private scene: Scene;
+
+ public componentWillUnmount() {
+ this.scene.destroy();
+ }
+
+ public async componentDidMount() {
+ const response = await fetch(
+ 'https://gw.alipayobjects.com/os/basement_prod/d2e0e930-fd44-4fca-8872-c1037b0fee7b.json',
+ );
+ const scene = new Scene({
+ id: 'map',
+ map: new AMap({
+ center: [110.19382669582967, 50.258134],
+ pitch: 0,
+ style: 'dark',
+ zoom: 3,
+ token: 'pg.xxx', // 高德或者 Mapbox 的 token
+ }),
+ });
+ const layer = new PolygonLayer({});
+
+ layer
+ .source(await response.json())
+ .size('name', [0, 10000, 50000, 30000, 100000])
+ .color('name', [
+ '#2E8AE6',
+ '#69D1AB',
+ '#DAF291',
+ '#FFD591',
+ '#FF7A45',
+ '#CF1D49',
+ ])
+ .shape('fill')
+ .style({
+ opacity: 0.8,
+ });
+ scene.addLayer(layer);
+ this.scene = scene;
+ }
+
+ public render() {
+ return (
+
+ );
+ }
+}
+```
+
+⚠️组件 Unmount 时需要通过 scene.destroy() 手动销毁场景。
+
+更多React使用 [示例查看](https://github.com/antvis/L7/tree/master/stories)
+
+### Vue 欢迎补充
\ No newline at end of file
diff --git a/examples/point/chart/demo/ring.js b/examples/point/chart/demo/ring.js
index 47541248ca..81b520e40b 100644
--- a/examples/point/chart/demo/ring.js
+++ b/examples/point/chart/demo/ring.js
@@ -44,67 +44,71 @@ const scene = new Scene({
zoom: 3.802
})
});
-Promise.all([
- fetch(
- 'https://gw.alipayobjects.com/os/basement_prod/5b772136-a1f4-4fc5-9a80-9f9974b4b182.json'
- ).then(d => d.json()),
- fetch(
- 'https://gw.alipayobjects.com/os/basement_prod/f3c467a4-9ae0-4f08-bb5f-11f9c869b2cb.json'
- ).then(d => d.json())
-]).then(function onLoad([ center, population ]) {
- const popobj = {};
- population.forEach(element => {
- popobj[element.Code] =
- element['Population, female (% of total) (% of total)'];
- });
- // 数据绑定
+addChart();
+scene.render();
+function addChart() {
+ Promise.all([
+ fetch(
+ 'https://gw.alipayobjects.com/os/basement_prod/5b772136-a1f4-4fc5-9a80-9f9974b4b182.json'
+ ).then(d => d.json()),
+ fetch(
+ 'https://gw.alipayobjects.com/os/basement_prod/f3c467a4-9ae0-4f08-bb5f-11f9c869b2cb.json'
+ ).then(d => d.json())
+ ]).then(function onLoad([ center, population ]) {
+ const popobj = {};
+ population.forEach(element => {
+ popobj[element.Code] =
+ element['Population, female (% of total) (% of total)'];
+ });
+ // 数据绑定
- center.features = center.features.map(fe => {
- fe.properties.female = popobj[fe.properties.id] * 1 || 0;
- return fe;
- });
- center.features.forEach(point => {
- const el = document.createElement('div');
- const coord = point.geometry.coordinates;
- const v = point.properties.female * 1;
- if (v < 1 || (v > 46 && v < 54)) {
- return;
- }
- const size = 60;
- const data = [
- {
- type: '男性',
- value: 100.0 - v.toFixed(2)
- },
- {
- type: '女性',
- value: v.toFixed(2) * 1
+ center.features = center.features.map(fe => {
+ fe.properties.female = popobj[fe.properties.id] * 1 || 0;
+ return fe;
+ });
+ center.features.forEach(point => {
+ const el = document.createElement('div');
+ const coord = point.geometry.coordinates;
+ const v = point.properties.female * 1;
+ if (v < 1 || (v > 46 && v < 54)) {
+ return;
}
- ];
- const chart = new G2.Chart({
- container: el,
- width: size,
- height: size,
- render: 'svg',
- padding: 0
+ const size = 60;
+ const data = [
+ {
+ type: '男性',
+ value: 100.0 - v.toFixed(2)
+ },
+ {
+ type: '女性',
+ value: v.toFixed(2) * 1
+ }
+ ];
+ const chart = new G2.Chart({
+ container: el,
+ width: size,
+ height: size,
+ render: 'svg',
+ padding: 0
+ });
+ chart.source(data);
+ chart.legend(false);
+ chart.tooltip(false);
+ chart.coord('theta', {
+ radius: 0.9,
+ innerRadius: 0.6
+ });
+ chart
+ .intervalStack()
+ .position('value')
+ .color('type', [ '#5CCEA1', '#5B8FF9' ])
+ .opacity(1);
+ chart.render();
+ const marker = new Marker({ element: el }).setLnglat({
+ lng: coord[0],
+ lat: coord[1]
+ });
+ scene.addMarker(marker);
});
- chart.source(data);
- chart.legend(false);
- chart.tooltip(false);
- chart.coord('theta', {
- radius: 0.9,
- innerRadius: 0.6
- });
- chart
- .intervalStack()
- .position('value')
- .color('type', [ '#5CCEA1', '#5B8FF9' ])
- .opacity(1);
- chart.render();
- const marker = new Marker({ element: el }).setLnglat({
- lng: coord[0],
- lat: coord[1]
- });
- scene.addMarker(marker);
});
-});
+}
diff --git a/packages/core/src/services/component/MarkerService.ts b/packages/core/src/services/component/MarkerService.ts
index e742bef00c..dd30a4b276 100644
--- a/packages/core/src/services/component/MarkerService.ts
+++ b/packages/core/src/services/component/MarkerService.ts
@@ -11,7 +11,7 @@ export default class MarkerService implements IMarkerService {
private markers: IMarker[] = [];
private unAddMarkers: IMarker[] = [];
public addMarker(marker: IMarker): void {
- if (!this.mapsService.map && this.mapsService.getMarkerContainer()) {
+ if (this.mapsService.map && this.mapsService.getMarkerContainer()) {
this.markers.push(marker);
marker.addTo(this.scene);
} else {
diff --git a/packages/core/src/services/config/ConfigService.ts b/packages/core/src/services/config/ConfigService.ts
index 0e3a7ff13b..0b76770525 100644
--- a/packages/core/src/services/config/ConfigService.ts
+++ b/packages/core/src/services/config/ConfigService.ts
@@ -47,9 +47,13 @@ const defaultLayerConfig: Partial = {
minZoom: 0,
maxZoom: 20,
visible: true,
+ autoFit: false,
zIndex: 0,
- enableMultiPassRenderer: false,
- enablePicking: false,
+ pickedFeatureID: -1,
+ enableMultiPassRenderer: true,
+ enablePicking: true,
+ active: false,
+ activeColor: 'red',
enableHighlight: false,
highlightColor: 'red',
enableTAA: false,
diff --git a/packages/core/src/services/interaction/IInteractionService.ts b/packages/core/src/services/interaction/IInteractionService.ts
index df77a1d265..ed351c574e 100644
--- a/packages/core/src/services/interaction/IInteractionService.ts
+++ b/packages/core/src/services/interaction/IInteractionService.ts
@@ -8,7 +8,7 @@ export interface IInteractionService {
destroy(): void;
on(
eventName: InteractionEvent,
- callback: (params: { x: number; y: number }) => void,
+ callback: (params: { x: number; y: number; type: string }) => void,
): void;
- triggerHover({ x, y }: { x: number; y: number }): void;
+ triggerHover({ x, y, type }: { x: number; y: number; type?: string }): void;
}
diff --git a/packages/core/src/services/interaction/InteractionService.ts b/packages/core/src/services/interaction/InteractionService.ts
index 65d1bb58a5..add63327e4 100644
--- a/packages/core/src/services/interaction/InteractionService.ts
+++ b/packages/core/src/services/interaction/InteractionService.ts
@@ -5,7 +5,6 @@ import { TYPES } from '../../types';
import { ILogService } from '../log/ILogService';
import { IMapService } from '../map/IMapService';
import { IInteractionService, InteractionEvent } from './IInteractionService';
-
/**
* 由于目前 L7 与地图结合的方案为双 canvas 而非共享 WebGL Context,事件监听注册在地图底图上。
* 除此之外,后续如果支持非地图场景,事件监听就需要注册在 L7 canvas 上。
@@ -49,8 +48,13 @@ export default class InteractionService extends EventEmitter
// hammertime.on('panmove', this.onPanmove);
// hammertime.on('panend', this.onPanend);
// hammertime.on('pinch', this.onPinch);
-
$containter.addEventListener('mousemove', this.onHover);
+ $containter.addEventListener('click', this.onHover);
+ $containter.addEventListener('mousedown', this.onHover);
+ $containter.addEventListener('mouseup', this.onHover);
+ $containter.addEventListener('dblclick', this.onHover);
+ $containter.addEventListener('contextmenu', this.onHover);
+
this.hammertime = hammertime;
// TODO: 根据场景注册事件到 L7 canvas 上
@@ -62,16 +66,21 @@ export default class InteractionService extends EventEmitter
const $containter = this.mapService.getMapContainer();
if ($containter) {
$containter.removeEventListener('mousemove', this.onHover);
+ $containter.removeEventListener('click', this.onHover);
+ $containter.removeEventListener('mousedown', this.onHover);
+ $containter.removeEventListener('mouseup', this.onHover);
+ $containter.removeEventListener('dblclick', this.onHover);
+ $containter.removeEventListener('contextmenu', this.onHover);
}
}
- private onHover = ({ x, y }: MouseEvent) => {
+ private onHover = ({ x, y, type }: MouseEvent) => {
const $containter = this.mapService.getMapContainer();
if ($containter) {
const { top, left } = $containter.getBoundingClientRect();
x -= left;
y -= top;
}
- this.emit(InteractionEvent.Hover, { x, y });
+ this.emit(InteractionEvent.Hover, { x, y, type });
};
}
diff --git a/packages/core/src/services/layer/ILayerService.ts b/packages/core/src/services/layer/ILayerService.ts
index 79d362db17..c3164a439a 100644
--- a/packages/core/src/services/layer/ILayerService.ts
+++ b/packages/core/src/services/layer/ILayerService.ts
@@ -22,7 +22,13 @@ import {
StyleAttributeOption,
Triangulation,
} from './IStyleAttributeService';
-
+export interface IDataState {
+ dataSourceNeedUpdate: boolean;
+ dataMappingNeedUpdate: boolean;
+ filterNeedUpdate: boolean;
+ featureScaleNeedUpdate: boolean;
+ StyleAttrNeedUpdate: boolean;
+}
export interface ILayerModelInitializationOptions {
moduleName: string;
vertexShader: string;
@@ -44,6 +50,10 @@ export interface IPickedFeature {
lnglat?: { lng: number; lat: number };
feature?: unknown;
}
+// 交互样式
+export interface IActiveOption {
+ color: string | number[];
+}
export interface ILayer {
id: string; // 一个场景中同一类型 Layer 可能存在多个
@@ -52,9 +62,11 @@ export interface ILayer {
zIndex: number;
plugins: ILayerPlugin[];
layerModelNeedUpdate: boolean;
- dataPluginsState: { [key: string]: boolean };
+ dataState: IDataState; // 数据流状态
+ pickedFeatureID: number;
hooks: {
init: SyncBailHook;
+ afterInit: SyncBailHook;
beforeRenderData: SyncWaterfallHook;
beforeRender: SyncBailHook;
afterRender: SyncHook;
@@ -87,7 +99,16 @@ export interface ILayer {
animate(option: IAnimateOption): ILayer;
// pattern(field: string, value: StyleAttributeOption): ILayer;
filter(field: string, value: StyleAttributeOption): ILayer;
- // active(option: ActiveOption): ILayer;
+ active(option: IActiveOption | boolean): ILayer;
+ setActive(
+ id: number | { x: number; y: number },
+ option?: IActiveOption,
+ ): void;
+ select(option: IActiveOption | false): ILayer;
+ setSelect(
+ id: number | { x: number; y: number },
+ option?: IActiveOption,
+ ): void;
style(options: unknown): ILayer;
hide(): ILayer;
show(): ILayer;
@@ -100,22 +121,24 @@ export interface ILayer {
destroy(): void;
source(data: any, option?: ISourceCFG): ILayer;
setData(data: any, option?: ISourceCFG): ILayer;
+ fitBounds(): ILayer;
/**
* 向当前图层注册插件
* @param plugin 插件实例
*/
addPlugin(plugin: ILayerPlugin): ILayer;
getSource(): ISource;
- isSourceNeedUpdate(): boolean;
setSource(source: ISource): void;
setEncodedData(encodedData: IEncodeFeature[]): void;
getEncodedData(): IEncodeFeature[];
getScaleOptions(): IScaleOptions;
+
/**
* 事件
*/
on(type: string, hander: (...args: any[]) => void): void;
off(type: string, hander: (...args: any[]) => void): void;
+ emit(type: string, hander: unknown): void;
once(type: string, hander: (...args: any[]) => void): void;
/**
* JSON Schema 用于校验配置项
@@ -126,6 +149,8 @@ export interface ILayer {
* 直接调用拾取方法,在非鼠标交互场景中使用
*/
pick(query: { x: number; y: number }): void;
+
+ updateLayerConfig(configToUpdate: Partial): void;
}
/**
@@ -160,6 +185,8 @@ export interface ILayerConfig {
maxZoom: number;
visible: boolean;
zIndex: number;
+ autoFit: boolean;
+ pickedFeatureID: number;
enableMultiPassRenderer: boolean;
passes: Array;
@@ -175,6 +202,8 @@ export interface ILayerConfig {
* 高亮颜色
*/
highlightColor: string | number[];
+ active: boolean;
+ activeColor: string | number[];
/**
* 开启 TAA
*/
diff --git a/packages/core/src/services/renderer/passes/PixelPickingPass.ts b/packages/core/src/services/renderer/passes/PixelPickingPass.ts
index cad35e8eb8..a9c50ee8d7 100644
--- a/packages/core/src/services/renderer/passes/PixelPickingPass.ts
+++ b/packages/core/src/services/renderer/passes/PixelPickingPass.ts
@@ -112,7 +112,18 @@ export default class PixelPickingPass<
* 拾取视口指定坐标属于的要素
* TODO:支持区域拾取
*/
- private pickFromPickingFBO = ({ x, y }: { x: number; y: number }) => {
+ private pickFromPickingFBO = ({
+ x,
+ y,
+ type,
+ }: {
+ x: number;
+ y: number;
+ type: string;
+ }) => {
+ if (!this.layer.isVisible()) {
+ return;
+ }
const {
getViewportSize,
readPixels,
@@ -152,9 +163,9 @@ export default class PixelPickingPass<
) {
this.logger.debug('picked');
const pickedFeatureIdx = decodePickingColor(pickedColors);
- const rawFeature = this.layer.getSource()?.data?.dataArray[
- pickedFeatureIdx
- ];
+ const rawFeature = this.layer
+ .getSource()
+ .getFeatureById(pickedFeatureIdx);
if (!rawFeature) {
// this.logger.error(
@@ -162,7 +173,7 @@ export default class PixelPickingPass<
// );
} else {
// trigger onHover/Click callback on layer
- this.triggerHoverOnLayer({ x, y, feature: rawFeature });
+ this.triggerHoverOnLayer({ x, y, type, feature: rawFeature });
}
}
});
@@ -175,27 +186,34 @@ export default class PixelPickingPass<
private triggerHoverOnLayer({
x,
y,
+ type,
feature,
}: {
x: number;
y: number;
+ type: string;
feature: unknown;
}) {
const { onHover, onClick } = this.layer.getLayerConfig();
- if (onHover) {
- onHover({
- x,
- y,
- feature,
- });
- }
- if (onClick) {
- onClick({
- x,
- y,
- feature,
- });
- }
+ // if (onHover) {
+ // onHover({
+ // x,
+ // y,
+ // feature,
+ // });
+ // }
+ // if (onClick) {
+ // onClick({
+ // x,
+ // y,
+ // feature,
+ // });
+ // }
+ this.layer.emit(type, {
+ x,
+ y,
+ feature,
+ });
}
/**
diff --git a/packages/core/src/services/source/ISourceService.ts b/packages/core/src/services/source/ISourceService.ts
index 7d547c2316..71d3ebbce9 100644
--- a/packages/core/src/services/source/ISourceService.ts
+++ b/packages/core/src/services/source/ISourceService.ts
@@ -61,6 +61,8 @@ export interface ISource {
data: IParserData;
cluster: boolean;
clusterOptions: Partial;
+ updateClusterData(zoom: number): void;
+ getFeatureById(id: number): unknown;
}
export interface IRasterCfg {
extent: [number, number, number, number];
diff --git a/packages/l7/package.json b/packages/l7/package.json
index 19812ad383..7f9876aa65 100644
--- a/packages/l7/package.json
+++ b/packages/l7/package.json
@@ -5,6 +5,7 @@
"main": "lib/index.js",
"module": "es/index.js",
"types": "es/index.d.ts",
+ "unpkg": "dist/l7.js",
"sideEffects": true,
"files": [
"dist",
diff --git a/packages/layers/src/core/BaseLayer.ts b/packages/layers/src/core/BaseLayer.ts
index 3874894474..d70a1f20be 100644
--- a/packages/layers/src/core/BaseLayer.ts
+++ b/packages/layers/src/core/BaseLayer.ts
@@ -1,6 +1,8 @@
import {
gl,
+ IActiveOption,
IAnimateOption,
+ IDataState,
IEncodeFeature,
IFontService,
IGlobalConfigService,
@@ -44,12 +46,10 @@ import mergeJsonSchemas from 'merge-json-schemas';
import { SyncBailHook, SyncHook, SyncWaterfallHook } from 'tapable';
import { normalizePasses } from '../plugins/MultiPassRendererPlugin';
import baseLayerSchema from './schema';
-
/**
* 分配 layer id
*/
let layerIdCounter = 0;
-const MapEventTypes = ['zoomchange', 'dragend', 'camerachange', 'resize'];
export default class BaseLayer extends EventEmitter
implements ILayer {
@@ -61,15 +61,19 @@ export default class BaseLayer extends EventEmitter
public maxZoom: number;
public inited: boolean = false;
public layerModelNeedUpdate: boolean = false;
- public dataPluginsState: { [key: string]: boolean } = {
- DataSource: false,
- DataMapping: false,
- FeatureScale: false,
- StyleAttr: false,
+ public pickedFeatureID: number = -1;
+
+ public dataState: IDataState = {
+ dataSourceNeedUpdate: false,
+ dataMappingNeedUpdate: false,
+ filterNeedUpdate: false,
+ featureScaleNeedUpdate: false,
+ StyleAttrNeedUpdate: false,
};
// 生命周期钩子
public hooks = {
init: new SyncBailHook(),
+ afterInit: new SyncBailHook(),
beforeRender: new SyncBailHook(),
beforeRenderData: new SyncWaterfallHook(['data']),
afterRender: new SyncHook(),
@@ -140,6 +144,8 @@ export default class BaseLayer extends EventEmitter
private rawConfig: Partial;
+ private needUpdateConfig: Partial;
+
/**
* 待更新样式属性,在初始化阶段完成注册
*/
@@ -165,11 +171,20 @@ export default class BaseLayer extends EventEmitter
public updateLayerConfig(
configToUpdate: Partial,
) {
- const sceneId = this.container.get(TYPES.SceneID);
- this.configService.setLayerConfig(sceneId, this.id, {
- ...this.configService.getLayerConfig(this.id),
- ...configToUpdate,
- });
+ if (!this.inited) {
+ this.needUpdateConfig = {
+ ...this.needUpdateConfig,
+ ...configToUpdate,
+ };
+ } else {
+ const sceneId = this.container.get(TYPES.SceneID);
+ this.configService.setLayerConfig(sceneId, this.id, {
+ ...this.configService.getLayerConfig(this.id),
+ ...this.needUpdateConfig,
+ ...configToUpdate,
+ });
+ this.needUpdateConfig = {};
+ }
}
/**
@@ -273,10 +288,11 @@ export default class BaseLayer extends EventEmitter
// 触发 init 生命周期插件
this.hooks.init.call();
+ this.inited = true;
+
+ this.hooks.afterInit.call();
this.buildModels();
-
- this.inited = true;
// 触发初始化完成事件;
this.emit('inited');
return this;
@@ -311,6 +327,7 @@ export default class BaseLayer extends EventEmitter
});
return this;
}
+ // 对mapping后的数据过滤,scale保持不变
public filter(
field: StyleAttributeField,
values?: StyleAttributeOption,
@@ -322,6 +339,7 @@ export default class BaseLayer extends EventEmitter
attributeValues: values,
updateOptions,
});
+ this.dataState.dataMappingNeedUpdate = true;
return this;
}
@@ -371,30 +389,6 @@ export default class BaseLayer extends EventEmitter
this.buildModels();
return this;
}
-
- public isSourceNeedUpdate() {
- const cluster = this.layerSource.cluster;
- if (cluster) {
- const { zoom = 0, bbox = [0, 0, 0, 0] } = this.layerSource.clusterOptions;
- const newZoom = this.mapService.getZoom();
- const bounds = this.mapService.getBounds();
- const newBbox: [number, number, number, number] = [
- bounds[0][0],
- bounds[0][1],
- bounds[1][0],
- bounds[1][1],
- ];
- // ||
- // bbox[0] !== newBbox[0] ||
- // bbox[2] !== newBbox[2]
- if (Math.abs(zoom - newZoom) > 1) {
- this.layerSource.updateClusterData(Math.floor(newZoom), newBbox);
- return true;
- }
- }
- return false;
- }
-
public style(options: object & Partial): ILayer {
const { passes, ...rest } = options;
@@ -442,11 +436,51 @@ export default class BaseLayer extends EventEmitter
return this;
}
+ public active(options: IActiveOption) {
+ this.updateLayerConfig({
+ enableHighlight: isObject(options) ? true : options,
+ highlightColor: isObject(options)
+ ? options.color
+ : this.getLayerConfig().highlightColor,
+ });
+ return this;
+ }
+ public setActive(
+ id: number | { x: number; y: number },
+ options?: IActiveOption,
+ ): void {
+ if (isObject(id)) {
+ const { x = 0, y = 0 } = id;
+ this.updateLayerConfig({
+ highlightColor: isObject(options)
+ ? options.color
+ : this.getLayerConfig().highlightColor,
+ });
+ this.pick({ x, y });
+ } else {
+ this.updateLayerConfig({
+ pickedFeatureID: id,
+ highlightColor: isObject(options)
+ ? options.color
+ : this.getLayerConfig().highlightColor,
+ });
+ }
+ }
+
+ public select(option: IActiveOption | false): ILayer {
+ return this;
+ }
+
+ public setSelect(
+ id: number | { x: number; y: number },
+ options?: IActiveOption,
+ ): void {
+ throw new Error('Method not implemented.');
+ }
public show(): ILayer {
this.updateLayerConfig({
visible: true,
});
- this.layerService.renderLayers();
return this;
}
@@ -454,7 +488,6 @@ export default class BaseLayer extends EventEmitter
this.updateLayerConfig({
visible: false,
});
- this.layerService.renderLayers();
return this;
}
@@ -491,13 +524,20 @@ export default class BaseLayer extends EventEmitter
/**
* zoom to layer Bounds
*/
- public fitBounds(): void {
+ public fitBounds(): ILayer {
+ if (!this.inited) {
+ this.updateLayerConfig({
+ autoFit: true,
+ });
+ return this;
+ }
const source = this.getSource();
const extent = source.extent;
this.mapService.fitBounds([
[extent[0], extent[1]],
[extent[2], extent[3]],
]);
+ return this;
}
public destroy() {
@@ -537,12 +577,7 @@ export default class BaseLayer extends EventEmitter
const bounds = this.mapService.getBounds();
const zoom = this.mapService.getZoom();
if (this.layerSource.cluster) {
- this.layerSource.updateClusterData(zoom, [
- bounds[0][0],
- bounds[0][1],
- bounds[1][0],
- bounds[1][1],
- ]);
+ this.layerSource.updateClusterData(zoom);
}
}
public getSource() {
@@ -643,11 +678,6 @@ export default class BaseLayer extends EventEmitter
};
}
- private registerMapEvent() {
- MapEventTypes.forEach((type) => {
- this.mapService.on(type, this.layerMapHander.bind(this, type));
- });
- }
private layerMapHander(type: string, data: any) {
this.emit(type, data);
}
diff --git a/packages/layers/src/heatmap/models/hexagon.ts b/packages/layers/src/heatmap/models/hexagon.ts
index 16df1f4102..11c9a6f473 100644
--- a/packages/layers/src/heatmap/models/hexagon.ts
+++ b/packages/layers/src/heatmap/models/hexagon.ts
@@ -45,29 +45,29 @@ export default class HexagonModel extends BaseModel {
}
protected registerBuiltinAttributes() {
// point layer size;
- this.styleAttributeService.registerStyleAttribute({
- name: 'size',
- type: AttributeType.Attribute,
- descriptor: {
- name: 'a_Size',
- buffer: {
- // give the WebGL driver a hint that this buffer may change
- usage: gl.DYNAMIC_DRAW,
- data: [],
- type: gl.FLOAT,
- },
- size: 1,
- update: (
- feature: IEncodeFeature,
- featureIdx: number,
- vertex: number[],
- attributeIdx: number,
- ) => {
- const { size } = feature;
- return Array.isArray(size) ? [size[0]] : [size as number];
- },
- },
- });
+ // this.styleAttributeService.registerStyleAttribute({
+ // name: 'size',
+ // type: AttributeType.Attribute,
+ // descriptor: {
+ // name: 'a_Size',
+ // buffer: {
+ // // give the WebGL driver a hint that this buffer may change
+ // usage: gl.DYNAMIC_DRAW,
+ // data: [],
+ // type: gl.FLOAT,
+ // },
+ // size: 1,
+ // update: (
+ // feature: IEncodeFeature,
+ // featureIdx: number,
+ // vertex: number[],
+ // attributeIdx: number,
+ // ) => {
+ // const { size } = feature;
+ // return Array.isArray(size) ? [size[0]] : [size as number];
+ // },
+ // },
+ // });
// point layer size;
this.styleAttributeService.registerStyleAttribute({
diff --git a/packages/layers/src/index.ts b/packages/layers/src/index.ts
index b1672cafaa..408d77b348 100644
--- a/packages/layers/src/index.ts
+++ b/packages/layers/src/index.ts
@@ -13,6 +13,7 @@ import ConfigSchemaValidationPlugin from './plugins/ConfigSchemaValidationPlugin
import DataMappingPlugin from './plugins/DataMappingPlugin';
import DataSourcePlugin from './plugins/DataSourcePlugin';
import FeatureScalePlugin from './plugins/FeatureScalePlugin';
+import LayerStylePlugin from './plugins/LayerStylePlugin';
import LightingPlugin from './plugins/LightingPlugin';
import MultiPassRendererPlugin from './plugins/MultiPassRendererPlugin';
import PixelPickingPlugin from './plugins/PixelPickingPlugin';
@@ -56,6 +57,15 @@ container
.bind(TYPES.ILayerPlugin)
.to(DataMappingPlugin)
.inRequestScope();
+
+/**
+ * 更新地图样式配置项 如active, show, hide
+ */
+container
+ .bind(TYPES.ILayerPlugin)
+ .to(LayerStylePlugin)
+ .inRequestScope();
+
/**
* 负责属性更新
*/
diff --git a/packages/layers/src/plugins/DataMappingPlugin.ts b/packages/layers/src/plugins/DataMappingPlugin.ts
index 6e12dd1ac2..bd7a897893 100644
--- a/packages/layers/src/plugins/DataMappingPlugin.ts
+++ b/packages/layers/src/plugins/DataMappingPlugin.ts
@@ -27,13 +27,13 @@ export default class DataMappingPlugin implements ILayerPlugin {
}: { styleAttributeService: IStyleAttributeService },
) {
layer.hooks.init.tap('DataMappingPlugin', () => {
- this.doMaping(layer, { styleAttributeService });
+ this.generateMaping(layer, { styleAttributeService });
});
layer.hooks.beforeRenderData.tap('DataMappingPlugin', (flag) => {
- if (flag) {
- layer.dataPluginsState.DataMapping = false;
- this.doMaping(layer, { styleAttributeService });
+ if (flag || layer.dataState.dataMappingNeedUpdate) {
+ layer.dataState.dataMappingNeedUpdate = false;
+ this.generateMaping(layer, { styleAttributeService });
return true;
}
return false;
@@ -52,7 +52,7 @@ export default class DataMappingPlugin implements ILayerPlugin {
}
});
}
- private doMaping(
+ private generateMaping(
layer: ILayer,
{
styleAttributeService,
@@ -62,6 +62,7 @@ export default class DataMappingPlugin implements ILayerPlugin {
const filter = styleAttributeService.getLayerStyleAttribute('filter');
const { dataArray } = layer.getSource().data;
let filterData = dataArray;
+ // 数据过滤完 在执行数据映射
if (filter?.scale) {
filterData = dataArray.filter((record: IParseDataItem) => {
return this.applyAttributeMapping(filter, record)[0];
diff --git a/packages/layers/src/plugins/DataSourcePlugin.ts b/packages/layers/src/plugins/DataSourcePlugin.ts
index 4db5efe3ba..e99b486125 100644
--- a/packages/layers/src/plugins/DataSourcePlugin.ts
+++ b/packages/layers/src/plugins/DataSourcePlugin.ts
@@ -1,10 +1,12 @@
-import { ILayer, ILayerPlugin } from '@antv/l7-core';
+import { ILayer, ILayerPlugin, IMapService, TYPES } from '@antv/l7-core';
import Source from '@antv/l7-source';
import { injectable } from 'inversify';
@injectable()
export default class DataSourcePlugin implements ILayerPlugin {
+ protected mapService: IMapService;
public apply(layer: ILayer) {
+ this.mapService = layer.getContainer().get(TYPES.IMapService);
layer.hooks.init.tap('DataSourcePlugin', () => {
const { data, options } = layer.sourceOption;
layer.setSource(new Source(data, options));
@@ -12,7 +14,12 @@ export default class DataSourcePlugin implements ILayerPlugin {
// 检测数据是不否需要更新
layer.hooks.beforeRenderData.tap('DataSourcePlugin', (flag) => {
- if (layer.isSourceNeedUpdate()) {
+ const source = layer.getSource();
+ const cluster = source.cluster;
+ const { zoom = 0, maxZoom = 16 } = source.clusterOptions;
+ const newZoom = this.mapService.getZoom();
+ if (cluster && Math.abs(zoom - newZoom) > 1 && maxZoom > zoom) {
+ source.updateClusterData(Math.floor(newZoom) + 1);
return true;
}
return false;
diff --git a/packages/layers/src/plugins/LayerStylePlugin.ts b/packages/layers/src/plugins/LayerStylePlugin.ts
new file mode 100644
index 0000000000..c6d3915604
--- /dev/null
+++ b/packages/layers/src/plugins/LayerStylePlugin.ts
@@ -0,0 +1,30 @@
+import { ILayer, ILayerPlugin, IMapService, TYPES } from '@antv/l7-core';
+import Source from '@antv/l7-source';
+import { injectable } from 'inversify';
+import { encodePickingColor, rgb2arr } from '../utils/color';
+@injectable()
+export default class LayerStylePlugin implements ILayerPlugin {
+ public apply(layer: ILayer) {
+ layer.hooks.afterInit.tap('LayerStylePlugin', () => {
+ layer.updateLayerConfig({});
+ const { autoFit } = layer.getLayerConfig();
+ if (autoFit) {
+ layer.fitBounds();
+ }
+ });
+
+ layer.hooks.beforeRender.tap('LayerStylePlugin', () => {
+ const {
+ highlightColor = 'red',
+ pickedFeatureID = -1,
+ } = layer.getLayerConfig();
+ layer.models.forEach((model) =>
+ model.addUniforms({
+ u_PickingStage: 2.0,
+ u_PickingColor: encodePickingColor(pickedFeatureID),
+ u_HighlightColor: rgb2arr(highlightColor as string),
+ }),
+ );
+ });
+ }
+}
diff --git a/packages/layers/src/plugins/PixelPickingPlugin.ts b/packages/layers/src/plugins/PixelPickingPlugin.ts
index 782043f322..3afd3f0387 100644
--- a/packages/layers/src/plugins/PixelPickingPlugin.ts
+++ b/packages/layers/src/plugins/PixelPickingPlugin.ts
@@ -8,15 +8,7 @@ import {
IStyleAttributeService,
} from '@antv/l7-core';
import { injectable } from 'inversify';
-import { rgb2arr } from '../utils/color';
-
-function encodePickingColor(featureIdx: number): [number, number, number] {
- return [
- (featureIdx + 1) & 255,
- ((featureIdx + 1) >> 8) & 255,
- (((featureIdx + 1) >> 8) >> 8) & 255,
- ];
-}
+import { encodePickingColor, rgb2arr } from '../utils/color';
const PickingStage = {
NONE: 0.0,
@@ -50,9 +42,13 @@ export default class PixelPickingPlugin implements ILayerPlugin {
},
size: 3,
// TODO: 固定 feature range 范围内的 pickingColor 都是固定的,可以生成 cache
- update: (feature: IEncodeFeature, featureIdx: number) =>
+ update: (feature: IEncodeFeature, featureIdx: number) => {
// 只有开启拾取才需要 encode
- enablePicking ? encodePickingColor(featureIdx) : [0, 0, 0],
+ const { id } = feature;
+ return enablePicking && layer.isVisible()
+ ? encodePickingColor(id as number)
+ : [0, 0, 0];
+ },
},
});
});
@@ -60,7 +56,7 @@ export default class PixelPickingPlugin implements ILayerPlugin {
// if (layer.multiPassRenderer) {
layer.hooks.beforePickingEncode.tap('PixelPickingPlugin', () => {
const { enablePicking } = layer.getLayerConfig();
- if (enablePicking) {
+ if (enablePicking && layer.isVisible()) {
layer.models.forEach((model) =>
model.addUniforms({
u_PickingStage: PickingStage.ENCODE,
@@ -71,7 +67,7 @@ export default class PixelPickingPlugin implements ILayerPlugin {
layer.hooks.afterPickingEncode.tap('PixelPickingPlugin', () => {
const { enablePicking } = layer.getLayerConfig();
- if (enablePicking) {
+ if (enablePicking && layer.isVisible()) {
layer.models.forEach((model) =>
model.addUniforms({
u_PickingStage: PickingStage.NONE,
diff --git a/packages/layers/src/utils/color.ts b/packages/layers/src/utils/color.ts
index 20cbcff16e..f366874639 100644
--- a/packages/layers/src/utils/color.ts
+++ b/packages/layers/src/utils/color.ts
@@ -15,6 +15,16 @@ export function rgb2arr(str: string) {
return arr;
}
+export function encodePickingColor(
+ featureIdx: number,
+): [number, number, number] {
+ return [
+ (featureIdx + 1) & 255,
+ ((featureIdx + 1) >> 8) & 255,
+ (((featureIdx + 1) >> 8) >> 8) & 255,
+ ];
+}
+
export function generateColorRamp(colorRamp: IColorRamp): ImageData {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d') as CanvasRenderingContext2D;
diff --git a/packages/source/__tests__/source.spec.ts b/packages/source/__tests__/source.spec.ts
index 047c150766..baec235c98 100644
--- a/packages/source/__tests__/source.spec.ts
+++ b/packages/source/__tests__/source.spec.ts
@@ -20,6 +20,6 @@ describe('source constructor', () => {
field: 'mag',
},
});
- source.updateClusterData(2, [10, 0, 130, 75]);
+ source.updateClusterData(2);
});
});
diff --git a/packages/source/src/index.ts b/packages/source/src/index.ts
index b4f693a4ca..4d8e363280 100644
--- a/packages/source/src/index.ts
+++ b/packages/source/src/index.ts
@@ -6,8 +6,11 @@ import json from './parser/json';
import raster from './parser/raster';
import Source from './source';
import { cluster } from './transform/cluster';
+import { filter } from './transform/filter';
import { aggregatorToGrid } from './transform/grid';
import { pointToHexbin } from './transform/hexagon';
+import { join } from './transform/join';
+import { map } from './transform/map';
export default Source;
registerParser('geojson', geojson);
registerParser('image', image);
@@ -15,6 +18,9 @@ registerParser('csv', csv);
registerParser('json', json);
registerParser('raster', raster);
registerTransform('cluster', cluster);
+registerTransform('filter', filter);
+registerTransform('join', join);
+registerTransform('map', map);
registerTransform('grid', aggregatorToGrid);
registerTransform('hexagon', pointToHexbin);
export {
diff --git a/packages/source/src/source.ts b/packages/source/src/source.ts
index 09dfd27f90..fc7210ce77 100644
--- a/packages/source/src/source.ts
+++ b/packages/source/src/source.ts
@@ -1,9 +1,12 @@
import {
IClusterOptions,
+ IMapService,
IParserCfg,
IParserData,
ISourceCFG,
ITransform,
+ lazyInject,
+ TYPES,
} from '@antv/l7-core';
import { extent } from '@antv/l7-utils';
import {
@@ -14,13 +17,13 @@ import {
Properties,
} from '@turf/helpers';
import { EventEmitter } from 'eventemitter3';
+import { Container } from 'inversify';
import { cloneDeep, isFunction, isString } from 'lodash';
import Supercluster from 'supercluster';
import { SyncHook } from 'tapable';
import { getParser, getTransform } from './';
import { statMap } from './utils/statistics';
import { getColumn } from './utils/util';
-
export default class Source extends EventEmitter {
public data: IParserData;
@@ -82,13 +85,9 @@ export default class Source extends EventEmitter {
this.init();
}
- public updateClusterData(
- zoom: number,
- bbox: [number, number, number, number],
- ): void {
+ public updateClusterData(zoom: number): void {
const { method = 'sum', field } = this.clusterOptions;
- let data = this.clusterIndex.getClusters(bbox, zoom);
- this.clusterOptions.bbox = bbox;
+ let data = this.clusterIndex.getClusters(this.extent, zoom);
this.clusterOptions.zoom = zoom;
data.forEach((p) => {
if (!p.id) {
@@ -122,6 +121,16 @@ export default class Source extends EventEmitter {
});
this.executeTrans();
}
+ public getFeatureById(id: number): unknown {
+ const { type = 'geojson' } = this.parser;
+ if (type === 'geojson') {
+ return id < this.rawData.features.length
+ ? this.rawData.features[id]
+ : 'null';
+ } else {
+ return id < this.data.dataArray.length ? this.data.dataArray[id] : 'null';
+ }
+ }
private excuteParser(): void {
const parser = this.parser;
diff --git a/packages/source/src/transform/filter.ts b/packages/source/src/transform/filter.ts
new file mode 100644
index 0000000000..9a0b50a127
--- /dev/null
+++ b/packages/source/src/transform/filter.ts
@@ -0,0 +1,8 @@
+import { IParserData } from '@antv/l7-core';
+export function filter(data: IParserData, options: { [key: string]: any }) {
+ const { callback } = options;
+ if (callback) {
+ data.dataArray = data.dataArray.filter(callback);
+ }
+ return data;
+}
diff --git a/packages/source/src/transform/join.ts b/packages/source/src/transform/join.ts
new file mode 100644
index 0000000000..023b1e74c4
--- /dev/null
+++ b/packages/source/src/transform/join.ts
@@ -0,0 +1,28 @@
+import { IParseDataItem, IParserData } from '@antv/l7-core';
+
+interface IJoinOption {
+ field: 'string';
+ data: any[];
+}
+
+/**
+ *
+ * @param data
+ * @param options
+ */
+export function join(geoData: IParserData, options: { [key: string]: any }) {
+ const { field, data } = options;
+ const dataObj: { [key: string]: any } = {};
+ data.forEach((element: { [key: string]: any }) => {
+ dataObj[element.field] = element;
+ });
+ geoData.dataArray = data.dataArray.map((item: IParseDataItem) => {
+ const joinName = item[field];
+ return {
+ ...dataObj[joinName],
+ ...item,
+ };
+ });
+
+ return data;
+}
diff --git a/packages/source/src/transform/map.ts b/packages/source/src/transform/map.ts
new file mode 100644
index 0000000000..2aedb29814
--- /dev/null
+++ b/packages/source/src/transform/map.ts
@@ -0,0 +1,9 @@
+import { IParserData } from '@antv/l7-core';
+type CallBack = (...args: any[]) => any;
+export function map(data: IParserData, options: { [key: string]: any }) {
+ const { callback } = options;
+ if (callback) {
+ data.dataArray = data.dataArray.map(callback);
+ }
+ return data;
+}
diff --git a/stories/Components/Components.stories.tsx b/stories/Components/Components.stories.tsx
index 405bf6cbba..0c7bb0b105 100644
--- a/stories/Components/Components.stories.tsx
+++ b/stories/Components/Components.stories.tsx
@@ -1,5 +1,6 @@
import { storiesOf } from '@storybook/react';
import * as React from 'react';
+import Chart from './components/chart';
import Marker from './components/Marker';
import Popup from './components/Popup';
import Scale from './components/Scale';
@@ -9,4 +10,5 @@ storiesOf('UI 组件', module)
.add('Zoom', () => )
.add('Scale', () => )
.add('Marker', () => )
+ .add('Chart', () => )
.add('Popup', () => );
diff --git a/stories/Components/components/chart.tsx b/stories/Components/components/chart.tsx
new file mode 100644
index 0000000000..095643fe4c
--- /dev/null
+++ b/stories/Components/components/chart.tsx
@@ -0,0 +1,108 @@
+// @ts-ignore
+import * as G2 from '@antv/g2';
+import { Marker, Scene } from '@antv/l7';
+import { GaodeMap, Mapbox } from '@antv/l7-maps';
+import * as React from 'react';
+
+export default class ChartComponent extends React.Component {
+ private scene: Scene;
+
+ public componentWillUnmount() {
+ this.scene.destroy();
+ }
+
+ public async componentDidMount() {
+ const scene = new Scene({
+ id: 'map',
+ map: new GaodeMap({
+ pitch: 0,
+ style: 'dark',
+ center: [52.21496184144132, 24.121126851768906],
+ zoom: 3.802,
+ }),
+ });
+ addChart();
+ scene.render();
+ function addChart() {
+ Promise.all([
+ fetch(
+ 'https://gw.alipayobjects.com/os/basement_prod/5b772136-a1f4-4fc5-9a80-9f9974b4b182.json',
+ ).then((d) => d.json()),
+ fetch(
+ 'https://gw.alipayobjects.com/os/basement_prod/f3c467a4-9ae0-4f08-bb5f-11f9c869b2cb.json',
+ ).then((d) => d.json()),
+ ]).then(function onLoad([center, population]) {
+ const popobj: { [key: string]: any } = {};
+ population.forEach((element: any) => {
+ popobj[element.Code] =
+ element['Population, female (% of total) (% of total)'];
+ });
+ // 数据绑定
+
+ center.features = center.features.map((fe: any) => {
+ fe.properties.female = popobj[fe.properties.id] * 1 || 0;
+ return fe;
+ });
+ center.features.forEach((point: any) => {
+ const el = document.createElement('div');
+ const coord = point.geometry.coordinates;
+ const v = (point.properties.female * 1) as number;
+ if (v < 1 || (v > 46 && v < 54)) {
+ return;
+ }
+ const size = 60;
+ const data = [
+ {
+ type: '男性',
+ value: 100.0 - Number(v.toFixed(2)),
+ },
+ {
+ type: '女性',
+ value: v.toFixed(2),
+ },
+ ];
+ const chart = new G2.Chart({
+ container: el,
+ width: size,
+ height: size,
+ // render: 'svg',
+ padding: 0,
+ });
+ chart.source(data);
+ chart.legend(false);
+ chart.tooltip(false);
+ chart.coord('theta', {
+ radius: 0.9,
+ innerRadius: 0.6,
+ });
+ chart
+ .intervalStack()
+ .position('value')
+ .color('type', ['#5CCEA1', '#5B8FF9'])
+ .opacity(1);
+ chart.render();
+ const marker = new Marker({ element: el }).setLnglat({
+ lng: coord[0],
+ lat: coord[1],
+ });
+ scene.addMarker(marker);
+ });
+ });
+ }
+ }
+
+ public render() {
+ return (
+
+ );
+ }
+}
diff --git a/stories/Layers/Layers.stories.tsx b/stories/Layers/Layers.stories.tsx
index c8af5a793c..3800e9eae3 100644
--- a/stories/Layers/Layers.stories.tsx
+++ b/stories/Layers/Layers.stories.tsx
@@ -5,7 +5,6 @@ import ArcLineDemo from './components/Arcline';
import Column from './components/column';
import DataUpdate from './components/data_update';
import HeatMapDemo from './components/HeatMap';
-import GridHeatMap from './components/HeatmapGrid';
import LineLayer from './components/Line';
import PointDemo from './components/Point';
import Point3D from './components/Point3D';
@@ -25,7 +24,6 @@ storiesOf('图层', module)
.add('线图层', () => )
.add('3D弧线', () => )
.add('2D弧线', () => )
- .add('网格热力图', () => )
.add('热力图', () => )
.add('栅格', () => )
.add('图片', () => );
diff --git a/stories/Layers/components/HeatmapGrid.tsx b/stories/Layers/components/HeatmapGrid.tsx
deleted file mode 100644
index 82a48d4c43..0000000000
--- a/stories/Layers/components/HeatmapGrid.tsx
+++ /dev/null
@@ -1,85 +0,0 @@
-import { HeatmapLayer, Scene } from '@antv/l7';
-import { GaodeMap } from '@antv/l7-maps';
-import * as React from 'react';
-
-export default class GridHeatMap extends React.Component {
- // @ts-ignore
- private scene: Scene;
-
- public componentWillUnmount() {
- this.scene.destroy();
- }
-
- public async componentDidMount() {
- const response = await fetch(
- 'https://gw.alipayobjects.com/os/basement_prod/7359a5e9-3c5e-453f-b207-bc892fb23b84.csv',
- );
- const data = await response.text();
- const scene = new Scene({
- id: 'map',
- map: new GaodeMap({
- style: 'dark',
- pitch: 0,
- center: [110.097892, 33.853662],
- zoom: 4.056,
- }),
- });
- const layer = new HeatmapLayer({
- enablePicking: true,
- enableHighlight: true,
- })
- .source(data, {
- parser: {
- type: 'csv',
- x: 'lng',
- y: 'lat',
- },
- transforms: [
- {
- type: 'grid',
- size: 10000,
- field: 'v',
- method: 'sum',
- },
- ],
- })
- .size('count', (value) => {
- return value * 0;
- })
- .shape('square')
- .style({
- coverage: 1,
- angle: 0,
- })
- .color(
- 'count',
- [
- '#FF3417',
- '#FF7412',
- '#FFB02A',
- '#FFE754',
- '#46F3FF',
- '#02BEFF',
- '#1A7AFF',
- '#0A1FB2',
- ].reverse(),
- );
- scene.addLayer(layer);
- this.scene = scene;
- }
-
- public render() {
- return (
-
- );
- }
-}
diff --git a/stories/Layers/components/Point.tsx b/stories/Layers/components/Point.tsx
index 5586e9087d..10ce3b4e0f 100644
--- a/stories/Layers/components/Point.tsx
+++ b/stories/Layers/components/Point.tsx
@@ -36,9 +36,6 @@ export default class Point3D extends React.Component {
.scale('point_count', {
type: 'quantile',
})
- .filter('point_count', (point_count: number) => {
- return point_count > 1;
- })
.size('point_count', [5, 10, 15, 20, 25])
.color('red')
.style({
@@ -46,7 +43,6 @@ export default class Point3D extends React.Component {
strokeWidth: 1,
});
scene.addLayer(pointLayer);
- console.log(pointLayer);
}
public render() {
diff --git a/stories/Picking/components/AdvancedAPI.tsx b/stories/Picking/components/AdvancedAPI.tsx
index 5c63622f46..5dfbe984c0 100644
--- a/stories/Picking/components/AdvancedAPI.tsx
+++ b/stories/Picking/components/AdvancedAPI.tsx
@@ -38,13 +38,18 @@ export default class AdvancedAPI extends React.Component {
highlightColor: [0, 0, 1, 1],
onHover: (pickedFeature) => {
// tslint:disable-next-line:no-console
- console.log(pickedFeature);
+ },
+ onClick: (pickedFeature) => {
+ // tslint:disable-next-line:no-console
},
});
layer
.source(await response.json())
.size('name', [0, 10000, 50000, 30000, 100000])
+ .active({
+ color: 'red',
+ })
.color('name', [
'#2E8AE6',
'#69D1AB',
@@ -58,6 +63,9 @@ export default class AdvancedAPI extends React.Component {
opacity: 0.8,
});
scene.addLayer(layer);
+ layer.on('click', (e) => {
+ console.log(e);
+ });
this.scene = scene;
@@ -67,9 +75,10 @@ export default class AdvancedAPI extends React.Component {
const styleOptions = {
enablePicking: true,
enableHighlight: true,
- highlightColor: [0, 0, 255],
+ highlightColor: [1, 0, 0],
pickingX: window.innerWidth / 2,
pickingY: window.innerHeight / 2,
+ visible: true,
};
const pointFolder = gui.addFolder('非鼠标 hover 交互');
pointFolder
@@ -80,23 +89,30 @@ export default class AdvancedAPI extends React.Component {
});
scene.render();
});
+ pointFolder.add(styleOptions, 'visible').onChange((visible: boolean) => {
+ layer.style({
+ visible,
+ });
+ scene.render();
+ });
pointFolder
.add(styleOptions, 'pickingX', 0, window.innerWidth)
.onChange((pickingX: number) => {
- layer.pick({ x: pickingX, y: styleOptions.pickingY });
+ layer.setActive({ x: pickingX, y: styleOptions.pickingY });
});
pointFolder
.add(styleOptions, 'pickingY', 0, window.innerHeight)
.onChange((pickingY: number) => {
- layer.pick({ x: styleOptions.pickingX, y: pickingY });
+ layer.setActive({ x: styleOptions.pickingX, y: pickingY });
});
pointFolder
.addColor(styleOptions, 'highlightColor')
.onChange((highlightColor: number[]) => {
const [r, g, b] = highlightColor.map((c) => c / 255);
- layer.style({
- highlightColor: [r, g, b, 1],
- });
+ layer.setActive(
+ { x: styleOptions.pickingX, y: styleOptions.pickingY },
+ { color: [r, g, b, 1] },
+ );
scene.render();
});
pointFolder.open();