From ef38a167019fe8c55a49fe1ceb2e966732f883ab Mon Sep 17 00:00:00 2001 From: lvisei Date: Thu, 19 May 2022 10:25:45 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E5=BC=8F=E6=A0=B8=E5=BF=83=E5=9B=BE=E5=B1=82=20(#171)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: core layer * test: dot layer * feat: text core layer * test: layer group * refactor: update data * fix: layer id number * test: layer group * refactor: layer options * test: scatter layer * chore: default value * docs: composite layer * build: config rollup --- package.json | 2 + packages/composite-layers/README.md | 2 +- .../composite-layers/area-layer/index.test.ts | 139 +++++ .../scatter-layer/index.test.ts | 136 +++++ .../core-layers/heatmap-layer/index.test.ts | 54 ++ .../unit/core-layers/line-layer/index.test.ts | 48 ++ .../point-layer}/index.test.ts | 8 +- .../core-layers/polygon-layer/index.test.ts | 52 ++ .../unit/core-layers/text-layer/index.test.ts | 67 +++ .../__tests__/unit/core/layer-group.test.ts | 17 +- .../unit/layers/area-layer/index.test.ts | 83 --- packages/composite-layers/package.json | 6 +- packages/composite-layers/rollup.config.js | 29 + .../src/adaptor/attribute/index.ts | 36 +- .../src/adaptor/source/index.ts | 28 +- .../composite-layers/area-layer/adaptor.ts | 57 ++ .../area-layer/constants.ts | 23 +- .../src/composite-layers/area-layer/index.ts | 522 ++++++++++++++++++ .../src/composite-layers/area-layer/types.ts | 77 +++ .../composite-layers/scatter-layer/adaptor.ts | 57 ++ .../scatter-layer/constants.ts | 44 ++ .../composite-layers/scatter-layer/index.ts | 493 +++++++++++++++++ .../composite-layers/scatter-layer/types.ts | 61 ++ .../src/core-layers/heatmap-layer/index.ts | 25 + .../src/core-layers/heatmap-layer/types.ts | 74 +++ .../src/core-layers/line-layer/index.ts | 25 + .../src/core-layers/line-layer/types.ts | 59 ++ .../src/core-layers/point-layer/index.ts | 25 + .../src/core-layers/point-layer/types.ts | 95 ++++ .../src/core-layers/polygon-layer/index.ts | 25 + .../src/core-layers/polygon-layer/types.ts | 27 + .../src/core-layers/text-layer/index.ts | 36 ++ .../src/core-layers/text-layer/types.ts | 31 ++ .../src/core/composite-layer.ts | 94 +--- .../composite-layers/src/core/core-layer.ts | 334 +++++++++++ .../composite-layers/src/core/layer-group.ts | 32 +- packages/composite-layers/src/index.ts | 16 +- .../src/layers/area-layer/adaptor.ts | 155 ------ .../src/layers/area-layer/index.ts | 309 ----------- .../src/layers/area-layer/types.ts | 60 -- .../src/layers/dot-layer/adaptor.ts | 24 - .../src/layers/dot-layer/constants.ts | 38 -- .../src/layers/dot-layer/index.ts | 85 --- .../src/layers/dot-layer/types.ts | 9 - packages/composite-layers/src/types/attr.ts | 51 +- packages/composite-layers/src/types/common.ts | 19 +- packages/composite-layers/src/types/index.ts | 5 +- .../composite-layers/src/types/interface.ts | 61 -- .../src/types/layer-category.ts | 7 + .../src/types/layer-config.ts | 340 ------------ .../src/types/layer-interface.ts | 106 ++++ .../composite-layers/src/types/layer-type.ts | 18 - .../stories/advanced-plot/index.stories.tsx | 2 +- .../area-layer/ChinaCitys.tsx | 97 ++++ .../area-layer/index.stories.tsx | 5 + .../scatter-layer/Earthquake.tsx | 111 ++++ .../scatter-layer/index.stories.tsx | 5 + .../stories/plots/area/index.stories.tsx | 2 +- .../plots/choropleth/index.stories.tsx | 2 +- .../stories/plots/clustere/index.stories.tsx | 2 +- .../plots/dot-density/index.stories.tsx | 2 +- storybook/stories/plots/dot/index.stories.tsx | 2 +- .../plots/dot/scatter/index.stories.tsx | 2 +- .../stories/plots/flow/index.stories.tsx | 2 +- .../stories/plots/grid/index.stories.tsx | 2 +- .../stories/plots/heatmap/index.stories.tsx | 2 +- .../stories/plots/hexbin/index.stories.tsx | 2 +- .../stories/plots/path/index.stories.tsx | 2 +- 68 files changed, 3105 insertions(+), 1363 deletions(-) create mode 100644 packages/composite-layers/__tests__/unit/composite-layers/area-layer/index.test.ts create mode 100644 packages/composite-layers/__tests__/unit/composite-layers/scatter-layer/index.test.ts create mode 100644 packages/composite-layers/__tests__/unit/core-layers/heatmap-layer/index.test.ts create mode 100644 packages/composite-layers/__tests__/unit/core-layers/line-layer/index.test.ts rename packages/composite-layers/__tests__/unit/{layers/dot-layer => core-layers/point-layer}/index.test.ts (87%) create mode 100644 packages/composite-layers/__tests__/unit/core-layers/polygon-layer/index.test.ts create mode 100644 packages/composite-layers/__tests__/unit/core-layers/text-layer/index.test.ts delete mode 100644 packages/composite-layers/__tests__/unit/layers/area-layer/index.test.ts create mode 100644 packages/composite-layers/rollup.config.js create mode 100644 packages/composite-layers/src/composite-layers/area-layer/adaptor.ts rename packages/composite-layers/src/{layers => composite-layers}/area-layer/constants.ts (56%) create mode 100644 packages/composite-layers/src/composite-layers/area-layer/index.ts create mode 100644 packages/composite-layers/src/composite-layers/area-layer/types.ts create mode 100644 packages/composite-layers/src/composite-layers/scatter-layer/adaptor.ts create mode 100644 packages/composite-layers/src/composite-layers/scatter-layer/constants.ts create mode 100644 packages/composite-layers/src/composite-layers/scatter-layer/index.ts create mode 100644 packages/composite-layers/src/composite-layers/scatter-layer/types.ts create mode 100644 packages/composite-layers/src/core-layers/heatmap-layer/index.ts create mode 100644 packages/composite-layers/src/core-layers/heatmap-layer/types.ts create mode 100644 packages/composite-layers/src/core-layers/line-layer/index.ts create mode 100644 packages/composite-layers/src/core-layers/line-layer/types.ts create mode 100644 packages/composite-layers/src/core-layers/point-layer/index.ts create mode 100644 packages/composite-layers/src/core-layers/point-layer/types.ts create mode 100644 packages/composite-layers/src/core-layers/polygon-layer/index.ts create mode 100644 packages/composite-layers/src/core-layers/polygon-layer/types.ts create mode 100644 packages/composite-layers/src/core-layers/text-layer/index.ts create mode 100644 packages/composite-layers/src/core-layers/text-layer/types.ts create mode 100644 packages/composite-layers/src/core/core-layer.ts delete mode 100644 packages/composite-layers/src/layers/area-layer/adaptor.ts delete mode 100644 packages/composite-layers/src/layers/area-layer/index.ts delete mode 100644 packages/composite-layers/src/layers/area-layer/types.ts delete mode 100644 packages/composite-layers/src/layers/dot-layer/adaptor.ts delete mode 100644 packages/composite-layers/src/layers/dot-layer/constants.ts delete mode 100644 packages/composite-layers/src/layers/dot-layer/index.ts delete mode 100644 packages/composite-layers/src/layers/dot-layer/types.ts delete mode 100644 packages/composite-layers/src/types/interface.ts create mode 100644 packages/composite-layers/src/types/layer-category.ts delete mode 100644 packages/composite-layers/src/types/layer-config.ts create mode 100644 packages/composite-layers/src/types/layer-interface.ts delete mode 100644 packages/composite-layers/src/types/layer-type.ts create mode 100644 storybook/stories/composite-layers/area-layer/ChinaCitys.tsx create mode 100644 storybook/stories/composite-layers/area-layer/index.stories.tsx create mode 100644 storybook/stories/composite-layers/scatter-layer/Earthquake.tsx create mode 100644 storybook/stories/composite-layers/scatter-layer/index.stories.tsx diff --git a/package.json b/package.json index 49a0b94f5..3cf13e0d4 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "bootstrap": "yarn", "dev": "lerna run dev --stream --parallel --no-bail --ignore=l7plot-website", "dev-l7lot": "lerna run dev --stream --scope=@antv/l7plot", + "dev-composite-layers": "lerna run dev --stream --scope=@antv/l7-composite-layers", "dev-component": "lerna run dev --stream --scope=@antv/l7plot-component", "dev-storybook": "lerna run dev --stream --scope=l7plot-storybook", "dev-website": "lerna run dev --stream --scope=l7plot-website", @@ -24,6 +25,7 @@ "test-cover": "jest --coverage", "build": "lerna run build --stream --scope=@antv/l7plot*", "build-l7lot": "lerna run build --stream --scope=@antv/l7plot", + "build-composite-layers": "lerna run build --stream --scope=@antv/l7-composite-layers", "build-component": "lerna run build --stream --scope=@antv/l7plot-component", "build-website": "lerna run build --stream --scope=l7plot-website", "bundle": "lerna run build:umd --stream --scope=@antv/l7plot", diff --git a/packages/composite-layers/README.md b/packages/composite-layers/README.md index a8ee773b5..f5ecb32c2 100644 --- a/packages/composite-layers/README.md +++ b/packages/composite-layers/README.md @@ -1,5 +1,5 @@

Composite Layers

-Composite layers for @antv/l7、@antv/l7plot、@antv/dipper. +Composite layers for @antv/l7、@antv/l7plot、@antv/larkmap、@antv/dipper.
diff --git a/packages/composite-layers/__tests__/unit/composite-layers/area-layer/index.test.ts b/packages/composite-layers/__tests__/unit/composite-layers/area-layer/index.test.ts new file mode 100644 index 000000000..3b407ca8d --- /dev/null +++ b/packages/composite-layers/__tests__/unit/composite-layers/area-layer/index.test.ts @@ -0,0 +1,139 @@ +import { getLayerStyleAttribute } from '../../../helper/layer'; +import { AreaLayer } from '../../../../src/composite-layers/area-layer'; + +describe('area layer', () => { + const layer = new AreaLayer({ + source: { data: [] }, + fillColor: { + field: 'adcode', + value: ['rgb(239,243,255)', 'rgb(189,215,231)', 'rgb(8,81,156)'], + }, + opacity: 1, + strokeColor: 'rgb(93,112,146)', + lineWidth: 0.6, + lineOpacity: 1, + label: { + field: 'label', + style: { + fill: '#fff', + opacity: 0.6, + fontSize: 12, + textAnchor: 'top', + textOffset: [0, 20], + spacing: 1, + padding: [5, 5], + stroke: '#ffffff', + strokeWidth: 0.3, + strokeOpacity: 1.0, + }, + }, + state: { + active: { + fillColor: 'red', + strokeColor: 'green', + lineWidth: 1.5, + lineOpacity: 0.8, + }, + select: { + fillColor: 'red', + strokeColor: 'yellow', + lineWidth: 1.5, + lineOpacity: 0.8, + }, + }, + }); + + it('type', () => { + expect(layer.type).toBe('areaLayer'); + expect(layer.fillLayer.type).toBe('polygonLayer'); + expect(layer.strokeLayer.type).toBe('lineLayer'); + expect(layer.highlightStrokeLayer.type).toBe('lineLayer'); + expect(layer.selectFillLayer.type).toBe('polygonLayer'); + expect(layer.selectStrokeLayer.type).toBe('lineLayer'); + expect(layer.labelLayer.type).toBe('textLayer'); + }); + + it('color', () => { + expect(getLayerStyleAttribute(layer.fillLayer.layer['pendingStyleAttributes'], 'color')).toEqual({ + attributeName: 'color', + attributeField: 'adcode', + attributeValues: ['rgb(239,243,255)', 'rgb(189,215,231)', 'rgb(8,81,156)'], + }); + }); + + it('shape', () => { + expect(getLayerStyleAttribute(layer.fillLayer.layer['pendingStyleAttributes'], 'shape')).toEqual({ + attributeName: 'shape', + attributeField: 'fill', + }); + expect(getLayerStyleAttribute(layer.strokeLayer.layer['pendingStyleAttributes'], 'shape')).toEqual({ + attributeName: 'shape', + attributeField: 'line', + }); + }); + + it('label', () => { + expect(getLayerStyleAttribute(layer.labelLayer.layer['pendingStyleAttributes'], 'size')).toEqual({ + attributeName: 'size', + attributeField: 12, + }); + expect(getLayerStyleAttribute(layer.labelLayer.layer['pendingStyleAttributes'], 'color')).toEqual({ + attributeName: 'color', + attributeField: '#fff', + }); + expect(getLayerStyleAttribute(layer.labelLayer.layer['pendingStyleAttributes'], 'shape')).toEqual({ + attributeName: 'shape', + attributeField: 'label', + attributeValues: 'text', + }); + expect(layer.labelLayer.layer['rawConfig']).toMatchObject({ + opacity: 0.6, + textAnchor: 'top', + textOffset: [0, 20], + spacing: 1, + padding: [5, 5], + stroke: '#ffffff', + strokeWidth: 0.3, + strokeOpacity: 1.0, + }); + }); + + it('style', () => { + expect(layer.fillLayer.layer['rawConfig']).toMatchObject({ opacity: 1 }); + + expect(layer.strokeLayer.layer['rawConfig']).toMatchObject({ opacity: 1 }); + expect(getLayerStyleAttribute(layer.strokeLayer.layer['pendingStyleAttributes'], 'color')).toEqual({ + attributeName: 'color', + attributeField: 'rgb(93,112,146)', + }); + expect(getLayerStyleAttribute(layer.strokeLayer.layer['pendingStyleAttributes'], 'size')).toEqual({ + attributeName: 'size', + attributeField: 0.6, + }); + }); + + it('state', () => { + expect(layer.fillLayer.layer['needUpdateConfig'].enableHighlight).toBeTruthy(); + expect(layer.fillLayer.layer['needUpdateConfig'].enableSelect).toBeFalsy(); + + expect(getLayerStyleAttribute(layer.highlightStrokeLayer.layer['pendingStyleAttributes'], 'color')).toEqual({ + attributeName: 'color', + attributeField: 'green', + }); + expect(getLayerStyleAttribute(layer.highlightStrokeLayer.layer['pendingStyleAttributes'], 'size')).toEqual({ + attributeName: 'size', + attributeField: 1.5, + }); + expect(layer.highlightStrokeLayer.layer['rawConfig']).toMatchObject({ opacity: 0.8 }); + + expect(getLayerStyleAttribute(layer.selectStrokeLayer.layer['pendingStyleAttributes'], 'color')).toEqual({ + attributeName: 'color', + attributeField: 'yellow', + }); + expect(getLayerStyleAttribute(layer.selectStrokeLayer.layer['pendingStyleAttributes'], 'size')).toEqual({ + attributeName: 'size', + attributeField: 1.5, + }); + expect(layer.selectStrokeLayer.layer['rawConfig']).toMatchObject({ opacity: 0.8 }); + }); +}); diff --git a/packages/composite-layers/__tests__/unit/composite-layers/scatter-layer/index.test.ts b/packages/composite-layers/__tests__/unit/composite-layers/scatter-layer/index.test.ts new file mode 100644 index 000000000..9f439171f --- /dev/null +++ b/packages/composite-layers/__tests__/unit/composite-layers/scatter-layer/index.test.ts @@ -0,0 +1,136 @@ +import { getLayerStyleAttribute } from '../../../helper/layer'; +import { ScatterLayer } from '../../../../src/composite-layers/scatter-layer'; + +describe('scatter layer', () => { + const layer = new ScatterLayer({ + source: { data: [], parser: { type: 'json', x: 'x', y: 'y' } }, + radius: 12, + // dotType: 'circle', + fillColor: '#fff', + opacity: 1, + strokeColor: 'red', + lineOpacity: 0.8, + lineWidth: 1, + label: { + field: 'label', + style: { + fill: '#fff', + opacity: 0.6, + fontSize: 12, + textAnchor: 'top', + textOffset: [0, 20], + spacing: 1, + padding: [5, 5], + stroke: '#ffffff', + strokeWidth: 0.3, + strokeOpacity: 1.0, + }, + }, + state: { + active: { + fillColor: 'red', + strokeColor: 'green', + lineWidth: 1.5, + lineOpacity: 0.8, + }, + select: { + fillColor: 'red', + strokeColor: 'yellow', + lineWidth: 1.5, + lineOpacity: 0.8, + }, + }, + }); + + it('type', () => { + expect(layer.type).toBe('scatterLayer'); + expect(layer.fillLayer.type).toBe('pointLayer'); + expect(layer.highlightStrokeLayer.type).toBe('pointLayer'); + expect(layer.selectFillLayer.type).toBe('pointLayer'); + expect(layer.selectStrokeLayer.type).toBe('pointLayer'); + expect(layer.labelLayer.type).toBe('textLayer'); + }); + + it('size', () => { + expect(getLayerStyleAttribute(layer.fillLayer.layer['pendingStyleAttributes'], 'size')).toEqual({ + attributeName: 'size', + attributeField: 12, + }); + }); + + it('color', () => { + expect(getLayerStyleAttribute(layer.fillLayer.layer['pendingStyleAttributes'], 'color')).toEqual({ + attributeName: 'color', + attributeField: '#fff', + }); + }); + + it('shape', () => { + expect(getLayerStyleAttribute(layer.fillLayer.layer['pendingStyleAttributes'], 'shape')).toEqual({ + attributeName: 'shape', + attributeField: 'circle', + }); + }); + + it('label', () => { + expect(getLayerStyleAttribute(layer.labelLayer.layer['pendingStyleAttributes'], 'size')).toEqual({ + attributeName: 'size', + attributeField: 12, + }); + expect(getLayerStyleAttribute(layer.labelLayer.layer['pendingStyleAttributes'], 'color')).toEqual({ + attributeName: 'color', + attributeField: '#fff', + }); + expect(getLayerStyleAttribute(layer.labelLayer.layer['pendingStyleAttributes'], 'shape')).toEqual({ + attributeName: 'shape', + attributeField: 'label', + attributeValues: 'text', + }); + expect(layer.labelLayer.layer['rawConfig']).toMatchObject({ + opacity: 0.6, + textAnchor: 'top', + textOffset: [0, 20], + spacing: 1, + padding: [5, 5], + stroke: '#ffffff', + strokeWidth: 0.3, + strokeOpacity: 1.0, + }); + }); + + it('style', () => { + expect(layer.fillLayer.layer['rawConfig']).toMatchObject({ + opacity: 1, + strokeWidth: 1, + stroke: 'red', + strokeOpacity: 0.8, + }); + }); + + it('state', () => { + expect(layer.fillLayer.layer['needUpdateConfig'].enableHighlight).toBeTruthy(); + expect(layer.fillLayer.layer['needUpdateConfig'].enableSelect).toBeFalsy(); + + expect(getLayerStyleAttribute(layer.highlightStrokeLayer.layer['pendingStyleAttributes'], 'size')).toEqual({ + attributeName: 'size', + attributeField: 12, + }); + expect(layer.highlightStrokeLayer.layer['rawConfig']).toMatchObject({ + opacity: 0, + strokeWidth: 1.5, + stroke: 'green', + strokeOpacity: 0.8, + }); + + expect(getLayerStyleAttribute(layer.selectStrokeLayer.layer['pendingStyleAttributes'], 'size')).toEqual({ + attributeName: 'size', + attributeField: 12, + }); + expect(layer.selectStrokeLayer.layer['rawConfig']).toMatchObject({ + opacity: 0, + strokeWidth: 1.5, + stroke: 'yellow', + strokeOpacity: 0.8, + }); + }); +}); diff --git a/packages/composite-layers/__tests__/unit/core-layers/heatmap-layer/index.test.ts b/packages/composite-layers/__tests__/unit/core-layers/heatmap-layer/index.test.ts new file mode 100644 index 000000000..af62a43ed --- /dev/null +++ b/packages/composite-layers/__tests__/unit/core-layers/heatmap-layer/index.test.ts @@ -0,0 +1,54 @@ +import { getLayerStyleAttribute } from '../../../helper/layer'; +import { HeatmapLayer } from '../../../../src/core-layers/heatmap-layer'; + +describe('heatmap layer', () => { + const layer = new HeatmapLayer({ + source: { data: [], parser: { type: 'json', x: 'x', y: 'y' } }, + shape: 'heatmap', + size: { + field: 'mag', + value: [0, 1], + }, + style: { + intensity: 3, + radius: 20, + opacity: 1, + rampColors: { + colors: ['#FF4818', '#F7B74A', '#FFF598', '#F27DEB', '#8C1EB2', '#421EB2'], + positions: [0, 0.2, 0.4, 0.6, 0.8, 1.0], + }, + }, + }); + + it('type', () => { + expect(layer.type).toBe('heatmapLayer'); + expect(layer.layer.type).toBe('HeatMapLayer'); + }); + + it('size', () => { + expect(getLayerStyleAttribute(layer.layer['pendingStyleAttributes'], 'size')).toEqual({ + attributeName: 'size', + attributeField: 'mag', + attributeValues: [0, 1], + }); + }); + + it('shape', () => { + expect(getLayerStyleAttribute(layer.layer['pendingStyleAttributes'], 'shape')).toEqual({ + attributeName: 'shape', + attributeField: 'heatmap', + }); + }); + + it('style', () => { + expect(layer.layer['rawConfig']).toMatchObject({ + intensity: 3, + radius: 20, + opacity: 1, + rampColors: { + colors: ['#FF4818', '#F7B74A', '#FFF598', '#F27DEB', '#8C1EB2', '#421EB2'], + positions: [0, 0.2, 0.4, 0.6, 0.8, 1.0], + }, + }); + }); +}); diff --git a/packages/composite-layers/__tests__/unit/core-layers/line-layer/index.test.ts b/packages/composite-layers/__tests__/unit/core-layers/line-layer/index.test.ts new file mode 100644 index 000000000..ab32012da --- /dev/null +++ b/packages/composite-layers/__tests__/unit/core-layers/line-layer/index.test.ts @@ -0,0 +1,48 @@ +import { getLayerStyleAttribute } from '../../../helper/layer'; +import { LineLayer } from '../../../../src/core-layers/line-layer'; + +describe('line layer', () => { + const layer = new LineLayer({ + source: { data: [], parser: { type: 'json', coordinates: 'coordinates' } }, + color: '#fff', + shape: 'line', + size: 1, + style: { opacity: 1, lineType: 'dash' }, + state: { active: true, select: true }, + }); + + it('type', () => { + expect(layer.type).toBe('lineLayer'); + expect(layer.layer.type).toBe('LineLayer'); + }); + + it('size', () => { + expect(getLayerStyleAttribute(layer.layer['pendingStyleAttributes'], 'size')).toEqual({ + attributeName: 'size', + attributeField: 1, + }); + }); + + it('color', () => { + expect(getLayerStyleAttribute(layer.layer['pendingStyleAttributes'], 'color')).toEqual({ + attributeName: 'color', + attributeField: '#fff', + }); + }); + + it('shape', () => { + expect(getLayerStyleAttribute(layer.layer['pendingStyleAttributes'], 'shape')).toEqual({ + attributeName: 'shape', + attributeField: 'line', + }); + }); + + it('style', () => { + expect(layer.layer['rawConfig']).toMatchObject({ opacity: 1, lineType: 'dash' }); + }); + + it('state', () => { + expect(layer.layer['needUpdateConfig'].enableHighlight).toBeTruthy(); + expect(layer.layer['needUpdateConfig'].enableSelect).toBeTruthy(); + }); +}); diff --git a/packages/composite-layers/__tests__/unit/layers/dot-layer/index.test.ts b/packages/composite-layers/__tests__/unit/core-layers/point-layer/index.test.ts similarity index 87% rename from packages/composite-layers/__tests__/unit/layers/dot-layer/index.test.ts rename to packages/composite-layers/__tests__/unit/core-layers/point-layer/index.test.ts index ba06a30f4..0db979b11 100644 --- a/packages/composite-layers/__tests__/unit/layers/dot-layer/index.test.ts +++ b/packages/composite-layers/__tests__/unit/core-layers/point-layer/index.test.ts @@ -1,8 +1,8 @@ import { getLayerStyleAttribute } from '../../../helper/layer'; -import { DotLayer } from '../../../../src/layers/dot-layer'; +import { PointLayer } from '../../../../src/core-layers/point-layer'; -describe('dot layer', () => { - const layer = new DotLayer({ +describe('point layer', () => { + const layer = new PointLayer({ source: { data: [], parser: { type: 'json', x: 'x', y: 'y' } }, size: 12, color: '#fff', @@ -12,7 +12,7 @@ describe('dot layer', () => { }); it('type', () => { - expect(layer.type).toBe('dotLayer'); + expect(layer.type).toBe('pointLayer'); expect(layer.layer.type).toBe('PointLayer'); }); diff --git a/packages/composite-layers/__tests__/unit/core-layers/polygon-layer/index.test.ts b/packages/composite-layers/__tests__/unit/core-layers/polygon-layer/index.test.ts new file mode 100644 index 000000000..b9db25032 --- /dev/null +++ b/packages/composite-layers/__tests__/unit/core-layers/polygon-layer/index.test.ts @@ -0,0 +1,52 @@ +import { getLayerStyleAttribute } from '../../../helper/layer'; +import { PolygonLayer } from '../../../../src/core-layers/polygon-layer'; + +describe('polygon layer', () => { + const layer = new PolygonLayer({ + source: { data: [] }, + size: 12, + shape: 'fill', + color: { + field: 'adcode', + value: ['rgb(239,243,255)', 'rgb(189,215,231)', 'rgb(8,81,156)'], + }, + style: { opacity: 1 }, + state: { active: true, select: true }, + }); + + it('type', () => { + expect(layer.type).toBe('polygonLayer'); + expect(layer.layer.type).toBe('PolygonLayer'); + }); + + it('size', () => { + expect(getLayerStyleAttribute(layer.layer['pendingStyleAttributes'], 'size')).toEqual({ + attributeName: 'size', + attributeField: 12, + }); + }); + + it('color', () => { + expect(getLayerStyleAttribute(layer.layer['pendingStyleAttributes'], 'color')).toEqual({ + attributeName: 'color', + attributeField: 'adcode', + attributeValues: ['rgb(239,243,255)', 'rgb(189,215,231)', 'rgb(8,81,156)'], + }); + }); + + it('shape', () => { + expect(getLayerStyleAttribute(layer.layer['pendingStyleAttributes'], 'shape')).toEqual({ + attributeName: 'shape', + attributeField: 'fill', + }); + }); + + it('style', () => { + expect(layer.layer['rawConfig']).toMatchObject({ opacity: 1 }); + }); + + it('state', () => { + expect(layer.layer['needUpdateConfig'].enableHighlight).toBeTruthy(); + expect(layer.layer['needUpdateConfig'].enableSelect).toBeTruthy(); + }); +}); diff --git a/packages/composite-layers/__tests__/unit/core-layers/text-layer/index.test.ts b/packages/composite-layers/__tests__/unit/core-layers/text-layer/index.test.ts new file mode 100644 index 000000000..f32b1d2df --- /dev/null +++ b/packages/composite-layers/__tests__/unit/core-layers/text-layer/index.test.ts @@ -0,0 +1,67 @@ +import { getLayerStyleAttribute } from '../../../helper/layer'; +import { TextLayer } from '../../../../src/core-layers/text-layer'; + +describe('text layer', () => { + const layer = new TextLayer({ + source: { data: [], parser: { type: 'json', x: 'x', y: 'y' } }, + field: 'label', + style: { + fill: '#fff', + opacity: 0.6, + fontSize: 12, + textAnchor: 'top', + textOffset: [0, 20], + spacing: 1, + padding: [5, 5], + stroke: '#ffffff', + strokeWidth: 0.3, + strokeOpacity: 1.0, + }, + state: { active: true, select: true }, + }); + + it('type', () => { + expect(layer.type).toBe('textLayer'); + expect(layer.layer.type).toBe('PointLayer'); + }); + + it('size', () => { + expect(getLayerStyleAttribute(layer.layer['pendingStyleAttributes'], 'size')).toEqual({ + attributeName: 'size', + attributeField: 12, + }); + }); + + it('color', () => { + expect(getLayerStyleAttribute(layer.layer['pendingStyleAttributes'], 'color')).toEqual({ + attributeName: 'color', + attributeField: '#fff', + }); + }); + + it('shape', () => { + expect(getLayerStyleAttribute(layer.layer['pendingStyleAttributes'], 'shape')).toEqual({ + attributeName: 'shape', + attributeField: 'label', + attributeValues: 'text', + }); + }); + + it('style', () => { + expect(layer.layer['rawConfig']).toMatchObject({ + opacity: 0.6, + textAnchor: 'top', + textOffset: [0, 20], + spacing: 1, + padding: [5, 5], + stroke: '#ffffff', + strokeWidth: 0.3, + strokeOpacity: 1.0, + }); + }); + + it('state', () => { + expect(layer.layer['needUpdateConfig'].enableHighlight).toBeTruthy(); + expect(layer.layer['needUpdateConfig'].enableSelect).toBeTruthy(); + }); +}); diff --git a/packages/composite-layers/__tests__/unit/core/layer-group.test.ts b/packages/composite-layers/__tests__/unit/core/layer-group.test.ts index d95168fe6..1ed7e441a 100644 --- a/packages/composite-layers/__tests__/unit/core/layer-group.test.ts +++ b/packages/composite-layers/__tests__/unit/core/layer-group.test.ts @@ -1,11 +1,14 @@ -import { PointLayer } from '@antv/l7-layers'; import { LayerGroup } from '../../../src/core/layer-group'; +import { PointLayer } from '../../../src/core-layers/point-layer'; describe('layer group', () => { const layerGroup = new LayerGroup([]); - const pointLayer1 = new PointLayer({ name: 'pointLayer' }); - const pointLayer2 = new PointLayer({}); - const pointLayer3 = new PointLayer({}); + const pointLayer1 = new PointLayer({ + name: 'pointLayer', + source: { data: [], parser: { type: 'json', x: 'x', y: 'y' } }, + }); + const pointLayer2 = new PointLayer({ source: { data: [], parser: { type: 'json', x: 'x', y: 'y' } } }); + const pointLayer3 = new PointLayer({ source: { data: [], parser: { type: 'json', x: 'x', y: 'y' } } }); it('addLayer', () => { layerGroup.addLayer(pointLayer1); @@ -18,14 +21,14 @@ describe('layer group', () => { it('getLayer', () => { expect(layerGroup.getLayers().length).toEqual(3); expect(layerGroup.getLayerByName(pointLayer1.name)).toEqual(pointLayer1); - expect(layerGroup.getLayer(pointLayer2.id)).toEqual(pointLayer2); + expect(layerGroup.getLayer(pointLayer2.name)).toEqual(pointLayer2); }); // eslint-disable-next-line jest/no-commented-out-tests // it('setZIndex', () => { // layerGroup.setZIndex(3); // layerGroup.getLayers().forEach((layer) => { - // expect(layer.zIndex).toEqual(3); + // expect(layer.layer.zIndex).toEqual(3); // }); // }); @@ -33,7 +36,7 @@ describe('layer group', () => { expect(layerGroup.removeLayer(pointLayer1)).toBeTruthy(); expect(layerGroup.hasLayer(pointLayer1)).toBeFalsy(); - expect(layerGroup.removeLayer(pointLayer2.id)).toBeTruthy(); + expect(layerGroup.removeLayer(pointLayer2.name)).toBeTruthy(); expect(layerGroup.hasLayer(pointLayer2)).toBeFalsy(); layerGroup.removeAllLayer(); diff --git a/packages/composite-layers/__tests__/unit/layers/area-layer/index.test.ts b/packages/composite-layers/__tests__/unit/layers/area-layer/index.test.ts deleted file mode 100644 index 345fa5d17..000000000 --- a/packages/composite-layers/__tests__/unit/layers/area-layer/index.test.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { getLayerStyleAttribute } from '../../../helper/layer'; -import { AreaLayer } from '../../../../src/layers/area-layer'; - -describe('area layer', () => { - const layer = new AreaLayer({ - source: { data: [] }, - color: { - field: 'adcode', - value: ['rgb(239,243,255)', 'rgb(189,215,231)', 'rgb(8,81,156)'], - }, - style: { - opacity: 1, - stroke: 'rgb(93,112,146)', - lineWidth: 0.6, - lineOpacity: 1, - }, - state: { - active: { - stroke: 'green', - lineWidth: 1.5, - lineOpacity: 0.8, - }, - select: { - stroke: 'yellow', - lineWidth: 1.5, - lineOpacity: 0.8, - }, - }, - }); - - it('type', () => { - expect(layer.type).toBe('areaLayer'); - expect(layer.layer.type).toBe('PolygonLayer'); - expect(layer.strokeLayer.type).toBe('LineLayer'); - expect(layer.highlightLayer.type).toBe('LineLayer'); - expect(layer.selectFillLayer.type).toBe('PolygonLayer'); - expect(layer.selectStrokeLayer.type).toBe('LineLayer'); - }); - - it('color', () => { - expect(getLayerStyleAttribute(layer.layer['pendingStyleAttributes'], 'color')).toEqual({ - attributeName: 'color', - attributeField: 'adcode', - attributeValues: ['rgb(239,243,255)', 'rgb(189,215,231)', 'rgb(8,81,156)'], - }); - }); - - it('shape', () => { - expect(getLayerStyleAttribute(layer.layer['pendingStyleAttributes'], 'shape')).toEqual({ - attributeName: 'shape', - attributeField: 'fill', - }); - expect(getLayerStyleAttribute(layer.strokeLayer['pendingStyleAttributes'], 'shape')).toEqual({ - attributeName: 'shape', - attributeField: 'line', - }); - }); - - it('style', () => { - expect(layer.layer['rawConfig']).toMatchObject({ opacity: 1 }); - }); - - it('state', () => { - expect(getLayerStyleAttribute(layer.highlightLayer['pendingStyleAttributes'], 'color')).toEqual({ - attributeName: 'color', - attributeField: 'green', - }); - expect(getLayerStyleAttribute(layer.highlightLayer['pendingStyleAttributes'], 'size')).toEqual({ - attributeName: 'size', - attributeField: 1.5, - }); - expect(layer.highlightLayer['rawConfig']).toMatchObject({ opacity: 0.8 }); - expect(getLayerStyleAttribute(layer.selectStrokeLayer['pendingStyleAttributes'], 'color')).toEqual({ - attributeName: 'color', - attributeField: 'yellow', - }); - expect(getLayerStyleAttribute(layer.selectStrokeLayer['pendingStyleAttributes'], 'size')).toEqual({ - attributeName: 'size', - attributeField: 1.5, - }); - expect(layer.selectStrokeLayer['rawConfig']).toMatchObject({ opacity: 0.8 }); - }); -}); diff --git a/packages/composite-layers/package.json b/packages/composite-layers/package.json index e04757ffd..4bc63e72c 100644 --- a/packages/composite-layers/package.json +++ b/packages/composite-layers/package.json @@ -1,6 +1,5 @@ { "name": "@antv/l7-composite-layers", - "private": true, "version": "0.0.1-alpha.2", "description": "Composite layer for @antv/l7", "main": "dist/lib/index.js", @@ -19,12 +18,13 @@ "clean": "rimraf dist", "build": "yarn run clean && yarn run build:cjs && yarn run build:esm", "build:cjs": "tsc -p tsconfig.json --target ES5 --module CommonJS --outDir dist/lib", - "build:esm": "tsc -p tsconfig.json --target ES5 --module ESNext --outDir dist/esm" + "build:esm": "tsc -p tsconfig.json --target ES5 --module ESNext --outDir dist/esm", + "build:umd": "rollup -c rollup.config.js" }, "keywords": [ "antv", "l7", - "Composite Layers" + "composite layers" ], "sideEffects": false, "author": "yunji", diff --git a/packages/composite-layers/rollup.config.js b/packages/composite-layers/rollup.config.js new file mode 100644 index 000000000..8bb1c8601 --- /dev/null +++ b/packages/composite-layers/rollup.config.js @@ -0,0 +1,29 @@ +import typescript from '@rollup/plugin-typescript'; +import nodeResolve from '@rollup/plugin-node-resolve'; +import commonjs from '@rollup/plugin-commonjs'; +import filesize from 'rollup-plugin-filesize'; +import analyze from 'rollup-plugin-analyzer'; +import { terser } from 'rollup-plugin-terser'; + +export default { + input: 'src/index.ts', + output: [ + { + file: 'dist/umd/l7-composite-layers.min.js', + format: 'umd', + name: 'L7CompositeLayers', + sourcemap: true, + plugins: [terser()], + }, + ], + plugins: [ + nodeResolve({ browser: true, preferBuiltins: false }), + commonjs(), + typescript(), + analyze({ + summaryOnly: true, + limit: 10, + }), + filesize(), + ], +}; diff --git a/packages/composite-layers/src/adaptor/attribute/index.ts b/packages/composite-layers/src/adaptor/attribute/index.ts index 59777fece..a379f4f36 100644 --- a/packages/composite-layers/src/adaptor/attribute/index.ts +++ b/packages/composite-layers/src/adaptor/attribute/index.ts @@ -2,7 +2,6 @@ import { isFunction, isObject, isString, isNumber, isBoolean, isArray } from '@a import { ILayer, ScaleConfig, - ScaleConfigMap, ColorAttr, SizeAttr, ShapeAttr, @@ -11,6 +10,7 @@ import { StateAttribute, TextureAttr, FilterAttr, + ScaleAttr, } from '../../types'; /** @@ -122,14 +122,13 @@ export class MappingAttribute { } } - static style(layer: ILayer, style: unknown) { - style && layer.style(style); - } - - static state(layer: ILayer, state: StateAttribute) { - const { active, select } = state; - active && layer.active(active); - select && layer.select(select); + static scale(layer: ILayer, field: string | ScaleAttr, cfg?: ScaleConfig) { + /** + * scale 的几种情况 + * layer.scale('name', {type: 'cat'}); + * layer.scale({name: {type: 'cat'}, value: {type: 'linear'}}); + */ + layer.scale(field, cfg); } static rotate(layer: ILayer, rotate: RotateAttr) { @@ -171,15 +170,6 @@ export class MappingAttribute { } } - static scale(layer: ILayer, field: string | ScaleConfigMap, cfg: ScaleConfig) { - /** - * scale 的几种情况 - * layer.scale('name', {type: 'cat'}); - * layer.scale({name: {type: 'cat'}, value: {type: 'linear'}}); - */ - layer.scale(field, cfg); - } - static filter(layer: ILayer, filter: FilterAttr) { /** * scale 的几种情况 @@ -189,4 +179,14 @@ export class MappingAttribute { const mappingFields = isArray(field) ? field : field.split('*'); layer.filter(mappingFields.join('*'), getMappingFunction(mappingFields, filter.value)); } + + static style(layer: ILayer, style: unknown) { + style && layer.style(style); + } + + static state(layer: ILayer, state: StateAttribute) { + const { active, select } = state; + active && layer.active(active); + select && layer.select(select); + } } diff --git a/packages/composite-layers/src/adaptor/source/index.ts b/packages/composite-layers/src/adaptor/source/index.ts index ddb2acb17..52e5a364b 100644 --- a/packages/composite-layers/src/adaptor/source/index.ts +++ b/packages/composite-layers/src/adaptor/source/index.ts @@ -1,15 +1,15 @@ -import { GridAggregation, SourceOptions } from '../../types'; +// import { GridAggregation, SourceOptions } from '../../types'; -export class MappingSource { - static aggregation(source: Partial, aggregation: GridAggregation) { - const { type = 'grid', radius, method, field } = aggregation; - const config = { type, size: radius, method, field }; - if (source.transforms) { - // 过滤 transform 有相同配置情况 - source.transforms = source.transforms.filter((transform) => transform.type !== config.type); - source.transforms.push(config); - } else { - source.transforms = [config]; - } - } -} +// export class MappingSource { +// static aggregation(source: Partial, aggregation: GridAggregation) { +// const { type = 'grid', radius, method, field } = aggregation; +// const config = { type, size: radius, method, field }; +// if (source.transforms) { +// // 过滤 transform 有相同配置情况 +// source.transforms = source.transforms.filter((transform) => transform.type !== config.type); +// source.transforms.push(config); +// } else { +// source.transforms = [config]; +// } +// } +// } diff --git a/packages/composite-layers/src/composite-layers/area-layer/adaptor.ts b/packages/composite-layers/src/composite-layers/area-layer/adaptor.ts new file mode 100644 index 000000000..03112e89f --- /dev/null +++ b/packages/composite-layers/src/composite-layers/area-layer/adaptor.ts @@ -0,0 +1,57 @@ +import { isUndefined } from '@antv/util'; +import { DEFAULT_STATE } from './constants'; +import { AreaLayerOptions } from './types'; + +export const getDefaultState = (state?: AreaLayerOptions['state']) => { + if (isUndefined(state)) { + return DEFAULT_STATE; + } + + if (state.active === false) { + DEFAULT_STATE.active = Object.assign(DEFAULT_STATE.active, { fill: false, stroke: false }); + } else if (typeof state.active === 'object') { + if (state.active.fillColor === false) { + DEFAULT_STATE.active.fillColor = false; + } else if (typeof state.active.fillColor === 'string') { + DEFAULT_STATE.active.fillColor = state.active.fillColor; + } + + if (state.active.strokeColor === false) { + DEFAULT_STATE.active.strokeColor = false; + } else if (typeof state.active.strokeColor === 'string') { + DEFAULT_STATE.active.strokeColor = state.active.strokeColor; + } + + if (typeof state.active.lineWidth === 'number') { + DEFAULT_STATE.active.lineWidth = state.active.lineWidth; + } + if (typeof state.active.lineOpacity === 'number') { + DEFAULT_STATE.active.lineOpacity = state.active.lineOpacity; + } + } + + if (state.select === false) { + DEFAULT_STATE.select = Object.assign(DEFAULT_STATE.select, { fill: false, stroke: false }); + } else if (typeof state.select === 'object') { + if (state.select.fillColor === false) { + DEFAULT_STATE.select.fillColor = false; + } else if (typeof state.select.fillColor === 'string') { + DEFAULT_STATE.select.fillColor = state.select.fillColor; + } + + if (state.select.strokeColor === false) { + DEFAULT_STATE.select.strokeColor = false; + } else if (typeof state.select.strokeColor === 'string') { + DEFAULT_STATE.select.strokeColor = state.select.strokeColor; + } + + if (typeof state.select.lineWidth === 'number') { + DEFAULT_STATE.select.lineWidth = state.select.lineWidth; + } + if (typeof state.select.lineOpacity === 'number') { + DEFAULT_STATE.select.lineOpacity = state.select.lineOpacity; + } + } + + return DEFAULT_STATE; +}; diff --git a/packages/composite-layers/src/layers/area-layer/constants.ts b/packages/composite-layers/src/composite-layers/area-layer/constants.ts similarity index 56% rename from packages/composite-layers/src/layers/area-layer/constants.ts rename to packages/composite-layers/src/composite-layers/area-layer/constants.ts index 650f232de..b8038ed18 100644 --- a/packages/composite-layers/src/layers/area-layer/constants.ts +++ b/packages/composite-layers/src/composite-layers/area-layer/constants.ts @@ -1,5 +1,10 @@ import { AreaLayerActiveOptions, AreaLayerOptions } from './types'; +/** + * 空值 source + */ +export const EMPTY_SOURCE = { data: { type: 'FeatureCollection', features: [] }, parser: { type: 'geojson' } }; + const defaultHighlightColor = '#2f54eb'; /** @@ -7,16 +12,16 @@ const defaultHighlightColor = '#2f54eb'; */ export const DEFAULT_STATE: { active: Required; select: Required } = { active: { - fill: false, - stroke: defaultHighlightColor, - lineWidth: 1.5, - lineOpacity: 0.8, + fillColor: false, + strokeColor: defaultHighlightColor, + lineWidth: 1, + lineOpacity: 1, }, select: { - fill: false, - stroke: defaultHighlightColor, - lineWidth: 1.5, - lineOpacity: 0.8, + fillColor: false, + strokeColor: defaultHighlightColor, + lineWidth: 1, + lineOpacity: 1, }, }; @@ -24,6 +29,8 @@ export const DEFAULT_STATE: { active: Required; select: * 默认配置项 */ export const DEFAULT_OPTIONS: Partial = { + visible: true, + source: EMPTY_SOURCE, state: { active: false, select: false, diff --git a/packages/composite-layers/src/composite-layers/area-layer/index.ts b/packages/composite-layers/src/composite-layers/area-layer/index.ts new file mode 100644 index 000000000..4fd17039b --- /dev/null +++ b/packages/composite-layers/src/composite-layers/area-layer/index.ts @@ -0,0 +1,522 @@ +import { clone, isEqual, isUndefined } from '@antv/util'; +import Source from '@antv/l7-source'; +import { CompositeLayer } from '../../core/composite-layer'; +import { LineLayer } from '../../core-layers/line-layer'; +import { PolygonLayer } from '../../core-layers/polygon-layer'; +import { TextLayer } from '../../core-layers/text-layer'; +import { getDefaultState } from './adaptor'; +import { AreaLayerOptions, AreaLayerSourceOptions } from './types'; +import { ICoreLayer, ISource, MouseEvent } from '../../types'; +import { DEFAULT_OPTIONS, DEFAULT_STATE, EMPTY_SOURCE } from './constants'; + +export type { AreaLayerOptions }; + +export class AreaLayer extends CompositeLayer { + /** + * 默认配置项 + */ + static DefaultOptions = DEFAULT_OPTIONS; + /** + * 复合图层类型 + */ + public type = CompositeLayer.LayerType.AreaLayer; + /** + * 主图层 + */ + protected get layer() { + return this.fillLayer; + } + /** + * 填充面图层 + */ + public get fillLayer() { + return this.subLayers.getLayer('fillLayer') as ICoreLayer; + } + /** + * 描边图层 + */ + public get strokeLayer() { + return this.subLayers.getLayer('strokeLayer') as ICoreLayer; + } + /** + * 高亮描边图层 + */ + public get highlightStrokeLayer() { + return this.subLayers.getLayer('highlightStrokeLayer') as ICoreLayer; + } + /** + * 高亮数据 + */ + private highlightData: any; + /** + * 选中填充面图层 + */ + public get selectFillLayer() { + return this.subLayers.getLayer('selectFillLayer') as ICoreLayer; + } + /** + * 选中描边图层 + */ + public get selectStrokeLayer() { + return this.subLayers.getLayer('selectStrokeLayer') as ICoreLayer; + } + /** + * 选中数据 + */ + private selectData: { feature: any; featureId: number }[] = []; + /** + * 标注文本图层 + */ + public get labelLayer() { + return this.subLayers.getLayer('labelLayer') as ICoreLayer; + } + /** + * 图层交互状态配置 + */ + private layerState = DEFAULT_STATE; + /** + * 图层是否具有交互属性 + */ + public interaction = true; + + constructor(options: AreaLayerOptions) { + super(options); + this.initSubLayersEvent(); + } + + /** + * 获取默认配置 + */ + public getDefaultOptions(): Partial { + return AreaLayer.DefaultOptions; + } + + /** + * 创建子图层 + */ + protected createSubLayers() { + this.layerState = getDefaultState(this.options.state); + const sourceOptions = this.options.source; + const source = sourceOptions instanceof Source ? sourceOptions : this.createSource(sourceOptions); + + // 映射填充面图层 + const fillLayer = new PolygonLayer({ + name: 'fillLayer', + shape: 'fill', + ...this.getFillLayerOptions(), + source, + }); + const fillBottomColor = this.options.fillBottomColor; + fillBottomColor && fillLayer.layer.setBottomColor(fillBottomColor); + + // 描边图层 + const strokeLayer = new LineLayer({ + name: 'strokeLayer', + shape: 'line', + ...this.getStrokeLayerOptions(), + source, + }); + + // 高亮描边图层 + const highlightStrokeLayer = new LineLayer({ + name: 'highlightStrokeLayer', + ...this.gethigHlightStrokeLayerOptions(), + }); + + // 选中填充图层 + const selectFillLayer = new PolygonLayer({ + name: 'selectFillLayer', + ...this.getSelectFillLayerOptions(), + }); + + // 选中描边图层 + const selectStrokeLayer = new LineLayer({ + name: 'selectStrokeLayer', + ...this.getSelectStrokeLayerOptions(), + }); + + // 标注图层 + const labelLayer = new TextLayer({ + name: 'labelLayer', + ...this.getTextLayerOptions(), + source, + }); + + const subLayers = [fillLayer, strokeLayer, highlightStrokeLayer, selectFillLayer, selectStrokeLayer, labelLayer]; + + return subLayers; + } + + private getFillLayerOptions() { + const { visible, minZoom, maxZoom, zIndex = 0, fillColor, opacity, ...baseConfig } = this.options; + const defaultState = this.layerState; + + const fillState = { + active: defaultState.active.fillColor === false ? false : { color: defaultState.active.fillColor }, + select: false, + }; + const fillStyle = { opacity: opacity }; + + const options = { + ...baseConfig, + visible, + minZoom, + maxZoom, + zIndex, + color: fillColor, + state: fillState, + style: fillStyle, + }; + + return options; + } + + private getStrokeLayerOptions() { + const { + visible, + minZoom, + maxZoom, + zIndex = 0, + opacity, + strokeColor, + lineWidth, + lineOpacity, + lineDash, + lineType, + } = this.options; + + const strokeStyle = { + opacity: isUndefined(lineOpacity) ? opacity : lineOpacity, + dashArray: lineDash, + lineType: lineType, + }; + + const options = { + visible, + zIndex, + minZoom, + maxZoom, + size: lineWidth, + color: strokeColor, + style: strokeStyle, + }; + + return options; + } + + private gethigHlightStrokeLayerOptions() { + const { visible, minZoom, maxZoom, zIndex = 0, lineWidth } = this.options; + const defaultState = this.layerState; + + const color = defaultState.active.strokeColor || undefined; + const size = defaultState.active.lineWidth || lineWidth; + + const options = { + visible: visible && Boolean(color), + zIndex: zIndex + 0.1, + minZoom, + maxZoom, + source: EMPTY_SOURCE, + size: size, + color: color, + style: { opacity: defaultState.active.lineOpacity }, + }; + + return options; + } + + private getSelectFillLayerOptions() { + const { visible, minZoom, maxZoom, zIndex = 0, opacity } = this.options; + const defaultState = this.layerState; + const color = defaultState.select.fillColor || undefined; + const fillStyle = { opacity: opacity }; + + const option = { + visible: visible && Boolean(color), + zIndex: zIndex + 0.1, + minZoom, + maxZoom, + source: EMPTY_SOURCE, + color, + style: fillStyle, + state: { select: false, active: false }, + }; + + return option; + } + + private getSelectStrokeLayerOptions() { + const { visible, minZoom, maxZoom, zIndex = 0, lineWidth } = this.options; + const defaultState = this.layerState; + const color = defaultState.select.strokeColor || undefined; + const size = defaultState.select.lineWidth || lineWidth; + + const option = { + visible: visible && Boolean(color), + zIndex: zIndex + 0.1, + minZoom, + maxZoom, + source: EMPTY_SOURCE, + size, + color, + style: { opacity: defaultState.select.lineOpacity }, + }; + + return option; + } + + private getTextLayerOptions() { + const { visible, minZoom, maxZoom, zIndex = 0, label } = this.options; + const options = { + zIndex: zIndex + 0.1, + minZoom, + maxZoom, + ...label, + visible: visible && (label?.visible || Boolean(label)), + }; + + return options; + } + + /** + * 设置子图层数据 + */ + protected setSubLayersSource(source: AreaLayerSourceOptions | ISource) { + if (source instanceof Source) { + this.fillLayer.setSource(source); + this.strokeLayer.setSource(source); + this.labelLayer.setSource(source); + } else { + const layerSource = this.fillLayer.source; + const { data, ...option } = source; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + layerSource.setData(data, option); + } + + this.highlightStrokeLayer.changeData(EMPTY_SOURCE); + this.selectFillLayer.changeData(EMPTY_SOURCE); + this.selectStrokeLayer.changeData(EMPTY_SOURCE); + } + + /** + * 设置高亮描边子图层数据 + */ + protected setHighlightLayerSource(feature?: any, featureId = -999) { + if (this.highlightData === featureId) { + return; + } + const features = feature ? [feature] : []; + this.highlightStrokeLayer.changeData({ + data: { type: 'FeatureCollection', features }, + parser: { type: 'geojson' }, + }); + this.highlightData = featureId; + } + + /** + * 设置选中描边与填充子图层数据 + */ + protected setSelectLayerSource(selectData: any[] = []) { + if ( + this.selectData.length === selectData.length && + isEqual( + this.selectData.map(({ featureId }) => featureId), + selectData.map(({ featureId }) => featureId) + ) + ) { + return; + } + const features = selectData.map(({ feature }) => feature); + this.selectFillLayer.changeData({ data: { type: 'FeatureCollection', features }, parser: { type: 'geojson' } }); + this.selectStrokeLayer.changeData({ data: { type: 'FeatureCollection', features }, parser: { type: 'geojson' } }); + this.selectData = selectData; + } + + /** + * 初始化子图层事件 + */ + protected initSubLayersEvent() { + // 初始化主图层交互事件 + this.fillLayer.off('mousemove', this.onHighlighHandle); + this.fillLayer.off('unmousemove', this.onHighlighHandle); + this.fillLayer.off('click', this.onSelectHandle); + this.selectData = []; + this.highlightData = null; + if (!this.options.state) return; + // active + if (this.options.state.active) { + this.fillLayer.on('mousemove', this.onHighlighHandle); + this.fillLayer.on('unmousemove', this.onUnhighlighHandle); + } + // select + if (this.options.state.select) { + this.fillLayer.on('click', this.onSelectHandle); + } + } + + /** + * 图层高亮回调 + */ + private onHighlighHandle = (event: MouseEvent) => { + const { feature, featureId } = event; + this.setHighlightLayerSource(feature, featureId); + }; + + /** + * 图层取消高亮回调 + */ + private onUnhighlighHandle = () => { + this.setHighlightLayerSource(); + }; + + /** + * 图层选中回调 + */ + private onSelectHandle = (event: MouseEvent) => { + const { feature, featureId } = event; + this.handleSelectData(featureId, feature); + }; + + private handleSelectData(featureId: number, feature: any) { + const enabledMultiSelect = this.options.enabledMultiSelect; + let selectData = clone(this.selectData); + const index = selectData.findIndex((item) => item.featureId === featureId); + + if (index === -1) { + if (enabledMultiSelect) { + selectData.push({ feature, featureId }); + } else { + selectData = [{ feature, featureId }]; + } + this.emit('select', feature, clone(selectData)); + } else { + const unselectFeature = selectData[index]; + if (enabledMultiSelect) { + selectData.splice(index, 1); + } else { + selectData = []; + } + this.emit('unselect', unselectFeature, clone(selectData)); + } + + this.setSelectLayerSource(selectData); + } + + /** + * 更新 + */ + public update(options: Partial) { + super.update(options); + + this.initSubLayersEvent(); + } + + /** + * 更新: 更新配置 + */ + public updateOption(options: Partial) { + super.update(options); + this.layerState = getDefaultState(this.options.state); + } + + /** + * 更新子图层 + */ + protected updateSubLayers(options: Partial) { + // 映射填充面图层 + this.fillLayer.update(this.getFillLayerOptions()); + + // 描边图层 + this.strokeLayer.update(this.getStrokeLayerOptions()); + + // 高亮图层 + this.highlightStrokeLayer.update(this.gethigHlightStrokeLayerOptions()); + + // 选中填充图层 + this.selectFillLayer.update(this.getSelectFillLayerOptions()); + + // 选中描边图层 + this.selectStrokeLayer.update(this.getSelectStrokeLayerOptions()); + + // 重置高亮/选中状态 + if (this.options.visible) { + if (!isUndefined(options.state) && !isEqual(this.lastOptions.state, this.options.state)) { + this.updateHighlightSubLayers(); + } + + if (this.layerState.active.strokeColor) { + this.setHighlightLayerSource(); + } + if (this.layerState.select.fillColor || this.layerState.select.strokeColor) { + this.setSelectLayerSource(); + } + } + } + + /** + * 更新高亮及选中子图层 + */ + private updateHighlightSubLayers() { + const defaultState = this.layerState; + const lasetDefaultState = getDefaultState(this.lastOptions.state); + + if (lasetDefaultState.active.strokeColor !== defaultState.active.strokeColor) { + defaultState.active.strokeColor ? this.highlightStrokeLayer.show() : this.highlightStrokeLayer.hide(); + } + + if (lasetDefaultState.select.fillColor !== defaultState.select.fillColor) { + defaultState.select.fillColor ? this.selectFillLayer.show() : this.selectFillLayer.hide(); + } + + if (lasetDefaultState.select.strokeColor !== defaultState.select.strokeColor) { + defaultState.select.strokeColor ? this.selectStrokeLayer.show() : this.selectStrokeLayer.hide(); + } + } + + public setIndex(zIndex: number) { + this.fillLayer.setIndex(zIndex); + this.strokeLayer.setIndex(zIndex); + this.highlightStrokeLayer.setIndex(zIndex + 0.1); + this.selectFillLayer.setIndex(zIndex + 0.1); + this.selectStrokeLayer.setIndex(zIndex + 0.1); + this.labelLayer.setIndex(zIndex + 0.1); + } + + public setActive(field: string, value: number | string) { + const source = this.fillLayer.source; + const featureId = source.getFeatureId(field, value); + if (isUndefined(featureId)) { + throw new Error('Feature non-existent' + field + value); + } + + if (this.layerState.active.fillColor) { + this.fillLayer.layer.setActive(featureId); + } + + if (this.layerState.active.strokeColor) { + const feature = source.getFeatureById(featureId); + this.setHighlightLayerSource(feature, featureId); + } + } + + public setSelect(field: string, value: number | string) { + const source = this.fillLayer.source; + const featureId = source.getFeatureId(field, value); + if (isUndefined(featureId)) { + throw new Error('Feature non-existent' + field + value); + } + + if (this.layerState.select.strokeColor === false || this.layerState.select.fillColor === false) { + return; + } + + const feature = source.getFeatureById(featureId); + this.handleSelectData(featureId, feature); + // TODO: L7 method pickFeature(id|{x,y}) + } + + public boxSelect(bounds: [number, number, number, number], callback: (...args: any[]) => void) { + this.fillLayer.boxSelect(bounds, callback); + } +} diff --git a/packages/composite-layers/src/composite-layers/area-layer/types.ts b/packages/composite-layers/src/composite-layers/area-layer/types.ts new file mode 100644 index 000000000..369e50cdd --- /dev/null +++ b/packages/composite-layers/src/composite-layers/area-layer/types.ts @@ -0,0 +1,77 @@ +import { PolygonLayerOptions } from '../../core-layers/polygon-layer/types'; +import { TextLayerOptions } from '../../core-layers/text-layer/types'; +import { CompositeLayerOptions } from '../../core/composite-layer'; +import { ISourceCFG, ISource } from '../../types'; + +/** + * 数据配置 + */ +export interface AreaLayerSourceOptions extends Pick { + data: any; +} + +export type AreaLayerActiveOptions = { + // 填充颜色 + fillColor?: false | string; + // 描边颜色 + strokeColor?: false | string; + // 描边的宽度 + lineWidth?: number; + // 描边透明度 + lineOpacity?: number; +}; + +export interface AreaLayerOptions extends CompositeLayerOptions { + /** + * 具体的数据 + */ + source: AreaLayerSourceOptions | ISource; + /** + * 填充色 + */ + fillColor?: PolygonLayerOptions['color']; + /** + * 填充兜底颜色,用于颜色值映值不存在时 + */ + fillBottomColor?: false | string; + /** + * 填充透明度 + */ + opacity?: number; + /** + * 描边色 + */ + strokeColor?: PolygonLayerOptions['color']; + /** + * 描边线宽 + */ + lineWidth?: PolygonLayerOptions['size']; + /** + * 描边透明度 + */ + lineOpacity?: number; + /** + * 描边的类型 + */ + lineType?: 'solid' | 'dash'; + /** + * 描边的虚线配置 + * 第一个值为虚线每个分段的长度,第二个值为分段间隔的距离。lineDash 设为[0,0]的效果为没有描边。 + */ + lineDash?: [number, number]; + /** + * 文本标注 + */ + label?: Omit; + /** + * 交互反馈 + */ + state?: { + active?: boolean | AreaLayerActiveOptions; + select?: boolean | AreaLayerActiveOptions; + }; + /** + * 是否启用多选 + */ + enabledMultiSelect?: boolean; +} diff --git a/packages/composite-layers/src/composite-layers/scatter-layer/adaptor.ts b/packages/composite-layers/src/composite-layers/scatter-layer/adaptor.ts new file mode 100644 index 000000000..326b921fe --- /dev/null +++ b/packages/composite-layers/src/composite-layers/scatter-layer/adaptor.ts @@ -0,0 +1,57 @@ +import { isUndefined } from '@antv/util'; +import { DEFAULT_STATE } from './constants'; +import { ScatterLayerOptions } from './types'; + +export const getDefaultState = (state?: ScatterLayerOptions['state']) => { + if (isUndefined(state)) { + return DEFAULT_STATE; + } + + if (state.active === false) { + DEFAULT_STATE.active = Object.assign(DEFAULT_STATE.active, { fillColor: false, strokeColor: false }); + } else if (typeof state.active === 'object') { + if (state.active.fillColor === false) { + DEFAULT_STATE.active.fillColor = false; + } else if (typeof state.active.fillColor === 'string') { + DEFAULT_STATE.active.fillColor = state.active.fillColor; + } + + if (state.active.strokeColor === false) { + DEFAULT_STATE.active.strokeColor = false; + } else if (typeof state.active.strokeColor === 'string') { + DEFAULT_STATE.active.strokeColor = state.active.strokeColor; + } + + if (typeof state.active.lineWidth === 'number') { + DEFAULT_STATE.active.lineWidth = state.active.lineWidth; + } + if (typeof state.active.lineOpacity === 'number') { + DEFAULT_STATE.active.lineOpacity = state.active.lineOpacity; + } + } + + if (state.select === false) { + DEFAULT_STATE.select = Object.assign(DEFAULT_STATE.select, { fillColor: false, strokeColor: false }); + } else if (typeof state.select === 'object') { + if (state.select.fillColor === false) { + DEFAULT_STATE.select.fillColor = false; + } else if (typeof state.select.fillColor === 'string') { + DEFAULT_STATE.select.fillColor = state.select.fillColor; + } + + if (state.select.strokeColor === false) { + DEFAULT_STATE.select.strokeColor = false; + } else if (typeof state.select.strokeColor === 'string') { + DEFAULT_STATE.select.strokeColor = state.select.strokeColor; + } + + if (typeof state.select.lineWidth === 'number') { + DEFAULT_STATE.select.lineWidth = state.select.lineWidth; + } + if (typeof state.select.lineOpacity === 'number') { + DEFAULT_STATE.select.lineOpacity = state.select.lineOpacity; + } + } + + return DEFAULT_STATE; +}; diff --git a/packages/composite-layers/src/composite-layers/scatter-layer/constants.ts b/packages/composite-layers/src/composite-layers/scatter-layer/constants.ts new file mode 100644 index 000000000..93d104174 --- /dev/null +++ b/packages/composite-layers/src/composite-layers/scatter-layer/constants.ts @@ -0,0 +1,44 @@ +import { DotLayerActiveOptions, ScatterLayerOptions } from './types'; + +/** + * 空值 source + */ +export const EMPTY_SOURCE = { data: [], parser: { type: 'json', x: 'x', y: 'y' } }; + +const defaultHighlightColor = '#2f54eb'; + +/** + * 默认的全部交互状态配置 + */ +export const DEFAULT_STATE: { active: Required; select: Required } = { + active: { + fillColor: false, + strokeColor: defaultHighlightColor, + lineWidth: 1.5, + lineOpacity: 1, + }, + select: { + fillColor: false, + strokeColor: defaultHighlightColor, + lineWidth: 1.5, + lineOpacity: 1, + }, +}; + +/** + * 默认配置项 + */ +export const DEFAULT_OPTIONS: Partial = { + visible: true, + source: { + data: [], + parser: { type: 'json', x: 'x', y: 'y' }, + }, + radius: 12, + fillColor: '#5FD3A6', + state: { + active: false, + select: false, + }, + enabledMultiSelect: false, +}; diff --git a/packages/composite-layers/src/composite-layers/scatter-layer/index.ts b/packages/composite-layers/src/composite-layers/scatter-layer/index.ts new file mode 100644 index 000000000..e771a77a3 --- /dev/null +++ b/packages/composite-layers/src/composite-layers/scatter-layer/index.ts @@ -0,0 +1,493 @@ +import { clone, isEqual, isUndefined } from '@antv/util'; +import Source from '@antv/l7-source'; +import { CompositeLayer } from '../../core/composite-layer'; +import { PointLayer } from '../../core-layers/point-layer'; +import { TextLayer } from '../../core-layers/text-layer'; +import { ICoreLayer, ISource, SourceOptions, MouseEvent } from '../../types'; +import { getDefaultState } from './adaptor'; +import { DEFAULT_OPTIONS, DEFAULT_STATE, EMPTY_SOURCE } from './constants'; +import { ScatterLayerOptions } from './types'; + +export type { ScatterLayerOptions }; + +export class ScatterLayer extends CompositeLayer { + /** + * 默认配置项 + */ + static DefaultOptions = DEFAULT_OPTIONS; + /** + * 复合图层类型 + */ + public type = CompositeLayer.LayerType.ScatterLayer; + /** + * 主图层 + */ + protected get layer() { + return this.fillLayer; + } + /** + * 填充图层 + */ + public get fillLayer() { + return this.subLayers.getLayer('fillLayer') as ICoreLayer; + } + /** + * 高亮描边图层 + */ + public get highlightStrokeLayer() { + return this.subLayers.getLayer('highlightStrokeLayer') as ICoreLayer; + } + /** + * 高亮数据 + */ + private highlightData: any; + /** + * 选中填充面图层 + */ + public get selectFillLayer() { + return this.subLayers.getLayer('selectFillLayer') as ICoreLayer; + } + /** + * 选中描边图层 + */ + public get selectStrokeLayer() { + return this.subLayers.getLayer('selectStrokeLayer') as ICoreLayer; + } + /** + * 选中数据 + */ + private selectData: { feature: any; featureId: number }[] = []; + /** + * 标注文本图层 + */ + public get labelLayer() { + return this.subLayers.getLayer('labelLayer') as ICoreLayer; + } + /** + * 图层交互状态配置 + */ + private layerState = DEFAULT_STATE; + /** + * 图层是否具有交互属性 + */ + public interaction = true; + + constructor(options: ScatterLayerOptions) { + super(options); + this.initSubLayersEvent(); + } + + /** + * 获取默认配置 + */ + public getDefaultOptions(): Partial { + return ScatterLayer.DefaultOptions; + } + + /** + * 创建子图层 + */ + protected createSubLayers() { + this.layerState = getDefaultState(this.options.state); + const sourceOptions = this.options.source; + const source = sourceOptions instanceof Source ? sourceOptions : this.createSource(sourceOptions); + + // 映射填充图层 + const fillLayer = new PointLayer({ + name: 'fillLayer', + shape: 'circle', + ...this.getFillLayerOptions(), + source, + }); + + // 高亮描边图层 + const highlightStrokeLayer = new PointLayer({ + name: 'highlightStrokeLayer', + shape: 'circle', + ...this.gethigHlightStrokeLayerOptions(), + }); + + // 选中填充图层 + const selectFillLayer = new PointLayer({ + name: 'selectFillLayer', + shape: 'circle', + ...this.getSelectFillLayerOptions(), + }); + + // 选中描边图层 + const selectStrokeLayer = new PointLayer({ + name: 'selectStrokeLayer', + shape: 'circle', + ...this.getSelectStrokeLayerOptions(), + }); + + // 标注图层 + const labelLayer = new TextLayer({ + name: 'labelLayer', + ...this.getTextLayerOptions(), + source, + }); + + const subLayers = [fillLayer, highlightStrokeLayer, selectFillLayer, selectStrokeLayer, labelLayer]; + + return subLayers; + } + + private getFillLayerOptions() { + const { + visible, + minZoom, + maxZoom, + zIndex = 0, + fillColor, + radius, + opacity, + strokeColor, + lineOpacity, + lineWidth, + ...baseConfig + } = this.options; + const defaultState = this.layerState; + + const fillState = { + active: defaultState.active.fillColor === false ? false : { color: defaultState.active.fillColor }, + select: false, + }; + const fillStyle = { + opacity: opacity, + stroke: strokeColor, + strokeOpacity: isUndefined(lineOpacity) ? opacity : lineOpacity, + strokeWidth: lineWidth, + }; + + const options = { + ...baseConfig, + visible, + minZoom, + maxZoom, + zIndex, + color: fillColor, + size: radius, + state: fillState, + style: fillStyle, + }; + + return options; + } + + private gethigHlightStrokeLayerOptions() { + const { visible, minZoom, maxZoom, zIndex = 0, radius } = this.options; + const defaultState = this.layerState; + const strokeStyle = { + opacity: 0, + stroke: defaultState.active.strokeColor || undefined, + strokeOpacity: defaultState.active.lineOpacity, + strokeWidth: defaultState.active?.lineWidth, + }; + + const options = { + visible: visible && Boolean(defaultState.active.strokeColor), + zIndex: zIndex + 0.1, + minZoom, + maxZoom, + source: EMPTY_SOURCE, + size: radius, + style: strokeStyle, + }; + + return options; + } + + private getSelectFillLayerOptions() { + const { visible, minZoom, maxZoom, zIndex = 0, radius, opacity } = this.options; + const defaultState = this.layerState; + const color = defaultState.select.fillColor || undefined; + const fillStyle = { opacity: opacity }; + + const option = { + visible: visible && Boolean(color), + zIndex: zIndex + 0.1, + minZoom, + maxZoom, + source: EMPTY_SOURCE, + color, + size: radius, + style: fillStyle, + state: { select: false, active: false }, + }; + + return option; + } + + private getSelectStrokeLayerOptions() { + const { visible, minZoom, maxZoom, zIndex = 0, radius } = this.options; + const defaultState = this.layerState; + const strokeStyle = { + opacity: 0, + stroke: defaultState.select.strokeColor || undefined, + strokeOpacity: defaultState.select.lineOpacity, + strokeWidth: defaultState.select.lineWidth, + }; + + const option = { + visible: visible && Boolean(strokeStyle.stroke), + zIndex: zIndex + 0.1, + minZoom, + maxZoom, + source: EMPTY_SOURCE, + size: radius, + style: strokeStyle, + }; + + return option; + } + + private getTextLayerOptions() { + const { visible, minZoom, maxZoom, zIndex = 0, label } = this.options; + const options = { + zIndex: zIndex + 0.1, + minZoom, + maxZoom, + ...label, + visible: visible && (label?.visible || Boolean(label)), + }; + + return options; + } + + /** + * 设置子图层数据 + */ + protected setSubLayersSource(source: SourceOptions | ISource) { + if (source instanceof Source) { + this.fillLayer.setSource(source); + this.labelLayer.setSource(source); + } else { + const layerSource = this.fillLayer.source; + const { data, ...option } = source; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + layerSource.setData(data, option); + } + + this.highlightStrokeLayer.changeData(EMPTY_SOURCE); + this.selectFillLayer.changeData(EMPTY_SOURCE); + this.selectStrokeLayer.changeData(EMPTY_SOURCE); + } + + /** + * 设置高亮描边子图层数据 + */ + protected setHighlightLayerSource(feature?: any, featureId = -999) { + if (this.highlightData === featureId) { + return; + } + const features = feature ? [feature] : []; + this.highlightStrokeLayer.changeData({ + data: features, + parser: this.fillLayer.source['parser'], + }); + this.highlightData = featureId; + } + + /** + * 设置选中描边与填充子图层数据 + */ + protected setSelectLayerSource(selectData: any[] = []) { + if ( + this.selectData.length === selectData.length && + isEqual( + this.selectData.map(({ featureId }) => featureId), + selectData.map(({ featureId }) => featureId) + ) + ) { + return; + } + const features = selectData.map(({ feature }) => feature); + this.selectFillLayer.changeData({ data: features, parser: this.fillLayer.source['parser'] }); + this.selectStrokeLayer.changeData({ data: features, parser: this.fillLayer.source['parser'] }); + this.selectData = selectData; + } + + /** + * 初始化子图层事件 + */ + protected initSubLayersEvent() { + // 初始化主图层交互事件 + this.fillLayer.off('mousemove', this.onHighlighHandle); + this.fillLayer.off('unmousemove', this.onHighlighHandle); + this.fillLayer.off('click', this.onSelectHandle); + this.selectData = []; + this.highlightData = null; + if (!this.options.state) return; + // active + if (this.options.state.active) { + this.fillLayer.on('mousemove', this.onHighlighHandle); + this.fillLayer.on('unmousemove', this.onUnhighlighHandle); + } + // select + if (this.options.state.select) { + this.fillLayer.on('click', this.onSelectHandle); + } + } + + /** + * 图层高亮回调 + */ + private onHighlighHandle = (event: MouseEvent) => { + const { feature, featureId } = event; + this.setHighlightLayerSource(feature, featureId); + }; + + /** + * 图层取消高亮回调 + */ + private onUnhighlighHandle = () => { + this.setHighlightLayerSource(); + }; + + /** + * 图层选中回调 + */ + private onSelectHandle = (event: MouseEvent) => { + const { feature, featureId } = event; + this.handleSelectData(featureId, feature); + }; + + private handleSelectData(featureId: number, feature: any) { + const enabledMultiSelect = this.options.enabledMultiSelect; + let selectData = clone(this.selectData); + const index = selectData.findIndex((item) => item.featureId === featureId); + + if (index === -1) { + if (enabledMultiSelect) { + selectData.push({ feature, featureId }); + } else { + selectData = [{ feature, featureId }]; + } + this.emit('select', feature, clone(selectData)); + } else { + const unselectFeature = selectData[index]; + if (enabledMultiSelect) { + selectData.splice(index, 1); + } else { + selectData = []; + } + this.emit('unselect', unselectFeature, clone(selectData)); + } + + this.setSelectLayerSource(selectData); + } + + /** + * 更新 + */ + public update(options: Partial) { + super.update(options); + + this.initSubLayersEvent(); + } + + /** + * 更新: 更新配置 + */ + public updateOption(options: Partial) { + super.update(options); + this.layerState = getDefaultState(this.options.state); + } + + /** + * 更新子图层 + */ + protected updateSubLayers(options: Partial) { + // 映射填充面图层 + this.fillLayer.update(this.getFillLayerOptions()); + + // 高亮图层 + this.highlightStrokeLayer.update(this.gethigHlightStrokeLayerOptions()); + + // 选中填充图层 + this.selectFillLayer.update(this.getSelectFillLayerOptions()); + + // 选中描边图层 + this.selectStrokeLayer.update(this.getSelectStrokeLayerOptions()); + + // 重置高亮/选中状态 + if (this.options.visible) { + if (!isUndefined(options.state) && !isEqual(this.lastOptions.state, this.options.state)) { + this.updateHighlightSubLayers(); + } + + if (this.layerState.active.strokeColor) { + this.setHighlightLayerSource(); + } + if (this.layerState.select.fillColor || this.layerState.select.strokeColor) { + this.setSelectLayerSource(); + } + } + } + + /** + * 更新高亮及选中子图层 + */ + private updateHighlightSubLayers() { + const defaultState = this.layerState; + const lasetDefaultState = getDefaultState(this.lastOptions.state); + + if (lasetDefaultState.active.strokeColor !== defaultState.active.strokeColor) { + defaultState.active.strokeColor ? this.highlightStrokeLayer.show() : this.highlightStrokeLayer.hide(); + } + + if (lasetDefaultState.select.fillColor !== defaultState.select.fillColor) { + defaultState.select.fillColor ? this.selectFillLayer.show() : this.selectFillLayer.hide(); + } + + if (lasetDefaultState.select.strokeColor !== defaultState.select.strokeColor) { + defaultState.select.strokeColor ? this.selectStrokeLayer.show() : this.selectStrokeLayer.hide(); + } + } + + public setIndex(zIndex: number) { + this.fillLayer.setIndex(zIndex); + this.highlightStrokeLayer.setIndex(zIndex + 0.1); + this.selectFillLayer.setIndex(zIndex + 0.1); + this.selectStrokeLayer.setIndex(zIndex + 0.1); + this.labelLayer.setIndex(zIndex + 0.1); + } + + public setActive(field: string, value: number | string) { + const source = this.fillLayer.source; + const featureId = source.getFeatureId(field, value); + if (isUndefined(featureId)) { + throw new Error('Feature non-existent' + field + value); + } + + if (this.layerState.active.fillColor) { + this.fillLayer.layer.setActive(featureId); + } + + if (this.layerState.active.strokeColor) { + const feature = source.getFeatureById(featureId); + this.setHighlightLayerSource(feature, featureId); + } + } + + public setSelect(field: string, value: number | string) { + const source = this.fillLayer.source; + const featureId = source.getFeatureId(field, value); + if (isUndefined(featureId)) { + throw new Error('Feature non-existent' + field + value); + } + + if (this.layerState.select.strokeColor === false || this.layerState.select.fillColor === false) { + return; + } + + const feature = source.getFeatureById(featureId); + this.handleSelectData(featureId, feature); + } + + public boxSelect(bounds: [number, number, number, number], callback: (...args: any[]) => void) { + this.fillLayer.boxSelect(bounds, callback); + } +} diff --git a/packages/composite-layers/src/composite-layers/scatter-layer/types.ts b/packages/composite-layers/src/composite-layers/scatter-layer/types.ts new file mode 100644 index 000000000..fce9a0b50 --- /dev/null +++ b/packages/composite-layers/src/composite-layers/scatter-layer/types.ts @@ -0,0 +1,61 @@ +import { PointLayerOptions } from '../../core-layers/point-layer/types'; +import { TextLayerOptions } from '../../core-layers/text-layer/types'; +import { CompositeLayerOptions } from '../../core/composite-layer'; +import { ISource, SourceOptions } from '../../types'; + +export type DotLayerActiveOptions = { + // 填充颜色 + fillColor?: false | string; + // 描边颜色 + strokeColor?: false | string; + // 描边的宽度 + lineWidth?: number; + // 描边透明度 + lineOpacity?: number; +}; + +export interface ScatterLayerOptions extends CompositeLayerOptions { + /** + * 具体的数据 + */ + source: SourceOptions | ISource; + /** + * 点半径 + */ + radius?: PointLayerOptions['size']; + /** + * 填充色 + */ + fillColor?: PointLayerOptions['color']; + /** + * 填充透明度 + */ + opacity?: number; + /** + * 描边线宽 + */ + lineWidth?: number; + /** + * 描边色 + */ + strokeColor?: string; + /** + * 描边透明度 + */ + lineOpacity?: number; + /** + * 文本标注 + */ + label?: Omit; + /** + * 交互反馈 + */ + state?: { + active?: boolean | DotLayerActiveOptions; + select?: boolean | DotLayerActiveOptions; + }; + /** + * 是否启用多选 + */ + enabledMultiSelect?: boolean; +} diff --git a/packages/composite-layers/src/core-layers/heatmap-layer/index.ts b/packages/composite-layers/src/core-layers/heatmap-layer/index.ts new file mode 100644 index 000000000..00f06eee6 --- /dev/null +++ b/packages/composite-layers/src/core-layers/heatmap-layer/index.ts @@ -0,0 +1,25 @@ +import { HeatmapLayer as L7HeatmapLayer } from '@antv/l7-layers'; +import { CoreLayer } from '../../core/core-layer'; +import { ILayer } from '../../types'; +import { HeatmapLayerOptions } from './types'; + +export type { HeatmapLayerOptions }; + +/** + * 热力图层 + * 对应 L7 的 HeatmapLayer + **/ +export class HeatmapLayer extends CoreLayer { + public type = 'heatmapLayer'; + + /** + * 创建图层 + */ + protected createLayer(): ILayer { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { source, shape, size, ...config } = this.options; + const layer = new L7HeatmapLayer(config); + + return layer; + } +} diff --git a/packages/composite-layers/src/core-layers/heatmap-layer/types.ts b/packages/composite-layers/src/core-layers/heatmap-layer/types.ts new file mode 100644 index 000000000..deb074eba --- /dev/null +++ b/packages/composite-layers/src/core-layers/heatmap-layer/types.ts @@ -0,0 +1,74 @@ +import { IColorRamp } from '@antv/l7-utils'; +import { CoreLayerOptions } from '../../core/core-layer'; + +/** + * 热力普通图层 色带 + */ +// export type ColorRamp = { color: string; position: number }[]; + +/** + * 热力普通图层 图层样式 + */ +export type HeatmapLayerStyleOptions = { + // 透明度 + opacity?: number; + // 旋转角度 + angle?: number; + // 全局热力权重,推荐权重范围 1-5 + intensity: number; + // 热力半径,单位像素 + radius: number; + // 色带 + // colorsRamp: ColorRamp; + // L7 原色带 + rampColors?: IColorRamp; +}; + +/** + * 热力网格图/蜂窝图层 图层样式 + */ +export type GridHeatmapLayerStyleOptions = { + // 透明度 + opacity?: number; + // 旋转角度 + angle?: number; + // 覆盖度 + coverage?: number; +}; + +/** + * 热力图层 图形形状 + */ +export type HeatmapShape2d = 'circle' | 'square' | 'hexagon' | 'triangle'; + +export type HeatmapShape3d = 'cylinder' | 'squareColumn' | 'hexagonColumn' | 'triangleColumn'; + +export type HeatmapShape = 'heatmap' | 'heatmap3D' | HeatmapShape2d | HeatmapShape3d; + +/** + * 热力图层基础配置 + */ +export interface HeatmapLayerConfig { + /** + * 图形形状 + */ + shape?: HeatmapShape; + /** + * 图层样式 + */ + style?: HeatmapLayerStyleOptions | GridHeatmapLayerStyleOptions; +} + +/** + * 线图层配置 + */ +export interface HeatmapLayerOptions extends CoreLayerOptions { + /** + * 图形形状 + */ + shape?: HeatmapShape; + /** + * 图层样式 + */ + style?: HeatmapLayerStyleOptions | GridHeatmapLayerStyleOptions; +} diff --git a/packages/composite-layers/src/core-layers/line-layer/index.ts b/packages/composite-layers/src/core-layers/line-layer/index.ts new file mode 100644 index 000000000..0b5b5213a --- /dev/null +++ b/packages/composite-layers/src/core-layers/line-layer/index.ts @@ -0,0 +1,25 @@ +import { LineLayer as L7LineLayer } from '@antv/l7-layers'; +import { CoreLayer } from '../../core/core-layer'; +import { ILayer } from '../../types'; +import { LineLayerOptions } from './types'; + +export type { LineLayerOptions }; + +/** + * 线图层 + * 对应 L7 的 LineLayer + **/ +export class LineLayer extends CoreLayer { + public type = 'lineLayer'; + + /** + * 创建图层 + */ + protected createLayer(): ILayer { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { source, shape, size, ...config } = this.options; + const layer = new L7LineLayer(config); + + return layer; + } +} diff --git a/packages/composite-layers/src/core-layers/line-layer/types.ts b/packages/composite-layers/src/core-layers/line-layer/types.ts new file mode 100644 index 000000000..b66059082 --- /dev/null +++ b/packages/composite-layers/src/core-layers/line-layer/types.ts @@ -0,0 +1,59 @@ +import { CoreLayerOptions } from '../../core/core-layer'; +import { ShapeAttr } from '../../types'; + +/** + * 线图层 图形形状 + */ +export type ArcLineShape = 'arc' | 'arc3d' | 'greatcircle'; + +export type LineShape = 'line' | ArcLineShape; + +/** + * 线图层 线类型 + */ +export enum LineStyleType { + 'solid' = 0.0, + 'dash' = 1.0, +} + +/** + * 线图层 图层样式 + */ +export type LinesLayerStyleOptions = { + // 透明度 + opacity?: number | [string, (data: any) => number] | [string, [number, number]]; + // 线类型 + lineType?: keyof typeof LineStyleType; + // 虚线间隔 + dashArray?: [number, number]; + // 弧线分段数 + segmentNumber?: number; + // 渐变起点颜色 + sourceColor?: string; + // 渐变终点颜色 + targetColor?: string; + // 是否反向,arc 支持 + forward?: boolean; + // 弧线的偏移量,arc 支持 + thetaOffset?: number; + // 是否开启纹理贴图 + lineTexture?: boolean; + // 纹理贴图步长 + iconStep?: number; + // 纹理混合方式 + textureBlend?: string; +}; + +/** + * 线图层配置 + */ +export interface LineLayerOptions extends CoreLayerOptions { + /** + * 图形形状 + */ + shape?: ShapeAttr; + /** + * 图层样式 + */ + style?: LinesLayerStyleOptions; +} diff --git a/packages/composite-layers/src/core-layers/point-layer/index.ts b/packages/composite-layers/src/core-layers/point-layer/index.ts new file mode 100644 index 000000000..6eb710940 --- /dev/null +++ b/packages/composite-layers/src/core-layers/point-layer/index.ts @@ -0,0 +1,25 @@ +import { PointLayer as L7PointLayer } from '@antv/l7-layers'; +import { CoreLayer } from '../../core/core-layer'; +import { ILayer } from '../../types'; +import { PointLayerOptions } from './types'; + +export type { PointLayerOptions }; + +/** + * 点图层 + * 对应 L7 的 PointLayer + **/ +export class PointLayer extends CoreLayer { + public type = 'pointLayer'; + + /** + * 创建图层 + */ + protected createLayer(): ILayer { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { source, shape, size, ...config } = this.options; + const layer = new L7PointLayer(config); + + return layer; + } +} diff --git a/packages/composite-layers/src/core-layers/point-layer/types.ts b/packages/composite-layers/src/core-layers/point-layer/types.ts new file mode 100644 index 000000000..d4331ddcd --- /dev/null +++ b/packages/composite-layers/src/core-layers/point-layer/types.ts @@ -0,0 +1,95 @@ +import { CoreLayerOptions } from '../../core/core-layer'; +import { ShapeAttr } from '../../types'; + +/** + * 点图层 图层样式 + */ +export type PointLayerStyleOptions = { + opacity?: number; + strokeWidth?: number; + stroke?: string; +}; + +/** + * 点图层 图形形状 + */ +export type PointShape2d = + | 'circle' + | 'square' + | 'hexagon' + | 'triangle' + | 'pentagon' + | 'octogon' + | 'hexagram' + | 'rhombus' + | 'vesica' + | 'dot'; + +export type PointShape3d = 'cylinder' | 'triangleColumn' | 'hexagonColumn' | 'squareColumn'; + +export type PointShape = PointShape2d | PointShape3d | 'text'; + +/** + * 点图层 文本相对锚点 + */ +export type AnchorType = + | 'right' + | 'top-right' + | 'left' + | 'bottom-right' + | 'left' + | 'top-left' + | 'bottom-left' + | 'bottom' + | 'bottom-right' + | 'bottom-left' + | 'top' + | 'top-right' + | 'top-left' + | 'center'; + +/** + * 点图层 文本样式 + */ +export type PointTextLayerStyleOptions = { + /* 透明度 */ + opacity?: number; + /* 文本相对锚点的位置 */ + textAnchor?: AnchorType; + /* 文本相对锚点的偏移量 */ + textOffset?: [number, number]; + /* 字符间距 */ + spacing?: number; + /* 文本包围盒 padding [水平,垂直],影响碰撞检测结果,避免相邻文本靠的太近 */ + padding?: [number, number]; + // TODO:注释 + halo?: number; + // TODO:注释 + gamma?: number; + /* 描边颜色 */ + stroke?: string; + /* 描边宽度 */ + strokeWidth?: number; + /* 描边透明度 */ + strokeOpacity?: number; + /* 字体 */ + fontFamily?: string; + /* 字体的粗细程度 */ + fontWeight?: string; + /* 是否换行 */ + textAllowOverlap?: boolean; +}; + +/** + * 点图层配置 + */ +export interface PointLayerOptions extends CoreLayerOptions { + /** + * 图形形状 + */ + shape?: ShapeAttr; + /** + * 图层样式 + */ + style?: PointLayerStyleOptions | PointTextLayerStyleOptions; +} diff --git a/packages/composite-layers/src/core-layers/polygon-layer/index.ts b/packages/composite-layers/src/core-layers/polygon-layer/index.ts new file mode 100644 index 000000000..10552660a --- /dev/null +++ b/packages/composite-layers/src/core-layers/polygon-layer/index.ts @@ -0,0 +1,25 @@ +import { PolygonLayer as L7PolygonLayer } from '@antv/l7-layers'; +import { CoreLayer } from '../../core/core-layer'; +import { ILayer } from '../../types'; +import { PolygonLayerOptions } from './types'; + +export type { PolygonLayerOptions }; + +/** + * 面图层 + * 对应 L7 的 PolygonLayer + **/ +export class PolygonLayer extends CoreLayer { + public type = 'polygonLayer'; + + /** + * 创建图层 + */ + protected createLayer(): ILayer { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { source, shape, size, ...config } = this.options; + const layer = new L7PolygonLayer(config); + + return layer; + } +} diff --git a/packages/composite-layers/src/core-layers/polygon-layer/types.ts b/packages/composite-layers/src/core-layers/polygon-layer/types.ts new file mode 100644 index 000000000..dea8731e9 --- /dev/null +++ b/packages/composite-layers/src/core-layers/polygon-layer/types.ts @@ -0,0 +1,27 @@ +import { CoreLayerOptions } from '../../core/core-layer'; + +/** + * 面图层 图形形状 + */ +export type PolygonShape = 'fill' | 'line' | 'extrude'; + +/** + * 面图层 图层样式 + */ +export type PolygonLayerStyleOptions = { + opacity?: number; +}; + +/** + * 面图层配置 + */ +export interface PolygonLayerOptions extends CoreLayerOptions { + /** + * 图形形状 + */ + shape?: PolygonShape; + /** + * 图层样式 + */ + style?: PolygonLayerStyleOptions; +} diff --git a/packages/composite-layers/src/core-layers/text-layer/index.ts b/packages/composite-layers/src/core-layers/text-layer/index.ts new file mode 100644 index 000000000..f91257cc9 --- /dev/null +++ b/packages/composite-layers/src/core-layers/text-layer/index.ts @@ -0,0 +1,36 @@ +import { PointLayer as L7PointLayer } from '@antv/l7-layers'; +import { CoreLayer } from '../../core/core-layer'; +import { ILayer } from '../../types'; +import { TextLayerOptions } from './types'; + +export type { TextLayerOptions }; + +/** + * 文本图层 + * 对应 L7 的 PointLayer + **/ +export class TextLayer extends CoreLayer { + public type = 'textLayer'; + + /** + * 创建图层 + */ + protected createLayer(): ILayer { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { source, field, ...config } = this.options; + const layer = new L7PointLayer(config); + + return layer; + } + + /** + * 适配属性配置 + */ + protected adaptorAttrOptions(options: TextLayerOptions) { + const { field, style = {} } = this.options; + const { fill: color, fontSize: size } = style || {}; + const shape = { field, value: 'text' }; + + return { shape, color, size, ...options }; + } +} diff --git a/packages/composite-layers/src/core-layers/text-layer/types.ts b/packages/composite-layers/src/core-layers/text-layer/types.ts new file mode 100644 index 000000000..3f3a6b529 --- /dev/null +++ b/packages/composite-layers/src/core-layers/text-layer/types.ts @@ -0,0 +1,31 @@ +import { ColorAttr, SizeAttr } from '../../types'; +import { CoreLayerOptions } from '../../core/core-layer'; +import { PointTextLayerStyleOptions } from '../point-layer/types'; + +/** + * 文本图层 文本样式 + */ +export type TextLayerStyleOptions = PointTextLayerStyleOptions & { + /* 字体颜色 */ + fill?: ColorAttr; + /* 字体大小 */ + fontSize?: SizeAttr; +}; + +/** + * 文本图层配置 + */ +export interface TextLayerOptions extends Omit { + /** 映射的字段 */ + field?: string; + // TODO: 多字段支持 + // fields?: string[]; + /** 映射文本回调函数 */ + // content?: string; + // TODO: 多字段 CallBack 支持 + // content?: string | ((data: Record) => string); + /** + * 字体样式 + */ + style?: TextLayerStyleOptions; +} diff --git a/packages/composite-layers/src/core/composite-layer.ts b/packages/composite-layers/src/core/composite-layer.ts index b7a75d373..77158e076 100644 --- a/packages/composite-layers/src/core/composite-layer.ts +++ b/packages/composite-layers/src/core/composite-layer.ts @@ -1,13 +1,10 @@ -import { isEqual, isUndefined, pick, deepMix, uniqueId } from '@antv/util'; -import Source from '@antv/l7-source'; +import { deepMix, uniqueId } from '@antv/util'; import EventEmitter from '@antv/event-emitter'; -import { Scene, ILayer, ILayerConfig, SourceOptions, ICompositeLayer, LayerType, LayerBlend } from '../types'; -import { MappingSource } from '../adaptor/source'; +import Source from '@antv/l7-source'; +import { Scene, SourceOptions, ICompositeLayer, CompositeLayerType, LayerBlend, ICoreLayer, ISource } from '../types'; import { LayerEventList } from './constants'; import { LayerGroup } from './layer-group'; -const LayerBaseConfigkeys = ['name', 'zIndex', 'visible', 'minZoom', 'maxZoom', 'pickingBuffer', 'autoFit', 'blend']; - /** * 复合图层的基础配置 */ @@ -27,11 +24,15 @@ export abstract class CompositeLayer extends Ev /** * 复合图层类型 */ - static LayerType = LayerType; + static LayerType = CompositeLayerType; /** * 默认的 options 配置项 */ static DefaultOptions: Partial = {}; + /** + * 是否是复合图层 + */ + public readonly isComposite = true; /** * 复合图层名称 */ @@ -39,7 +40,7 @@ export abstract class CompositeLayer extends Ev /** * 复合图层类型 */ - public abstract readonly type: LayerType | string; + public abstract readonly type: CompositeLayerType | string; /** * 复合图层的 schema 配置 */ @@ -55,7 +56,7 @@ export abstract class CompositeLayer extends Ev /** * 主子图层实例 */ - public abstract readonly layer: ILayer; + protected abstract readonly layer: ICoreLayer; /** * 图层是否具有交互效果,用于 tooltip */ @@ -63,21 +64,17 @@ export abstract class CompositeLayer extends Ev /** * 子图层组 */ - protected subLayers: LayerGroup; + public subLayers: LayerGroup; constructor(options: O) { super(); - const { name, source } = options; + const { name } = options; this.name = name ? name : uniqueId('composite-layer'); this.options = deepMix({}, this.getDefaultOptions(), options); this.lastOptions = this.options; - const subLayers = this.createSubLayers(); - this.subLayers = new LayerGroup(subLayers); - this.adaptorSubLayersAttr(); - - this.setSubLayersSource(source); - // this.initEvent(); + const layers = this.createSubLayers(); + this.subLayers = new LayerGroup(layers); } /** @@ -88,39 +85,24 @@ export abstract class CompositeLayer extends Ev } /** - * 获取子主图层基础配置项 + * 创建 source 实例 */ - protected pickLayerBaseConfig(): Partial { - const config = pick(this.options, LayerBaseConfigkeys); - return config; + protected createSource(sourceOptions: SourceOptions) { + const { data, ...sourceCFG } = sourceOptions; + const source = new Source(data, sourceCFG); + return source; } /** * 创建子图层 */ - protected abstract createSubLayers(): ILayer[]; - - /** - * 映射子图层属性 - */ - protected abstract adaptorSubLayersAttr(): void; + protected abstract createSubLayers(): ICoreLayer[]; /** * 设置子图层数据 */ - protected setSubLayersSource(source: SourceOptions | Source) { - if (source instanceof Source) { - this.layer.setSource(source); - } else { - const { data, aggregation, ...option } = source; - aggregation && MappingSource.aggregation(option, aggregation); - const layerSource = this.layer.getSource(); - if (layerSource) { - this.layer.setData(data, option); - } else { - this.layer.source(data, option); - } - } + protected setSubLayersSource(source: SourceOptions | ISource) { + this.layer.changeData(source); } /** @@ -151,7 +133,7 @@ export abstract class CompositeLayer extends Ev */ public update(options: Partial) { this.updateOption(options); - this.updateConfig(options); + this.updateSubLayers(options); } /** @@ -162,28 +144,10 @@ export abstract class CompositeLayer extends Ev this.options = deepMix({}, this.options, options); } - // 更新: 更新图层属性配置 - public updateConfig(options: Partial) { - if (!isUndefined(options.zIndex) && !isEqual(this.lastOptions.zIndex, this.options.zIndex)) { - this.setIndex(options.zIndex); - } - - if (!isUndefined(options.blend) && !isEqual(this.lastOptions.blend, this.options.blend)) { - this.setBlend(options.blend); - } - - if (!isUndefined(options.minZoom) && !isEqual(this.lastOptions.minZoom, this.options.minZoom)) { - this.setMinZoom(options.minZoom); - } - - if (!isUndefined(options.maxZoom) && !isEqual(this.lastOptions.maxZoom, this.options.maxZoom)) { - this.setMinZoom(options.maxZoom); - } - - if (!isUndefined(options.visible) && !isEqual(this.lastOptions.visible, this.options.visible)) { - options.visible ? this.show() : this.hide(); - } - } + /** + * 更新子图层 + */ + protected abstract updateSubLayers(options: Partial): void; public render() { if (this.scene) { @@ -191,7 +155,7 @@ export abstract class CompositeLayer extends Ev } } - public changeData(source: SourceOptions | Source) { + public changeData(source: SourceOptions) { this.setSubLayersSource(source); } @@ -241,7 +205,7 @@ export abstract class CompositeLayer extends Ev this.layer.fitBounds(fitBoundsOptions); } - public getlegenditems(type: string): Record[] { + public getLegendItems(type: string): Record[] { return this.layer.getLegendItems(type); } diff --git a/packages/composite-layers/src/core/core-layer.ts b/packages/composite-layers/src/core/core-layer.ts new file mode 100644 index 000000000..583af571a --- /dev/null +++ b/packages/composite-layers/src/core/core-layer.ts @@ -0,0 +1,334 @@ +import EventEmitter from '@antv/event-emitter'; +import { deepMix, isEqual, isUndefined, uniqueId } from '@antv/util'; +import { + ICoreLayer, + ILayer, + LayerBlend, + LayerBaseConfig, + Scene, + SourceOptions, + ShapeAttr, + ColorAttr, + SizeAttr, + ScaleAttr, + AnimateAttr, + StateAttribute, + TextureAttr, + ISource, +} from '../types'; +import Source from '@antv/l7-source'; +import { LayerEventList } from './constants'; +import { MappingAttribute } from '../adaptor/attribute'; + +/** + * 核心图层的基础配置 + */ +export interface CoreLayerOptions extends Partial { + /** + * 数据 + */ + source: SourceOptions | Source; + /** + * 图形形状 + */ + shape?: ShapeAttr; + /** + * 图形颜色 + */ + color?: ColorAttr; + /** + * 图形大小 + */ + size?: SizeAttr; + /** + * 比例尺 + */ + scale?: ScaleAttr; + /** + * 纹理贴图 + */ + texture?: TextureAttr; + /** + * 图层样式 + */ + style?: Record; + /** + * animation 配置 + */ + animate?: AnimateAttr; + /** + * 交互反馈 + */ + state?: StateAttribute; +} + +export abstract class CoreLayer extends EventEmitter implements ICoreLayer { + /** + * 默认的 options 配置项 + */ + static DefaultOptions: Partial = {}; + /** + * 是否是复合图层 + */ + public readonly isComposite = false; + /** + * 图层名称 + */ + public readonly name: string; + /** + * 图层类型 + */ + public abstract readonly type: string; + /** + * 图层 schema 配置 + */ + public options: O; + /** + * 图层上一次的 schema 配置 + */ + public lastOptions: O; + /** + * Scene 实例 + */ + protected scene: Scene | undefined; + /** + * 图层实例 + */ + public readonly layer: ILayer; + /** + * 图层是否初始化成功 + */ + public get inited() { + return this.layer.inited; + } + /** + * 图层的 source 实例 + */ + public get source() { + return this.layer.getSource(); + } + + constructor(options: O) { + super(); + const { name, source } = options; + this.name = name ? name : uniqueId('core-layer'); + this.options = deepMix({}, this.getDefaultOptions(), options); + this.lastOptions = this.options; + this.layer = this.createLayer(); + + this.adaptorLayerAttr(); + this.setSource(source); + } + + /** + * 获取默认配置 + */ + public getDefaultOptions(): Partial { + return CoreLayer.DefaultOptions; + } + + /** + * 创建图层 + */ + protected abstract createLayer(): ILayer; + + /** + * 适配属性配置 + */ + protected adaptorAttrOptions(options: O): CoreLayerOptions { + return options; + } + + /** + * 映射图层属性 + */ + protected adaptorLayerAttr(): void { + const { shape, color, size, scale, texture, style, animate, state } = this.adaptorAttrOptions(this.options); + // mapping shape + shape && MappingAttribute.shape(this.layer, shape); + // mapping size + size && MappingAttribute.size(this.layer, size); + // mapping color + color && MappingAttribute.color(this.layer, color); + // mapping scale + scale && MappingAttribute.scale(this.layer, scale); + // mapping texture + texture && MappingAttribute.texture(this.layer, texture); + // mapping style + style && MappingAttribute.style(this.layer, style); + // mapping animate + animate && MappingAttribute.animate(this.layer, animate); + // mapping state + state && MappingAttribute.state(this.layer, state); + } + + /** + * 设置图层数据 + */ + public setSource(source: SourceOptions | ISource) { + if (source instanceof Source) { + this.layer.setSource(source); + } else { + const { data, ...option } = source; + const layerSource = this.layer.getSource(); + if (layerSource) { + this.layer.setData(data, option); + } else { + this.layer.source(data, option); + } + } + } + + /** + * 添加到场景 + */ + public addTo(scene: Scene) { + this.scene = scene; + scene.addLayer(this.layer); + } + + /** + * 从场景移除 + */ + public remove() { + if (!this.scene) return; + this.scene.removeLayer(this.layer); + } + + /** + * 更新 + */ + public update(options: Partial) { + this.updateOption(options); + this.updateConfig(options); + + this.adaptorLayerAttr(); + } + + /** + * 更新: 更新配置 + */ + public updateOption(options: Partial) { + this.lastOptions = this.options; + this.options = deepMix({}, this.options, options); + } + + // 更新: 更新图层属性配置 + public updateConfig(options: Partial) { + if (!isUndefined(options.zIndex) && !isEqual(this.lastOptions.zIndex, this.options.zIndex)) { + this.setIndex(options.zIndex); + } + + if (!isUndefined(options.blend) && !isEqual(this.lastOptions.blend, this.options.blend)) { + this.setBlend(options.blend); + } + + if (!isUndefined(options.minZoom) && !isEqual(this.lastOptions.minZoom, this.options.minZoom)) { + this.setMinZoom(options.minZoom); + } + + if (!isUndefined(options.maxZoom) && !isEqual(this.lastOptions.maxZoom, this.options.maxZoom)) { + this.setMinZoom(options.maxZoom); + } + + if (!isUndefined(options.visible) && !isEqual(this.lastOptions.visible, this.options.visible)) { + options.visible ? this.show() : this.hide(); + } + } + + public render() { + if (this.scene) { + this.scene.render(); + } + } + + public changeData(source: SourceOptions) { + this.setSource(source); + } + + public setIndex(zIndex: number) { + this.layer.setIndex(zIndex); + } + + public setBlend(blend: LayerBlend) { + this.layer.setBlend(blend); + } + + public setMinZoom(minZoom: number) { + this.layer.setMinZoom(minZoom); + } + + public setMaxZoom(maxZoom: number) { + this.layer.setMaxZoom(maxZoom); + } + + public show() { + if (!this.layer.inited) return; + this.layer.show(); + } + + public hide() { + if (!this.layer.inited) return; + this.layer.hide(); + } + + public toggleVisible() { + this.isVisible() ? this.hide() : this.show(); + } + + public isVisible() { + return this.layer.inited ? this.layer.isVisible() : this.options.visible || false; + } + + public boxSelect(bounds: [number, number, number, number], callback: (...args: any[]) => void) { + this.layer.boxSelect(bounds, callback); + } + + public fitBounds(fitBoundsOptions?: unknown) { + this.layer.fitBounds(fitBoundsOptions); + } + + public getLegendItems(type: string): Record[] { + return this.layer.getLegendItems(type); + } + + /** + * 事件代理: 绑定事件 + */ + public on(name: string, callback: (...args: any[]) => void) { + if (LayerEventList.indexOf(name) !== -1) { + this.layer.on(name, callback); + } else { + super.on(name, callback); + } + return this; + } + + /** + * 事件代理: 绑定一次事件 + */ + public once(name: string, callback: (...args: any[]) => void) { + if (LayerEventList.indexOf(name) !== -1) { + this.layer.once(name, callback); + } else { + super.once(name, callback); + } + return this; + } + + /** + * 事件代理: 解绑事件 + */ + public off(name: string, callback: (...args: any[]) => void) { + if (LayerEventList.indexOf(name) !== -1) { + this.layer.off(name, callback); + } else { + super.off(name, callback); + } + return this; + } + + public destroy() { + this.layer.destroy(); + } +} diff --git a/packages/composite-layers/src/core/layer-group.ts b/packages/composite-layers/src/core/layer-group.ts index 425c0470b..834eb0cd1 100644 --- a/packages/composite-layers/src/core/layer-group.ts +++ b/packages/composite-layers/src/core/layer-group.ts @@ -1,6 +1,6 @@ import { uniqueId } from '@antv/util'; import EventEmitter from '@antv/event-emitter'; -import { ILayer, Scene, ILayerGroup } from '../types'; +import { Scene, ILayerGroup, ICoreLayer } from '../types'; export type LayerGroupOptions = { name?: string; @@ -14,13 +14,13 @@ export class LayerGroup extends EventEmitter implements ILayerGroup { /** * 子图层 */ - private layerMap = new Map(); + private layerMap = new Map(); /** * 地图容器 */ private scene: Scene | undefined; - constructor(layers: ILayer[] = [], option: LayerGroupOptions = {}) { + constructor(layers: ICoreLayer[] = [], option: LayerGroupOptions = {}) { super(); this.name = option.name ? option.name : uniqueId('layerGroup'); for (let index = 0; index < layers.length; index++) { @@ -44,7 +44,7 @@ export class LayerGroup extends EventEmitter implements ILayerGroup { this.emit('inited-layers'); } }); - scene.addLayer(layer); + layer.addTo(scene); } } @@ -60,7 +60,7 @@ export class LayerGroup extends EventEmitter implements ILayerGroup { /** * 图层组是否有该图层 */ - public hasLayer(layer: ILayer): boolean { + public hasLayer(layer: string | ICoreLayer): boolean { const layerId = typeof layer === 'string' ? layer : this.getLayerId(layer); return this.layerMap.has(layerId); } @@ -68,21 +68,21 @@ export class LayerGroup extends EventEmitter implements ILayerGroup { /** * 添加图层 */ - public addLayer(layer: ILayer) { + public addLayer(layer: ICoreLayer) { const layerId = this.getLayerId(layer); this.layerMap.set(layerId, layer); if (this.scene) { layer.once('inited', (e) => this.emit('inited-layer', e)); - this.scene.addLayer(layer); + layer.addTo(this.scene); } } /** * 添加多个图层 */ - public addLayers(layers: ILayer[]) { + public addLayers(layers: ICoreLayer[]) { layers.forEach((layer) => { this.addLayer(layer); }); @@ -91,7 +91,7 @@ export class LayerGroup extends EventEmitter implements ILayerGroup { /** * 根据图层 id 或图层实例移除 layer 图层 */ - public removeLayer(layer: string | ILayer): boolean { + public removeLayer(layer: string | ICoreLayer): boolean { const layerId = typeof layer === 'string' ? layer : this.getLayerId(layer); const findLayer = this.layerMap.get(layerId); @@ -99,7 +99,7 @@ export class LayerGroup extends EventEmitter implements ILayerGroup { this.layerMap.delete(layerId); if (this.scene) { - this.scene.removeLayer(findLayer); + findLayer.remove(); } return true; } @@ -107,21 +107,21 @@ export class LayerGroup extends EventEmitter implements ILayerGroup { /** * 获取图层组所有的图层 */ - public getLayers(): ILayer[] { + public getLayers(): ICoreLayer[] { return Array.from(this.layerMap.values()); } /** * 根据图层 ID 获取图层 */ - public getLayer(id: string): ILayer | undefined { + public getLayer(id: string): ICoreLayer | undefined { return this.layerMap.get(id); } /** * 根据图层 name 获取图层 */ - public getLayerByName(name: string): ILayer | undefined { + public getLayerByName(name: string): ICoreLayer | undefined { return this.getLayers().find((itemLayer) => itemLayer.name === name); } @@ -131,7 +131,7 @@ export class LayerGroup extends EventEmitter implements ILayerGroup { public removeAllLayer() { for (const layer of this.layerMap.values()) { if (this.scene) { - this.scene.removeLayer(layer); + layer.remove(); } } this.layerMap.clear(); @@ -156,12 +156,12 @@ export class LayerGroup extends EventEmitter implements ILayerGroup { /** * 根据图层获取图层 ID */ - public getLayerId(layer: ILayer) { + public getLayerId(layer: ICoreLayer) { if ('name' in layer) { return layer.name; } - return layer.id; + return layer.layer.id; } /** diff --git a/packages/composite-layers/src/index.ts b/packages/composite-layers/src/index.ts index db40bcb98..279a7009d 100644 --- a/packages/composite-layers/src/index.ts +++ b/packages/composite-layers/src/index.ts @@ -7,6 +7,20 @@ export * from './types'; // 图层组及类型定义 | author by [yunji]](https://github.com/lvisei) export { LayerGroup, LayerGroupOptions } from './core/layer-group'; +/** 核心图层 **/ +// 点图层及类型定义 | author by [yunji]](https://github.com/lvisei) +export { PointLayer, PointLayerOptions } from './core-layers/point-layer'; +// 文本图层及类型定义 | author by [yunji]](https://github.com/lvisei) +export { TextLayer, TextLayerOptions } from './core-layers/text-layer'; +// 热力图层及类型定义 | author by [yunji]](https://github.com/lvisei) +export { HeatmapLayer, HeatmapLayerOptions } from './core-layers/heatmap-layer'; +// 线图层及类型定义 | author by [yunji]](https://github.com/lvisei) +export { LineLayer, LineLayerOptions } from './core-layers/line-layer'; +// 面图层及类型定义 | author by [yunji]](https://github.com/lvisei) +export { PolygonLayer, PolygonLayerOptions } from './core-layers/polygon-layer'; + /** 复合图层 **/ +// 散点图层及类型定义 | author by [yunji]](https://github.com/lvisei) +export { ScatterLayer, ScatterLayerOptions } from './composite-layers/scatter-layer'; // 区域图层及类型定义 | author by [yunji]](https://github.com/lvisei) -export { AreaLayer, AreaLayerOptions } from './layers/area-layer'; +export { AreaLayer, AreaLayerOptions } from './composite-layers/area-layer'; diff --git a/packages/composite-layers/src/layers/area-layer/adaptor.ts b/packages/composite-layers/src/layers/area-layer/adaptor.ts deleted file mode 100644 index 554148db8..000000000 --- a/packages/composite-layers/src/layers/area-layer/adaptor.ts +++ /dev/null @@ -1,155 +0,0 @@ -import { isUndefined } from '@antv/util'; -import { MappingAttribute } from '../../adaptor/attribute'; -import { ILayer } from '../../types'; -import { DEFAULT_STATE } from './constants'; -import { AreaLayerOptions } from './types'; - -export const getDefaultState = (state?: AreaLayerOptions['state']) => { - if (isUndefined(state)) { - return DEFAULT_STATE; - } - - if (state.active === false) { - DEFAULT_STATE.active = Object.assign(DEFAULT_STATE.active, { fill: false, stroke: false }); - } else if (typeof state.active === 'object') { - if (state.active.fill === false) { - DEFAULT_STATE.active.fill = false; - } else if (typeof state.active.fill === 'string') { - DEFAULT_STATE.active.fill = state.active.fill; - } - - if (state.active.stroke === false) { - DEFAULT_STATE.active.stroke = false; - } else if (typeof state.active.stroke === 'string') { - DEFAULT_STATE.active.stroke = state.active.stroke; - } - - if (typeof state.active.lineWidth === 'number') { - DEFAULT_STATE.active.lineWidth = state.active.lineWidth; - } - if (typeof state.active.lineOpacity === 'number') { - DEFAULT_STATE.active.lineOpacity = state.active.lineOpacity; - } - } - - if (state.select === false) { - DEFAULT_STATE.select = Object.assign(DEFAULT_STATE.select, { fill: false, stroke: false }); - } else if (typeof state.select === 'object') { - if (state.select.fill === false) { - DEFAULT_STATE.select.fill = false; - } else if (typeof state.select.fill === 'string') { - DEFAULT_STATE.select.fill = state.select.fill; - } - - if (state.select.stroke === false) { - DEFAULT_STATE.select.stroke = false; - } else if (typeof state.select.stroke === 'string') { - DEFAULT_STATE.select.stroke = state.select.stroke; - } - - if (typeof state.select.lineWidth === 'number') { - DEFAULT_STATE.select.lineWidth = state.select.lineWidth; - } - if (typeof state.select.lineOpacity === 'number') { - DEFAULT_STATE.select.lineOpacity = state.select.lineOpacity; - } - } - - return DEFAULT_STATE; -}; - -export function mappingLayersAttr( - layer: ILayer, - strokeLayer: ILayer, - highlightLayer: ILayer, - selectFillLayer: ILayer, - selectStrokeLayer: ILayer, - options: AreaLayerOptions -): void { - const { color, style, state } = options; - const defaultState = getDefaultState(state); - - const fillState = { - active: defaultState.active.fill === false ? false : { color: defaultState.active.fill }, - select: false, - }; - const fillStyle = { opacity: style?.opacity }; - const fillBottomColor = style?.fillBottomColor; - const strokeSize = style?.lineWidth; - const strokeColor = style?.stroke; - const strokeStyle = { opacity: style?.lineOpacity, dashArray: style?.lineDash, lineType: style?.lineType }; - - /** - * 映射填充面图层 - */ - // shape - MappingAttribute.shape(layer, 'fill'); - // color - color && MappingAttribute.color(layer, color); - // style - fillStyle && MappingAttribute.style(layer, fillStyle); - // state - fillState && MappingAttribute.state(layer, fillState); - // bottomColor - fillBottomColor && layer.setBottomColor(fillBottomColor); - - /** - * 描边图层 - */ - // shape - MappingAttribute.shape(strokeLayer, 'line'); - // size - strokeSize && MappingAttribute.size(strokeLayer, strokeSize); - // color - strokeColor && MappingAttribute.color(strokeLayer, strokeColor); - // style - strokeStyle && MappingAttribute.style(strokeLayer, strokeStyle); - - /** - * 高亮图层 - */ - if (defaultState.active.stroke) { - const color = defaultState.active.stroke; - const size = defaultState.active.lineWidth || strokeSize; - const style = { opacity: defaultState.active.lineOpacity }; - // shape - MappingAttribute.shape(highlightLayer, 'line'); - // size - size && MappingAttribute.size(highlightLayer, size); - // color - color && MappingAttribute.color(highlightLayer, color); - // style - style && MappingAttribute.style(highlightLayer, style); - } - - /** - * 选中填充图层 - */ - if (defaultState.select.fill) { - const color = defaultState.select.fill; - // shape - MappingAttribute.shape(selectFillLayer, 'fill'); - // color - color && MappingAttribute.color(selectFillLayer, color); - // style - fillStyle && MappingAttribute.style(selectFillLayer, fillStyle); - // state - MappingAttribute.state(selectFillLayer, { select: false, active: false }); - } - /** - * 选中描边图层 - */ - if (defaultState.select.stroke) { - const color = defaultState.select.stroke; - const size = defaultState.select.lineWidth || strokeSize; - const style = { opacity: defaultState.select.lineOpacity }; - // shape - MappingAttribute.shape(selectStrokeLayer, 'line'); - // size - size && MappingAttribute.size(selectStrokeLayer, size); - // color - color && MappingAttribute.color(selectStrokeLayer, color); - // style - style && MappingAttribute.style(selectStrokeLayer, style); - } -} diff --git a/packages/composite-layers/src/layers/area-layer/index.ts b/packages/composite-layers/src/layers/area-layer/index.ts deleted file mode 100644 index c73b9f1d3..000000000 --- a/packages/composite-layers/src/layers/area-layer/index.ts +++ /dev/null @@ -1,309 +0,0 @@ -import { clone, isEqual, isUndefined } from '@antv/util'; -import { PolygonLayer, LineLayer } from '@antv/l7-layers'; -import { CompositeLayer } from '../../core/composite-layer'; -import { getDefaultState, mappingLayersAttr } from './adaptor'; -import { AreaLayerOptions, AreaLayerSourceOptions } from './types'; -import { ILayer, MouseEvent, Source } from '../../types'; -import { DEFAULT_OPTIONS } from './constants'; - -export type { AreaLayerOptions }; - -export class AreaLayer extends CompositeLayer { - /** - * 默认配置项 - */ - static DefaultOptions = DEFAULT_OPTIONS; - /** - * 复合图层类型 - */ - public type = CompositeLayer.LayerType.AreaLayer; - /** - * 主图层 填充面图层实例 - */ - public get layer() { - return this.subLayers.getLayer('fillLayer') as ILayer; - } - /** - * 描边图层 - */ - public get strokeLayer() { - return this.subLayers.getLayer('strokeLayer') as ILayer; - } - /** - * 高亮描边图层 - */ - public get highlightLayer() { - return this.subLayers.getLayer('highlightLayer') as ILayer; - } - /** - * 高亮描边数据 - */ - private highlightLayerData: any; - /** - * 选中填充面图层 - */ - public get selectFillLayer() { - return this.subLayers.getLayer('selectFillLayer') as ILayer; - } - /** - * 选中描边图层 - */ - public get selectStrokeLayer() { - return this.subLayers.getLayer('selectStrokeLayer') as ILayer; - } - /** - * 选中数据 - */ - private selectData: { feature: any; featureId: number }[] = []; - /** - * 图层是否具有交互属性 - */ - public interaction = true; - - constructor(options: AreaLayerOptions) { - super(options); - this.initSubLayersEvent(); - } - - /** - * 获取默认配置 - */ - public getDefaultOptions(): Partial { - return AreaLayer.DefaultOptions; - } - - /** - * 创建子图层 - */ - protected createSubLayers() { - const { state } = this.options; - const baseConfig = this.pickLayerBaseConfig(); - const { visible, minZoom, maxZoom, zIndex = 0 } = baseConfig; - const defaultState = getDefaultState(state); - - const fillLayer = new PolygonLayer({ ...baseConfig, name: 'fillLayer' }); - const strokeLayer = new LineLayer({ name: 'strokeLayer', visible, zIndex, minZoom, maxZoom }); - const highlightLayer = new LineLayer({ - name: 'highlightLayer', - visible: visible && Boolean(defaultState.active.stroke), - zIndex: zIndex + 0.1, - minZoom, - maxZoom, - }); - const selectFillLayer = new PolygonLayer({ - name: 'selectFillLayer', - visible: visible && Boolean(defaultState.select.fill), - zIndex: zIndex + 0.1, - minZoom, - maxZoom, - }); - const selectStrokeLayer = new LineLayer({ - name: 'selectStrokeLayer', - visible: visible && Boolean(defaultState.select.stroke), - zIndex: zIndex + 0.1, - minZoom, - maxZoom, - }); - - const subLayers = [fillLayer, strokeLayer, highlightLayer, selectFillLayer, selectStrokeLayer]; - - return subLayers; - } - - /** - * 映射子图层属性 - */ - protected adaptorSubLayersAttr() { - mappingLayersAttr( - this.layer, - this.strokeLayer, - this.highlightLayer, - this.selectFillLayer, - this.selectStrokeLayer, - this.options - ); - } - - /** - * 设置子图层数据 - */ - protected setSubLayersSource(source: AreaLayerSourceOptions | Source) { - super.setSubLayersSource(source); - this.setStrokeLayerSource(); - this.setHighlightLayerSource(); - this.selectFillLayer.source({ type: 'FeatureCollection', features: [] }, { parser: { type: 'geojson' } }); - this.selectStrokeLayer.source({ type: 'FeatureCollection', features: [] }, { parser: { type: 'geojson' } }); - } - - /** - * 设置描边子图层数据 - */ - protected setStrokeLayerSource() { - const layerSource = this.layer.getSource(); - if (layerSource) { - this.strokeLayer.setSource(layerSource); - } else { - const { data, options } = this.layer.sourceOption; - this.strokeLayer.source(data, options); - } - } - - /** - * 设置高亮描边子图层数据 - */ - protected setHighlightLayerSource(feature?: any, featureId = -999) { - if (this.highlightLayerData === featureId) { - return; - } - const features = feature ? [feature] : []; - this.highlightLayer.setData({ type: 'FeatureCollection', features }, { parser: { type: 'geojson' } }); - this.highlightLayerData = featureId; - } - - /** - * 设置选中描边与填充子图层数据 - */ - protected setSelectLayerSource(selectData: any[] = []) { - if ( - this.selectData.length === selectData.length && - isEqual( - this.selectData.map(({ featureId }) => featureId), - selectData.map(({ featureId }) => featureId) - ) - ) { - return; - } - const features = selectData.map(({ feature }) => feature); - this.selectFillLayer.setData({ type: 'FeatureCollection', features }, { parser: { type: 'geojson' } }); - this.selectStrokeLayer.setData({ type: 'FeatureCollection', features }, { parser: { type: 'geojson' } }); - this.selectData = selectData; - } - - /** - * 初始化子图层事件 - */ - protected initSubLayersEvent() { - // 初始化主图层交互事件 - this.layer.off('mousemove', this.onHighlighHandle); - this.layer.off('unmousemove', this.onHighlighHandle); - this.layer.off('click', this.onSelectHandle); - this.selectData = []; - this.highlightLayerData = null; - if (!this.options.state) return; - // active - if (this.options.state.active) { - this.layer.on('mousemove', this.onHighlighHandle); - this.layer.on('unmousemove', this.onUnhighlighHandle); - } - // select - if (this.options.state.select) { - this.layer.on('click', this.onSelectHandle); - } - } - - /** - * 图层高亮回调 - */ - private onHighlighHandle = (event: MouseEvent) => { - const { feature, featureId } = event; - this.setHighlightLayerSource(feature, featureId); - }; - - /** - * 图层取消高亮回调 - */ - private onUnhighlighHandle = () => { - this.setHighlightLayerSource(); - }; - - /** - * 图层选中回调 - */ - private onSelectHandle = (event: MouseEvent) => { - const enabledMultiSelect = this.options.enabledMultiSelect; - const { feature, featureId } = event; - let selectData = clone(this.selectData); - const index = selectData.findIndex((item) => item.featureId === featureId); - - if (index === -1) { - if (enabledMultiSelect) { - selectData.push({ feature, featureId }); - } else { - selectData = [{ feature, featureId }]; - } - this.emit('select', feature, clone(selectData)); - } else { - const unselectFeature = selectData[index]; - if (enabledMultiSelect) { - selectData.splice(index, 1); - } else { - selectData = []; - } - this.emit('unselect', unselectFeature, clone(selectData)); - } - - this.setSelectLayerSource(selectData); - }; - - /** - * 更新 - */ - public update(options: Partial) { - super.update(options); - - this.adaptorSubLayersAttr(); - - if (this.options.visible) { - this.updateHighlightSubLayers(this.options); - } - - this.initSubLayersEvent(); - } - - /** - * 更新高亮及选中子图层 - */ - private updateHighlightSubLayers(options: Partial) { - const defaultState = getDefaultState(this.options.state); - - // 更新是否开启高亮与选中子图层 - if (!isUndefined(options.state) && !isEqual(this.lastOptions.state, this.options.state)) { - const lasetDefaultState = getDefaultState(this.lastOptions.state); - if (lasetDefaultState.active.stroke !== defaultState.active.stroke) { - defaultState.active.stroke ? this.highlightLayer.show() : this.highlightLayer.hide(); - } - - if (lasetDefaultState.select.fill !== defaultState.select.fill) { - defaultState.select.fill ? this.selectFillLayer.show() : this.selectFillLayer.hide(); - } - - if (lasetDefaultState.select.stroke !== defaultState.select.stroke) { - defaultState.select.stroke ? this.selectStrokeLayer.show() : this.selectStrokeLayer.hide(); - } - } - - // 更新之后清除旧数据 - if (defaultState.active.stroke) { - this.setHighlightLayerSource(); - } - if (defaultState.select.fill || defaultState.select.stroke) { - this.setSelectLayerSource(); - } - } - - public setIndex(zIndex: number) { - this.layer.setIndex(zIndex); - this.strokeLayer.setIndex(zIndex); - this.highlightLayer.setIndex(zIndex + 0.1); - this.selectFillLayer.setIndex(zIndex + 0.1); - this.selectStrokeLayer.setIndex(zIndex + 0.1); - } - - public setActive(id: number) { - // TODO: L7 method pickFeature(id|{x,y}) - } - - public setSelect(id: number) { - // TODO: L7 method pickFeature(id|{x,y}) - } -} diff --git a/packages/composite-layers/src/layers/area-layer/types.ts b/packages/composite-layers/src/layers/area-layer/types.ts deleted file mode 100644 index e5aeec9d9..000000000 --- a/packages/composite-layers/src/layers/area-layer/types.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { CompositeLayerOptions } from '../../core/composite-layer'; -import { ISourceCFG, PolygonLayerConfig, Source } from '../../types'; - -/** - * 数据配置 - */ -export interface AreaLayerSourceOptions extends Pick { - data: any; -} - -type AreaLayerStyle = { - // 填充透明度 - opacity?: number; - // 描边 - stroke?: string; - // 描边的宽度 - lineWidth?: number; - // 描边的类型 - lineType?: 'solid' | 'dash'; - // 描边的虚线配置 - // 第一个值为虚线每个分段的长度,第二个值为分段间隔的距离。lineDash 设为[0,0]的效果为没有描边。 - lineDash?: [number, number]; - // 描边透明度 - lineOpacity?: number; - // 填充兜底颜色,用于颜色值映值不存在时 - fillBottomColor?: false | string; -}; - -export type AreaLayerActiveOptions = { - // 填充颜色 - fill?: false | string; - // 描边颜色 - stroke?: false | string; - // 描边的宽度 - lineWidth?: number; - // 描边透明度 - lineOpacity?: number; -}; - -export interface AreaLayerOptions extends Pick, CompositeLayerOptions { - /** - * 具体的数据 - */ - source: AreaLayerSourceOptions | Source; - /** - * 图层样式 - */ - style?: AreaLayerStyle; - /** - * 交互反馈 - */ - state?: { - active?: boolean | AreaLayerActiveOptions; - select?: boolean | AreaLayerActiveOptions; - }; - /** - * 是否启用多选 - */ - enabledMultiSelect?: boolean; -} diff --git a/packages/composite-layers/src/layers/dot-layer/adaptor.ts b/packages/composite-layers/src/layers/dot-layer/adaptor.ts deleted file mode 100644 index d9cbddd67..000000000 --- a/packages/composite-layers/src/layers/dot-layer/adaptor.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { MappingAttribute } from '../../adaptor/attribute'; -import { ILayer } from '../../types'; -import { DotLayerOptions } from './types'; - -export function mappingLayersAttr(layer: ILayer, options: DotLayerOptions): void { - const { shape, color, size, style, state, animate } = options; - // mapping shape - shape && MappingAttribute.shape(layer, shape); - - // mapping size - size && MappingAttribute.size(layer, size); - - // mapping color - color && MappingAttribute.color(layer, color); - - // mapping style - style && MappingAttribute.style(layer, style); - - // mapping state - state && MappingAttribute.state(layer, state); - - // mapping animate - animate && MappingAttribute.animate(layer, animate); -} diff --git a/packages/composite-layers/src/layers/dot-layer/constants.ts b/packages/composite-layers/src/layers/dot-layer/constants.ts deleted file mode 100644 index ae98f3571..000000000 --- a/packages/composite-layers/src/layers/dot-layer/constants.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { DotLayerOptions } from './types'; - -// const defaultHighlightColor = '#2f54eb'; - -/** - * 默认的全部交互状态配置 - */ -// export const DEFAULT_STATE: { active: Required; select: Required } = { -// active: { -// fill: false, -// stroke: defaultHighlightColor, -// lineWidth: 1.5, -// lineOpacity: 0.8, -// }, -// select: { -// fill: false, -// stroke: defaultHighlightColor, -// lineWidth: 1.5, -// lineOpacity: 0.8, -// }, -// }; - -/** - * 默认配置项 - */ -export const DEFAULT_OPTIONS: Partial = { - source: { - data: [], - parser: { - type: 'json', - x: 'x', - y: 'y', - }, - }, - shape: 'circle', - size: 12, - color: '#5FD3A6', -}; diff --git a/packages/composite-layers/src/layers/dot-layer/index.ts b/packages/composite-layers/src/layers/dot-layer/index.ts deleted file mode 100644 index a606bb93e..000000000 --- a/packages/composite-layers/src/layers/dot-layer/index.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { PointLayer } from '@antv/l7-layers'; -import { CompositeLayer } from '../../core/composite-layer'; -import { mappingLayersAttr } from './adaptor'; -import { DotLayerOptions } from './types'; -import { ILayer, Source, SourceOptions } from '../../types'; -import { DEFAULT_OPTIONS } from './constants'; - -export type { DotLayerOptions }; - -export class DotLayer extends CompositeLayer { - /** - * 默认配置项 - */ - static DefaultOptions = DEFAULT_OPTIONS; - /** - * 复合图层类型 - */ - public type = CompositeLayer.LayerType.DotLayer; - /** - * 主图层 点图层实例 - */ - public get layer() { - return this.subLayers.getLayer('pointLayer') as ILayer; - } - /** - * 图层是否具有交互属性 - */ - public interaction = true; - - constructor(options: O) { - super(options); - this.initSubLayersEvent(); - } - - /** - * 获取默认配置 - */ - public getDefaultOptions(): Partial { - return DotLayer.DefaultOptions as O; - } - - /** - * 创建子图层 - */ - protected createSubLayers() { - // const { state } = this.options; - const baseConfig = this.pickLayerBaseConfig(); - // const { visible, minZoom, maxZoom, zIndex = 0 } = baseConfig; - - const pointLayer = new PointLayer({ ...baseConfig, name: 'pointLayer' }); - - const subLayers = [pointLayer]; - - return subLayers; - } - - /** - * 映射子图层属性 - */ - protected adaptorSubLayersAttr() { - mappingLayersAttr(this.layer, this.options); - } - - /** - * 设置子图层数据 - */ - protected setSubLayersSource(source: SourceOptions | Source) { - super.setSubLayersSource(source); - } - - /** - * 初始化子图层事件 - */ - protected initSubLayersEvent() { - // - } - - public update(options: Partial) { - super.update(options); - - this.adaptorSubLayersAttr(); - - this.initSubLayersEvent(); - } -} diff --git a/packages/composite-layers/src/layers/dot-layer/types.ts b/packages/composite-layers/src/layers/dot-layer/types.ts deleted file mode 100644 index 762d7606b..000000000 --- a/packages/composite-layers/src/layers/dot-layer/types.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { CompositeLayerOptions } from '../../core/composite-layer'; -import { SourceOptions, Source, PointLayerConfig } from '../../types'; - -export interface DotLayerOptions extends PointLayerConfig, CompositeLayerOptions { - /** - * 具体的数据 - */ - source: SourceOptions | Source; -} diff --git a/packages/composite-layers/src/types/attr.ts b/packages/composite-layers/src/types/attr.ts index a98404af3..7963487cc 100644 --- a/packages/composite-layers/src/types/attr.ts +++ b/packages/composite-layers/src/types/attr.ts @@ -58,35 +58,15 @@ export type AnimateAttr = boolean | Partial; /** 纹理贴图 */ export type TextureAttr = string; +/** 比例尺 */ +export type ScaleAttr = Record; + /** 数据过滤 */ export type FilterAttr = { field?: string | string[]; value: Callback; }; -/** 聚合方法 */ -export type AggregationMethod = 'count' | 'max' | 'min' | 'sum' | 'mean'; - -/** 网格聚合 */ -export type GridAggregation = { - /** - * 聚合类型 - */ - type?: 'grid' | 'hexagon'; - /** - * 聚合字段 - */ - field: string; - /** - * 网格半径 - */ - radius?: number; - /** - * 聚合方法 - */ - method?: AggregationMethod; -}; - /** * 数据配置 */ @@ -95,5 +75,28 @@ export interface SourceOptions extends ISourceCFG { /** * 网格聚合 */ - aggregation?: GridAggregation; + // aggregation?: GridAggregation; } + +/** 聚合方法 */ +// export type AggregationMethod = 'count' | 'max' | 'min' | 'sum' | 'mean'; + +/** 网格聚合 */ +// export type GridAggregation = { +// /** +// * 聚合类型 +// */ +// type?: 'grid' | 'hexagon'; +// /** +// * 聚合字段 +// */ +// field: string; +// /** +// * 网格半径 +// */ +// radius?: number; +// /** +// * 聚合方法 +// */ +// method?: AggregationMethod; +// }; diff --git a/packages/composite-layers/src/types/common.ts b/packages/composite-layers/src/types/common.ts index 08d8ee13e..df07c12c4 100644 --- a/packages/composite-layers/src/types/common.ts +++ b/packages/composite-layers/src/types/common.ts @@ -1,9 +1,9 @@ import { ILayer, ILayerConfig, + ISource, ISourceCFG, IScale, - IScaleOptions, ILngLat, BlendType, IImage, @@ -12,15 +12,24 @@ import { ILegendClassificaItem, } from '@antv/l7-core'; import type { Scene } from '@antv/l7-scene'; -import type Source from '@antv/l7-source'; -export { ILayer, ILayerConfig, ISourceCFG, BlendType, IImage, ITransform, ILegendSegmentItem, ILegendClassificaItem }; +export { + ILayer, + ILayerConfig, + ISource, + ISourceCFG, + BlendType, + IImage, + ITransform, + ILegendSegmentItem, + ILegendClassificaItem, +}; -export { Scene, Source }; +export { Scene }; +export type LayerBaseConfig = Omit; export type ValueOf = T[keyof T]; export type ScaleConfig = IScale; -export type ScaleConfigMap = IScaleOptions; /** * 图层混合配置 diff --git a/packages/composite-layers/src/types/index.ts b/packages/composite-layers/src/types/index.ts index b05da4069..8394469d3 100644 --- a/packages/composite-layers/src/types/index.ts +++ b/packages/composite-layers/src/types/index.ts @@ -1,5 +1,4 @@ export * from './common'; export * from './attr'; -export * from './interface'; -export * from './layer-config'; -export * from './layer-type'; +export * from './layer-interface'; +export * from './layer-category'; diff --git a/packages/composite-layers/src/types/interface.ts b/packages/composite-layers/src/types/interface.ts deleted file mode 100644 index 72622d10e..000000000 --- a/packages/composite-layers/src/types/interface.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { SourceOptions } from './attr'; -import { ILayer, Scene } from './common'; - -export interface ILayerGroup { - name: string; - - addTo(scene: Scene): void; - remove(): void; - - hasLayer(layer: string | ILayer): boolean; - addLayer(layer: ILayer): void; - removeLayer(layer: string | ILayer): void; - getLayers(): ILayer[]; - getLayer(id: string): ILayer | undefined; - getLayerByName(name: string): ILayer | undefined; - removeAllLayer(): void; - isEmpty(): boolean; - - setZIndex(zIndex: number): void; - - destroy(): void; - - on(name: string, callback: (...args: any[]) => void): this; - once(name: string, callback: (...args: any[]) => void): this; - off(name: string, callback: (...args: any[]) => void): this; -} - -/** - * 复合图层的基类接口 - */ -export interface ICompositeLayer { - name: string; - type: string; - - addTo(scene: Scene): void; - remove(): void; - - update(options: T): void; - updateOption(options: T): void; - - changeData(data: SourceOptions): void; - render(): void; - - show(): void; - hide(): void; - toggleVisible(): void; - - setIndex(zIndex: number): void; - setMinZoom(minZoom: number): void; - setMaxZoom(maxZoom: number): void; - - fitBounds(fitBoundsOptions?: unknown): void; - getlegenditems(type: string): Record[]; - getColorLegendItems(): Record[]; - - destroy(): void; - - on(name: string, callback: (...args: any[]) => void): this; - once(name: string, callback: (...args: any[]) => void): this; - off(name: string, callback: (...args: any[]) => void): this; -} diff --git a/packages/composite-layers/src/types/layer-category.ts b/packages/composite-layers/src/types/layer-category.ts new file mode 100644 index 000000000..7bbf5aec2 --- /dev/null +++ b/packages/composite-layers/src/types/layer-category.ts @@ -0,0 +1,7 @@ +/** + * 复合图层类型 + */ +export enum CompositeLayerType { + ScatterLayer = 'scatterLayer', + AreaLayer = 'areaLayer', +} diff --git a/packages/composite-layers/src/types/layer-config.ts b/packages/composite-layers/src/types/layer-config.ts deleted file mode 100644 index 2761da7a1..000000000 --- a/packages/composite-layers/src/types/layer-config.ts +++ /dev/null @@ -1,340 +0,0 @@ -import { IColorRamp } from '@antv/l7-utils'; -import { AnimateAttr, ColorAttr, StateAttribute, ShapeAttr, SizeAttr, TextureAttr } from './attr'; - -/************************** - * 点图层 - *************************/ - -/** - * 点图层 图层样式 - */ -export type PointLayerStyleOptions = { - opacity?: number; - strokeWidth?: number; - stroke?: string; -}; - -/** - * 点图层 图形形状 - */ -export type PointShape2d = - | 'circle' - | 'square' - | 'hexagon' - | 'triangle' - | 'pentagon' - | 'octogon' - | 'hexagram' - | 'rhombus' - | 'vesica' - | 'dot'; - -export type PointShape3d = 'cylinder' | 'triangleColumn' | 'hexagonColumn' | 'squareColumn'; - -export type PointShape = PointShape2d | PointShape3d; - -/** - * 点图层基础配置 - */ -export interface PointLayerConfig { - /** - * 图形形状 - */ - shape?: ShapeAttr; - /** - * 图形颜色 - */ - color?: ColorAttr; - /** - * 图形大小 - */ - size?: SizeAttr; - /** - * 图层样式 - */ - style?: PointLayerStyleOptions; - /** - * animation 配置 - */ - animate?: AnimateAttr; - /** - * 交互反馈 - */ - state?: StateAttribute; -} - -/************************** - * 文字图层 - *************************/ - -/** - * 文字图层 文本相对锚点 - */ -export type AnchorType = - | 'right' - | 'top-right' - | 'left' - | 'bottom-right' - | 'left' - | 'top-left' - | 'bottom-left' - | 'bottom' - | 'bottom-right' - | 'bottom-left' - | 'top' - | 'top-right' - | 'top-left' - | 'center'; - -/** - * 文字图层 样式 - */ -export type PointTextLayerStyleOptions = { - /* 字体颜色 */ - fill?: ColorAttr; - /* 字体大小 */ - fontSize?: SizeAttr; - /* 透明度 */ - opacity?: number; - /* 文本相对锚点的位置 */ - textAnchor?: AnchorType; - /* 文本相对锚点的偏移量 */ - textOffset?: [number, number]; - /* 字符间距 */ - spacing?: number; - /* 文本包围盒 padding [水平,垂直],影响碰撞检测结果,避免相邻文本靠的太近 */ - padding?: [number, number]; - // TODO:注释 - halo?: number; - // TODO:注释 - gamma?: number; - /* 描边颜色 */ - stroke?: string; - /* 描边宽度 */ - strokeWidth?: number; - /* 描边透明度 */ - strokeOpacity?: number; - /* 字体 */ - fontFamily?: string; - /* 字体的粗细程度 */ - fontWeight?: string; - /* 是否换行 */ - textAllowOverlap?: boolean; -}; - -/** - * 文字图层基础配置 - */ -export interface TextLayerConfig { - /** 映射的字段 */ - field?: string; - // TODO: 多字段支持 - // fields?: string[]; - /** 回调函数 */ - content?: string; - // TODO: 多字段 CallBack 支持 - // content?: string | ((data: Record) => string); - /** 字体样式 */ - style?: PointTextLayerStyleOptions; - /* 旋转文字 */ - // rotate?: RotateAttr; - /* 交互反馈 */ - state?: StateAttribute; -} - -/************************** - * 热力图层 - *************************/ - -/** - * 热力普通图层 色带 - */ -export type ColorRamp = { color: string; position: number }[]; - -/** - * 热力普通图层 图层样式 - */ -export type HeatmapLayerStyleOptions = { - // 透明度 - opacity?: number; - // 旋转角度 - angle?: number; - // 全局热力权重,推荐权重范围 1-5 - intensity: number; - // 热力半径,单位像素 - radius: number; - // 色带 - colorsRamp: ColorRamp; - // L7 原色带 - rampColors?: IColorRamp; -}; - -/** - * 热力网格图/蜂窝图层 图层样式 - */ -export type GridHeatmapLayerStyleOptions = { - // 透明度 - opacity?: number; - // 旋转角度 - angle?: number; - // 覆盖度 - coverage?: number; -}; - -/** - * 热力图层 图形形状 - */ -export type HeatmapShape2d = 'circle' | 'square' | 'hexagon' | 'triangle'; - -export type HeatmapShape3d = 'cylinder' | 'squareColumn' | 'hexagonColumn' | 'triangleColumn'; - -export type HeatmapShape = 'heatmap' | 'heatmap3D' | HeatmapShape2d | HeatmapShape3d; - -/** - * 热力图层基础配置 - */ -export interface HeatmapLayerConfig { - /** - * 图形形状 - */ - shape?: HeatmapShape; - /** - * 图形颜色 - */ - color?: ColorAttr; - /** - * 图形大小 - */ - size?: SizeAttr; - /** - * 图层样式 - */ - style?: HeatmapLayerStyleOptions | GridHeatmapLayerStyleOptions; - /** - * 交互反馈 - */ - state?: StateAttribute; -} - -/************************** - * 线图层 - *************************/ - -/** - * 线图层 图形形状 - */ -export type ArcLineShape = 'arc' | 'arc3d' | 'greatcircle'; - -export type LineShape = 'line' | ArcLineShape; - -/** - * 线图层 线类型 - */ -export enum LineStyleType { - 'solid' = 0.0, - 'dash' = 1.0, -} - -/** - * 线图层 图层样式 - */ -export type LinesLayerStyleOptions = { - // 透明度 - opacity?: number | [string, (data: any) => number] | [string, [number, number]]; - // 线类型 - lineType?: keyof typeof LineStyleType; - // 虚线间隔 - dashArray?: [number, number]; - // 弧线分段数 - segmentNumber?: number; - // 渐变起点颜色 - sourceColor?: string; - // 渐变终点颜色 - targetColor?: string; - // 是否反向,arc 支持 - forward?: boolean; - // 弧线的偏移量,arc 支持 - thetaOffset?: number; - // 是否开启纹理贴图 - lineTexture?: boolean; - // 纹理贴图步长 - iconStep?: number; - // 纹理混合方式 - textureBlend?: string; -}; - -/** - * 线图层基础配置 - */ -export interface LinesLayerConfig { - /** - * 图形形状 - */ - shape?: ShapeAttr; - /** - * 图形颜色 - */ - color?: ColorAttr; - /** - * 图形大小 - */ - size?: SizeAttr; - /** - * 图层样式 - */ - style?: LinesLayerStyleOptions; - /** - * animation 配置 - */ - animate?: AnimateAttr; - /** - * 交互反馈 - */ - state?: StateAttribute; - /** - * 纹理贴图 - */ - texture?: TextureAttr; -} - -/************************** - * 面图层 - *************************/ - -/** - * 面图层 图形形状 - */ -export type PolygonShape = 'fill' | 'line' | 'extrude'; - -/** - * 面图层 图层样式 - */ -export type PolygonLayerStyleOptions = { - opacity?: number; -}; - -/** - * 面图层基础配置 - */ -export interface PolygonLayerConfig { - /** - * 图形形状 - */ - shape?: PolygonShape; - /** - * 图形颜色 - */ - color?: ColorAttr; - /** - * 图形大小 - */ - size?: SizeAttr; - /** - * 图层样式 - */ - style?: PolygonLayerStyleOptions; - /** - * 交互反馈 - */ - state?: StateAttribute; -} diff --git a/packages/composite-layers/src/types/layer-interface.ts b/packages/composite-layers/src/types/layer-interface.ts new file mode 100644 index 000000000..0db9461eb --- /dev/null +++ b/packages/composite-layers/src/types/layer-interface.ts @@ -0,0 +1,106 @@ +import { SourceOptions } from './attr'; +import { ILayer, LayerBlend, Scene, ISource } from './common'; + +export interface ILayerGroup { + name: string; + + addTo(scene: Scene): void; + remove(): void; + + hasLayer(layer: string | ICoreLayer): boolean; + addLayer(layer: ICoreLayer): void; + removeLayer(layer: string | ICoreLayer): void; + getLayers(): ICoreLayer[]; + getLayer(id: string): ICoreLayer | undefined; + getLayerByName(name: string): ICoreLayer | undefined; + removeAllLayer(): void; + isEmpty(): boolean; + + setZIndex(zIndex: number): void; + + destroy(): void; + + on(name: string, callback: (...args: any[]) => void): this; + once(name: string, callback: (...args: any[]) => void): this; + off(name: string, callback: (...args: any[]) => void): this; +} + +/** + * 核心图层的基类接口 + */ +export interface ICoreLayer { + name: string; + type: string; + layer: ILayer; + inited: boolean; + source: ISource; + + addTo(scene: Scene): void; + remove(): void; + + update(options: T): void; + updateOption(options: T): void; + + changeData(data: SourceOptions): void; + setSource(source: ISource): void; + render(): void; + + show(): void; + hide(): void; + toggleVisible(): void; + isVisible(): boolean; + + setIndex(zIndex: number): void; + setMinZoom(minZoom: number): void; + setMaxZoom(maxZoom: number): void; + setBlend(blend: LayerBlend): void; + + boxSelect(bounds: [number, number, number, number], callback: (...args: any[]) => void): void; + + fitBounds(fitBoundsOptions?: unknown): void; + getLegendItems(type: string): Record[]; + + destroy(): void; + + on(name: string, callback: (...args: any[]) => void): this; + once(name: string, callback: (...args: any[]) => void): this; + off(name: string, callback: (...args: any[]) => void): this; +} + +/** + * 复合图层的基类接口 + */ +export interface ICompositeLayer { + name: string; + type: string; + + subLayers: ILayerGroup; + + addTo(scene: Scene): void; + remove(): void; + + update(options: T): void; + updateOption(options: T): void; + + changeData(data: SourceOptions): void; + render(): void; + + show(): void; + hide(): void; + toggleVisible(): void; + + setIndex(zIndex: number): void; + setMinZoom(minZoom: number): void; + setMaxZoom(maxZoom: number): void; + setBlend(blend: LayerBlend): void; + + fitBounds(fitBoundsOptions?: unknown): void; + getLegendItems(type: string): Record[]; + getColorLegendItems(): Record[]; + + destroy(): void; + + on(name: string, callback: (...args: any[]) => void): this; + once(name: string, callback: (...args: any[]) => void): this; + off(name: string, callback: (...args: any[]) => void): this; +} diff --git a/packages/composite-layers/src/types/layer-type.ts b/packages/composite-layers/src/types/layer-type.ts deleted file mode 100644 index 6fc4f739b..000000000 --- a/packages/composite-layers/src/types/layer-type.ts +++ /dev/null @@ -1,18 +0,0 @@ -/** - * 复合图层类型 - */ -export enum LayerType { - TextLayer = 'textLayer', - DotLayer = 'dotLayer', - IconLayer = 'iconLayer', - DotDensity = 'dotDensityLayer', - ColumnLayer = 'columnLayer', - HeatmapLayer = 'heatmapLayer', - GridLayer = 'gridLayer', - HexbinLayer = 'hexbinLayer', - LinesLayer = 'linesLayer', - PathLayer = 'pathLayer', - ArcLayer = 'arcLayer', - AreaLayer = 'areaLayer', - PrismLayer = 'prismLayer', -} diff --git a/storybook/stories/advanced-plot/index.stories.tsx b/storybook/stories/advanced-plot/index.stories.tsx index 8853b7617..8731bd9be 100644 --- a/storybook/stories/advanced-plot/index.stories.tsx +++ b/storybook/stories/advanced-plot/index.stories.tsx @@ -7,7 +7,7 @@ import WindField from './WindField'; import WorldBubbleMap from './WorldBubbleMap'; import ChinaBubbleMap from './ChinaBubbleMap'; -storiesOf('高级图表', module) +storiesOf('图表/高级图表', module) .add('流向图', () => ) .add('航向图', () => ) .add('风场图', () => ) diff --git a/storybook/stories/composite-layers/area-layer/ChinaCitys.tsx b/storybook/stories/composite-layers/area-layer/ChinaCitys.tsx new file mode 100644 index 000000000..6d6a27f57 --- /dev/null +++ b/storybook/stories/composite-layers/area-layer/ChinaCitys.tsx @@ -0,0 +1,97 @@ +import React, { Component } from 'react'; +import { Scene, Mapbox } from '@antv/l7'; +import { AreaLayer } from '@antv/l7-composite-layers'; + +class ChinaCitys extends Component { + public scene: Scene | undefined; + + constructor(props) { + super(props); + } + + async initMap() { + this.scene = new Scene({ + id: 'container', + map: new Mapbox({ + pitch: 0, + style: 'dark', + zoom: 3, + center: [120.19660949707033, 30.234747338474293], + }), + }); + + fetch('https://gw.alipayobjects.com/os/bmw-prod/707cd4be-8ffe-4778-b863-3335eefd5fd5.json') + .then((response) => response.json()) + .then((data) => { + const areaLayer = new AreaLayer({ + source: { + data: data, + parser: { + type: 'geojson', + }, + }, + fillColor: { + field: 'name', + value: ['#fee5d9', '#fcae91', '#fb6a4a', '#de2d26', '#a50f15'], + }, + opacity: 0.8, + strokeColor: '#c0c0c0', + lineType: 'solid', + lineWidth: 0.81, + lineOpacity: 0.5, + label: { + visible: true, + field: 'name', + style: { + fill: '#fff', + opacity: 0.8, + fontSize: 12, + textAnchor: 'center', // 文本相对锚点的位置 center|left|right|top|bottom|top-left + spacing: 1, // 字符间距 + padding: [15, 15], // 文本包围盒 padding [水平,垂直],影响碰撞检测结果,避免相邻文本靠的太近 + stroke: '#ffffff', // 描边颜色 + strokeWidth: 0.3, // 描边宽度 + }, + }, + state: { + active: { + strokeColor: 'blue', + }, + select: { + strokeColor: 'yellow', + lineWidth: 1, + lineOpacity: 0.8, + }, + }, + enabledMultiSelect: true, + }); + + this.scene && areaLayer.addTo(this.scene); + }); + } + + componentDidMount() { + this.initMap(); + } + + componentWillUnmount() { + this.scene && this.scene.destroy(); + } + + render() { + return ( +
+ ); + } +} + +export default ChinaCitys; diff --git a/storybook/stories/composite-layers/area-layer/index.stories.tsx b/storybook/stories/composite-layers/area-layer/index.stories.tsx new file mode 100644 index 000000000..e412f0752 --- /dev/null +++ b/storybook/stories/composite-layers/area-layer/index.stories.tsx @@ -0,0 +1,5 @@ +import { storiesOf } from '@storybook/react'; + +import ChinaCitys from './ChinaCitys'; + +storiesOf('复合图层/区域图层', module).add('中国城市区域', () => ); diff --git a/storybook/stories/composite-layers/scatter-layer/Earthquake.tsx b/storybook/stories/composite-layers/scatter-layer/Earthquake.tsx new file mode 100644 index 000000000..4c421cd32 --- /dev/null +++ b/storybook/stories/composite-layers/scatter-layer/Earthquake.tsx @@ -0,0 +1,111 @@ +import React, { Component } from 'react'; +import { Scene, Mapbox } from '@antv/l7'; +import { ScatterLayer } from '@antv/l7-composite-layers'; +import { Dot } from '@antv/l7plot'; + +class Earthquake extends Component { + public scene: Scene | undefined; + + constructor(props) { + super(props); + } + + async initMap() { + this.scene = new Scene({ + id: 'container', + map: new Mapbox({ + pitch: 0, + style: 'dark', + center: [103.447303, 31.753574], + zoom: 7, + }), + }); + + const response = await fetch('https://gw.alipayobjects.com/os/antfincdn/m5r7MFHt8U/wenchuandizhenshuju.json'); + const { data } = await response.json(); + + const scatterLayer = new ScatterLayer({ + autoFit: true, + source: { + data: data, + parser: { + type: 'json', + x: 'lng', + y: 'lat', + }, + }, + fillColor: { + field: 'mag', + value: ({ mag }) => { + if (mag > 7) { + return '#82cf9c'; + } else if (mag <= 7 && mag >= 5.5) { + return '#10b3b0'; + } else { + return '#2033ab'; + } + }, + }, + strokeColor: '#c0c0c0', + lineWidth: 1, + radius: { + field: 'mag', + value: ({ mag }) => (mag - 4.3) * 10, + }, + opacity: 0.8, + label: { + visible: true, + field: 'mag', + style: { + fill: 'red', + opacity: 1, + fontSize: 14, + textAnchor: 'top', // 文本相对锚点的位置 center|left|right|top|bottom|top-left + spacing: 1, // 字符间距 + padding: [15, 15], // 文本包围盒 padding [水平,垂直],影响碰撞检测结果,避免相邻文本靠的太近 + stroke: '#ffffff', // 描边颜色 + strokeWidth: 0.3, // 描边宽度 + textOffset: [0, 20], + }, + }, + state: { + active: { + strokeColor: 'blue', + lineWidth: 2, + }, + select: { + strokeColor: 'yellow', + lineWidth: 3, + }, + }, + enabledMultiSelect: true, + }); + + this.scene && scatterLayer.addTo(this.scene); + } + + componentDidMount() { + this.initMap(); + } + + componentWillUnmount() { + this.scene && this.scene.destroy(); + } + + render() { + return ( +
+ ); + } +} + +export default Earthquake; diff --git a/storybook/stories/composite-layers/scatter-layer/index.stories.tsx b/storybook/stories/composite-layers/scatter-layer/index.stories.tsx new file mode 100644 index 000000000..3ac342dc4 --- /dev/null +++ b/storybook/stories/composite-layers/scatter-layer/index.stories.tsx @@ -0,0 +1,5 @@ +import { storiesOf } from '@storybook/react'; + +import Earthquake from './Earthquake'; + +storiesOf('复合图层/散点图层', module).add('地震震级', () => ); diff --git a/storybook/stories/plots/area/index.stories.tsx b/storybook/stories/plots/area/index.stories.tsx index 86ca2f024..4ee8d4120 100644 --- a/storybook/stories/plots/area/index.stories.tsx +++ b/storybook/stories/plots/area/index.stories.tsx @@ -4,7 +4,7 @@ import ChinaCitys from './ChinaCitys'; import WorldSufei from './WorldSufei'; import WorldSufeiMove from './WorldSufeiMove'; -storiesOf('区域图', module) +storiesOf('图表/区域图', module) .add('中国城市区域', () => ) .add('WorldSufei', () => ) .add('WorldSufeiMove', () => ); diff --git a/storybook/stories/plots/choropleth/index.stories.tsx b/storybook/stories/plots/choropleth/index.stories.tsx index e463e58b0..57bb5f12b 100644 --- a/storybook/stories/plots/choropleth/index.stories.tsx +++ b/storybook/stories/plots/choropleth/index.stories.tsx @@ -10,7 +10,7 @@ import Drill from './Drill'; import DrillCallback from './DrillCallback'; import WorldSufei from './WorldSufei'; -storiesOf('行政区域分布图', module) +storiesOf('图表/行政区域分布图', module) .add('中国省级行政图', () => ) .add('中国市级行政图', () => ) .add('中国区县级行政图', () => ) diff --git a/storybook/stories/plots/clustere/index.stories.tsx b/storybook/stories/plots/clustere/index.stories.tsx index 5f830e3f9..3f542489a 100644 --- a/storybook/stories/plots/clustere/index.stories.tsx +++ b/storybook/stories/plots/clustere/index.stories.tsx @@ -2,4 +2,4 @@ import { storiesOf } from '@storybook/react'; import EarthquakeMagnitude from './EarthquakeMagnitude'; -storiesOf('聚合图', module).add('地震震级聚合', () => ); +storiesOf('图表/聚合图', module).add('地震震级聚合', () => ); diff --git a/storybook/stories/plots/dot-density/index.stories.tsx b/storybook/stories/plots/dot-density/index.stories.tsx index 13acb530d..a39e40e42 100644 --- a/storybook/stories/plots/dot-density/index.stories.tsx +++ b/storybook/stories/plots/dot-density/index.stories.tsx @@ -4,7 +4,7 @@ import ShanghaiTraffic from './ShanghaiTraffic'; import BeijingTraffic from './BeijingTraffic'; import CuisineNationwide from './CuisineNationwide'; -storiesOf('点密度图', module) +storiesOf('图表/点密度图', module) .add('6万点位全国粤菜分布', () => ) .add('10万辆北京公共交通车辆', () => ) .add('164万辆上海市交通车辆', () => ); diff --git a/storybook/stories/plots/dot/index.stories.tsx b/storybook/stories/plots/dot/index.stories.tsx index c9bf3b85e..84c156f1b 100644 --- a/storybook/stories/plots/dot/index.stories.tsx +++ b/storybook/stories/plots/dot/index.stories.tsx @@ -10,7 +10,7 @@ import Animate from './Animate'; import POI from '../dot/POI'; import BankOutlets from './BankOutlets'; -storiesOf('散点图', module) +storiesOf('图表/散点图', module) .add('气温分布', () => ) .add('气温分布-L7', () => ) .add('地震震级', () => ) diff --git a/storybook/stories/plots/dot/scatter/index.stories.tsx b/storybook/stories/plots/dot/scatter/index.stories.tsx index e55104565..7cdaffa70 100644 --- a/storybook/stories/plots/dot/scatter/index.stories.tsx +++ b/storybook/stories/plots/dot/scatter/index.stories.tsx @@ -3,6 +3,6 @@ import { storiesOf } from '@storybook/react'; import Classified from './Classified'; import Monochrome from './Monochrome'; -storiesOf('散点图', module) +storiesOf('图表/散点图', module) .add('分类散点', () => ) .add('全国城市与区县分类', () => ); diff --git a/storybook/stories/plots/flow/index.stories.tsx b/storybook/stories/plots/flow/index.stories.tsx index 247951be6..7f7b20983 100644 --- a/storybook/stories/plots/flow/index.stories.tsx +++ b/storybook/stories/plots/flow/index.stories.tsx @@ -5,7 +5,7 @@ import GroundFlow from './GroundFlow'; import Flowtsx from './Flow'; import Airline from './Airline'; -storiesOf('流向图', module) +storiesOf('图表/流向图', module) .add('弧线图', () => ) .add('贴地流向图', () => ) .add('流向图', () => ) diff --git a/storybook/stories/plots/grid/index.stories.tsx b/storybook/stories/plots/grid/index.stories.tsx index 231c27b1f..41012cdc5 100644 --- a/storybook/stories/plots/grid/index.stories.tsx +++ b/storybook/stories/plots/grid/index.stories.tsx @@ -3,6 +3,6 @@ import { storiesOf } from '@storybook/react'; import Grid2D from './Grid2D'; import Grid3D from './Grid3D'; -storiesOf('网格地图', module) +storiesOf('图表/网格地图', module) .add('网格热力', () => ) .add('网格热力3D', () => ); diff --git a/storybook/stories/plots/heatmap/index.stories.tsx b/storybook/stories/plots/heatmap/index.stories.tsx index 768ab63ce..9263e1b15 100644 --- a/storybook/stories/plots/heatmap/index.stories.tsx +++ b/storybook/stories/plots/heatmap/index.stories.tsx @@ -4,7 +4,7 @@ import Heat from './Heat'; import L7Heat from './L7Heat'; import Heat3D from './Heat3D'; -storiesOf('热力图', module) +storiesOf('图表/热力图', module) .add('杭州房屋交易量', () => ) .add('杭州房屋交易量-L7', () => ) .add('全国交通事故增长率', () => ); diff --git a/storybook/stories/plots/hexbin/index.stories.tsx b/storybook/stories/plots/hexbin/index.stories.tsx index 6711e6324..a017d9962 100644 --- a/storybook/stories/plots/hexbin/index.stories.tsx +++ b/storybook/stories/plots/hexbin/index.stories.tsx @@ -3,6 +3,6 @@ import { storiesOf } from '@storybook/react'; import HexagonDelay from './HexbinDelay'; import Hexagon3D from './Hexbin3D'; -storiesOf('蜂窝地图', module) +storiesOf('图表/蜂窝地图', module) .add('杭州交通高峰期路口延误指数', () => ) .add('蜂窝热力图3D', () => ); diff --git a/storybook/stories/plots/path/index.stories.tsx b/storybook/stories/plots/path/index.stories.tsx index 177c3b71c..83770e8a0 100644 --- a/storybook/stories/plots/path/index.stories.tsx +++ b/storybook/stories/plots/path/index.stories.tsx @@ -6,7 +6,7 @@ import BusRoutes from './BusRoutes'; import BusLevelRoutes from './BusLevelRoutes'; import Trail from './Trail'; -storiesOf('路径图', module) +storiesOf('图表/路径图', module) .add('北京地铁线', () => ) .add('北京公交线路', () => ) .add('公交路线', () => )