diff --git a/.eslintrc.js b/.eslintrc.js index 966a95948f..f3a3cd327b 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -28,6 +28,7 @@ module.exports = { 'no-case-declarations': 0, 'no-useless-catch': 0, '@typescript-eslint/explicit-module-boundary-types': 0, + '@typescript-eslint/no-explicit-any':0, 'prefer-rest-params':0, }, settings: { diff --git a/.umirc.ts b/.umirc.ts index b46d03903c..e74913e700 100644 --- a/.umirc.ts +++ b/.umirc.ts @@ -12,6 +12,12 @@ export default defineConfig({ resolve: { includes: ['dev-demos'] }, + polyfill: { + imports: [ + 'element-remove', + 'babel-polyfill', + ] + }, targets: { chrome: 58, ie: 11, @@ -30,6 +36,7 @@ export default defineConfig({ extraBabelPresets:[ '@babel/preset-typescript' ], + extraBabelIncludes: ['@umijs/preset-dumi','split-on-first','query-string','strict-uri-encode','copy-text-to-clipboard'], extraBabelPlugins: [ [ 'transform-import-css-l7' @@ -47,11 +54,15 @@ export default defineConfig({ 'react-dom': 'window.ReactDOM', antd: 'window.antd', lodash: '_', + fetch:"window.fetch" }, links: ['https://gw.alipayobjects.com/os/lib/antd/4.16.13/dist/antd.css'], scripts: [ - 'https://gw.alipayobjects.com/os/lib/react/17.0.1/umd/react.development.js', - 'https://gw.alipayobjects.com/os/lib/react-dom/17.0.1/umd/react-dom.development.js', + 'https://gw.alipayobjects.com/os/lib/whatwg-fetch/3.6.2/dist/fetch.umd.js', + 'https://gw.alipayobjects.com/os/lib/react/17.0.2/umd/react.profiling.min.js', + 'https://gw.alipayobjects.com/os/lib/react-dom/17.0.2/umd/react-dom.profiling.min.js', + 'https://gw.alipayobjects.com/os/lib/react/17.0.2/umd/react.production.min.js', + 'https://gw.alipayobjects.com/os/lib/react-dom/17.0.2/umd/react-dom.production.min.js', // 'https://gw.alipayobjects.com/os/lib/antd/4.16.13/dist/antd-with-locales.js', 'https://gw.alipayobjects.com/os/lib/antd/4.19.4/dist/antd.js', /** lodash */ diff --git a/dev-demos/gallery/index.md b/dev-demos/gallery/index.md deleted file mode 100644 index 4bd7ac97a0..0000000000 --- a/dev-demos/gallery/index.md +++ /dev/null @@ -1 +0,0 @@ -### 经典demo 案例 \ No newline at end of file diff --git a/dev-demos/gallery/scale/cat.md b/dev-demos/gallery/scale/cat.md new file mode 100644 index 0000000000..296267a01d --- /dev/null +++ b/dev-demos/gallery/scale/cat.md @@ -0,0 +1,3 @@ +### 枚举类型 + + \ No newline at end of file diff --git a/dev-demos/gallery/scale/cat.tsx b/dev-demos/gallery/scale/cat.tsx new file mode 100644 index 0000000000..61034be468 --- /dev/null +++ b/dev-demos/gallery/scale/cat.tsx @@ -0,0 +1,57 @@ +import { PolygonLayer, Scene } from '@antv/l7'; +import { Mapbox } from '@antv/l7-maps'; +import React, { useEffect } from 'react'; +import { useData, addLayers } from './useLine'; + +export default () => { + const { geoData } = useData(); + + useEffect(() => { + const scene = new Scene({ + id: 'map', + map: new Mapbox({ + pitch: 0, + style: 'light', + center: [-96, 37.8], + zoom: 3, + }), + }); + if (geoData) { + const layer = new PolygonLayer({}) + .source(geoData.county, { + transforms: [ + { + type: 'join', + sourceField: 'id', + targetField: 'id', + data: geoData.unemploymentdata, + }, + ], + }) + .scale('rate', { + type: 'quantile', + }) + .shape('fill') + .color('rate', ['#ffffcc', '#b6e2b6', '#64c1c0', '#338cbb', '#253494']) + .style({ + opacity: 1, + }); + + scene.addLayer(layer); + addLayers(geoData, scene, layer); + } + return () => { + scene.destroy(); + }; + }, [geoData]); + + return ( +
+ ); +}; diff --git a/dev-demos/gallery/scale/diverging.md b/dev-demos/gallery/scale/diverging.md new file mode 100644 index 0000000000..983fdc2a73 --- /dev/null +++ b/dev-demos/gallery/scale/diverging.md @@ -0,0 +1,2 @@ +### 离散类型 + \ No newline at end of file diff --git a/dev-demos/gallery/scale/diverging.tsx b/dev-demos/gallery/scale/diverging.tsx new file mode 100644 index 0000000000..452cb4a9f5 --- /dev/null +++ b/dev-demos/gallery/scale/diverging.tsx @@ -0,0 +1,66 @@ +import { PolygonLayer, Scene } from '@antv/l7'; +import { Mapbox } from '@antv/l7-maps'; +import React, { useEffect } from 'react'; +import { useEuropeData, addEuropeLayers } from './useLine'; + +export default () => { + const { geoData } = useEuropeData(); + + useEffect(() => { + const scene = new Scene({ + id: 'map', + map: new Mapbox({ + pitch: 0, + style: 'light', + center: [-96, 37.8], + zoom: 3, + }), + }); + if (geoData) { + const layer = new PolygonLayer({ + autoFit: true, + }) + .source(geoData.country, { + transforms: [ + { + type: 'join', + sourceField: 'country', + targetField: 'NAME', + data: geoData.turnout, + }, + ], + }) + // .scale('turnout', { + // type: 'quantize', // the input domain and output range of a diverging scale always has exactly three elements + // // domain: [40, 70, 90], + // }) + .shape('fill') + .color('turnout', [ + '#b2182b', + '#f9b393', + '#f8f6e9', + '#9fc7e0', + '#2166ac', + ]) + .style({ + opacity: 1, + }); + + scene.addLayer(layer); + addEuropeLayers(geoData, scene, layer); + } + return () => { + scene.destroy(); + }; + }, [geoData]); + + return ( +
+ ); +}; diff --git a/dev-demos/gallery/scale/index.md b/dev-demos/gallery/scale/index.md new file mode 100644 index 0000000000..5712341a4b --- /dev/null +++ b/dev-demos/gallery/scale/index.md @@ -0,0 +1,74 @@ +## Scale 简介 + +Scale 度量是将地图数据值(数字、日期、类别等数据)转成视觉值(颜色、大小、形状)。尺度 Scale 是数据可视化的基本组成部分,因为它们决定了视觉编码的性质。 L7 目前支持连续、离散、枚举类型数据的Scale,并支持位置、形状、大小和颜色编码的映射。 + +Range 和 domain 是 Scale 中非常重要的两个参数 + +- domain: 地图数据值的定义区间 +- range:视觉值的区间定义 + +不同Scale 的差异在于 domain->range 的转换方法的不同 + +| 数据类型 | 度量类型 | +| ---- | ---- | +| 连续 | linear、log、pow、time、quantize、quantile | +| 分类 | cat、timeCat | +| 常量 | identity | + +在使用 L7 开发过程中默认情况下不需要进行度量的配置,因为 G2 代码内部已经根据数据的形式对度量进行了假设,其计算过程如下: + +查看用户是否制定了对应字段的数据类型 (type) +如果没有,判断字段的第一条数据的字段类型 + +如果数据中不存在对应的字段,则为 'identity' +如果是数字则为 'linear'; +如果是字符串,判定是否是时间格式,如果是时间格式则为时间类型 'time', +否则是分类类型 'cat' + +### Cat + +Cat 指枚举类型,用于展示分类数据,比如农作物种植区分布图,水稻、玉米、大豆等不同类别需要映射为不同的颜色。 + +比如 + + domain = ['corn','rice',soybean'] + + range = ['red','white','blue'] + + 三种作物会分别转成对应的颜色 + + +### identify + +常量度量 某个字段是不变的常量。 + +### Linear + +线性是连续数据的映射方法,数据和视觉值是通过线性方法换算的。如数据值 1-100 线性映射到红到蓝的线下渐变色每个数字对应一个颜色 + +### Sequential + +### quantize + +相等间隔会将属性值的范围划分为若干个大小相等的子范围。相等间隔最适用于常见的数据范围,如百分比和温度。这种方法强调的是某个属性值相对于其他值的量 +### quantile + +每个类都含有相等数量的要素。分位数分类非常适用于呈线性分布的数据。分位数为每个类分配数量相等的数据值。不存在空类,也不存在值过多或过少的类。 +由于使用“分位数”分类将要素以同等数量分组到每个类中,因此得到的地图往往具有误导性。可能会将相似的要素置于相邻的类中,或将值差异较大的要素置于相同类中。可通过增加类的数量将这种失真降至最低。 + +### threshold + +他允许将域的任意子集(非统一段)映射到范围内的离散值。输入域仍然是连续的,并根据提供给域属性的一组阈值划分为多个切片。 range 属性必须有 N+1 个元素,其中 N 是域中提供的阈值边界数 + +手动设置间隔 Manual interval 手动设置分级分类区间,某些数据会有相应的业界标准,或者需要进行某种特殊的显示。如空气质量数据有严格数据分段标准 + +``` +-1 => "red" +0 => "white" +0.5 => "white" +1.0 => "blue" +1000 => "blue + +``` + +### diverging diff --git a/dev-demos/gallery/scale/linear.md b/dev-demos/gallery/scale/linear.md new file mode 100644 index 0000000000..0b3af945c1 --- /dev/null +++ b/dev-demos/gallery/scale/linear.md @@ -0,0 +1,2 @@ +### 连续线性 + \ No newline at end of file diff --git a/dev-demos/gallery/scale/linear.tsx b/dev-demos/gallery/scale/linear.tsx new file mode 100644 index 0000000000..b7fa4f6c31 --- /dev/null +++ b/dev-demos/gallery/scale/linear.tsx @@ -0,0 +1,54 @@ +import { PolygonLayer, Scene } from '@antv/l7'; +import { Mapbox } from '@antv/l7-maps'; +import React, { useEffect } from 'react'; +import { useData, addLayers } from './useLine'; + +export default () => { + const { geoData } = useData(); + + useEffect(() => { + const scene = new Scene({ + id: 'map', + map: new Mapbox({ + pitch: 0, + style: 'light', + center: [-96, 37.8], + zoom: 3, + }), + }); + if (geoData) { + const layer = new PolygonLayer({}) + .source(geoData.county, { + transforms: [ + { + type: 'join', + sourceField: 'id', + targetField: 'id', + data: geoData.unemploymentdata, + }, + ], + }) + .shape('fill') + .color('rate', ['#ffffcc', '#b6e2b6', '#64c1c0', '#338cbb', '#253494']) // '#b6e2b6', '#64c1c0', '#338cbb', + .style({ + opacity: 1, + }); + + scene.addLayer(layer); + addLayers(geoData, scene, layer); + } + return () => { + scene.destroy(); + }; + }, [geoData]); + + return ( +
+ ); +}; diff --git a/dev-demos/gallery/scale/quantile.md b/dev-demos/gallery/scale/quantile.md new file mode 100644 index 0000000000..b09c02e2a9 --- /dev/null +++ b/dev-demos/gallery/scale/quantile.md @@ -0,0 +1,2 @@ +### 等分位 + \ No newline at end of file diff --git a/dev-demos/gallery/scale/quantile.tsx b/dev-demos/gallery/scale/quantile.tsx new file mode 100644 index 0000000000..61034be468 --- /dev/null +++ b/dev-demos/gallery/scale/quantile.tsx @@ -0,0 +1,57 @@ +import { PolygonLayer, Scene } from '@antv/l7'; +import { Mapbox } from '@antv/l7-maps'; +import React, { useEffect } from 'react'; +import { useData, addLayers } from './useLine'; + +export default () => { + const { geoData } = useData(); + + useEffect(() => { + const scene = new Scene({ + id: 'map', + map: new Mapbox({ + pitch: 0, + style: 'light', + center: [-96, 37.8], + zoom: 3, + }), + }); + if (geoData) { + const layer = new PolygonLayer({}) + .source(geoData.county, { + transforms: [ + { + type: 'join', + sourceField: 'id', + targetField: 'id', + data: geoData.unemploymentdata, + }, + ], + }) + .scale('rate', { + type: 'quantile', + }) + .shape('fill') + .color('rate', ['#ffffcc', '#b6e2b6', '#64c1c0', '#338cbb', '#253494']) + .style({ + opacity: 1, + }); + + scene.addLayer(layer); + addLayers(geoData, scene, layer); + } + return () => { + scene.destroy(); + }; + }, [geoData]); + + return ( +
+ ); +}; diff --git a/dev-demos/gallery/scale/quantize.md b/dev-demos/gallery/scale/quantize.md new file mode 100644 index 0000000000..88eca372bf --- /dev/null +++ b/dev-demos/gallery/scale/quantize.md @@ -0,0 +1,2 @@ +### 等间距 + \ No newline at end of file diff --git a/dev-demos/gallery/scale/quantize.tsx b/dev-demos/gallery/scale/quantize.tsx new file mode 100644 index 0000000000..723247f40e --- /dev/null +++ b/dev-demos/gallery/scale/quantize.tsx @@ -0,0 +1,57 @@ +import { PolygonLayer, Scene } from '@antv/l7'; +import { Mapbox } from '@antv/l7-maps'; +import React, { useEffect } from 'react'; +import { useData, addLayers } from './useLine'; + +export default () => { + const { geoData } = useData(); + + useEffect(() => { + const scene = new Scene({ + id: 'map', + map: new Mapbox({ + pitch: 0, + style: 'light', + center: [-96, 37.8], + zoom: 3, + }), + }); + if (geoData) { + const layer = new PolygonLayer({}) + .source(geoData.county, { + transforms: [ + { + type: 'join', + sourceField: 'id', + targetField: 'id', + data: geoData.unemploymentdata, + }, + ], + }) + .scale('rate', { + type: 'quantize', + }) + .shape('fill') + .color('rate', ['#ffffcc', '#b6e2b6', '#64c1c0', '#338cbb', '#253494']) // '#b6e2b6', '#64c1c0', '#338cbb', + .style({ + opacity: 1, + }); + + scene.addLayer(layer); + addLayers(geoData, scene, layer); + } + return () => { + scene.destroy(); + }; + }, [geoData]); + + return ( +
+ ); +}; diff --git a/dev-demos/gallery/scale/sequential.md b/dev-demos/gallery/scale/sequential.md new file mode 100644 index 0000000000..b105cb06f8 --- /dev/null +++ b/dev-demos/gallery/scale/sequential.md @@ -0,0 +1,2 @@ +### 连续 + \ No newline at end of file diff --git a/dev-demos/gallery/scale/sequential.tsx b/dev-demos/gallery/scale/sequential.tsx new file mode 100644 index 0000000000..9b6337a5a7 --- /dev/null +++ b/dev-demos/gallery/scale/sequential.tsx @@ -0,0 +1,57 @@ +import { PolygonLayer, Scene } from '@antv/l7'; +import { Mapbox } from '@antv/l7-maps'; +import React, { useEffect } from 'react'; +import { useData, addLayers } from './useLine'; + +export default () => { + const { geoData } = useData(); + + useEffect(() => { + const scene = new Scene({ + id: 'map', + map: new Mapbox({ + pitch: 0, + style: 'light', + center: [-96, 37.8], + zoom: 3, + }), + }); + if (geoData) { + const layer = new PolygonLayer({}) + .source(geoData.county, { + transforms: [ + { + type: 'join', + sourceField: 'id', + targetField: 'id', + data: geoData.unemploymentdata, + }, + ], + }) + .scale('rate', { + type: 'sequential', // he input domain and output range of a sequential scale always has exactly two elements, and the output range is typically specified as an interpolator rather than an array of values. + }) + .shape('fill') + .color('rate', ['#ffffcc', '#253494']) + .style({ + opacity: 1, + }); + + scene.addLayer(layer); + addLayers(geoData, scene, layer); + } + return () => { + scene.destroy(); + }; + }, [geoData]); + + return ( +
+ ); +}; diff --git a/dev-demos/gallery/scale/threshold.md b/dev-demos/gallery/scale/threshold.md new file mode 100644 index 0000000000..ac1c6913d9 --- /dev/null +++ b/dev-demos/gallery/scale/threshold.md @@ -0,0 +1,3 @@ +### 自定义分段 + + \ No newline at end of file diff --git a/dev-demos/gallery/scale/threshold.tsx b/dev-demos/gallery/scale/threshold.tsx new file mode 100644 index 0000000000..6891c35963 --- /dev/null +++ b/dev-demos/gallery/scale/threshold.tsx @@ -0,0 +1,58 @@ +import { PolygonLayer, Scene } from '@antv/l7'; +import { Mapbox } from '@antv/l7-maps'; +import React, { useEffect } from 'react'; +import { useData, addLayers } from './useLine'; + +export default () => { + const { geoData } = useData(); + + useEffect(() => { + const scene = new Scene({ + id: 'map', + map: new Mapbox({ + pitch: 0, + style: 'light', + center: [-96, 37.8], + zoom: 3, + }), + }); + if (geoData) { + const layer = new PolygonLayer({}) + .source(geoData.county, { + transforms: [ + { + type: 'join', + sourceField: 'id', + targetField: 'id', + data: geoData.unemploymentdata, + }, + ], + }) + .scale('rate', { + type: 'threshold', + domain: [3, 6, 8, 10], + }) + .shape('fill') + .color('rate', ['#ffffcc', '#b6e2b6', '#64c1c0', '#338cbb', '#253494']) + .style({ + opacity: 1, + }); + + scene.addLayer(layer); + addLayers(geoData, scene, layer); + } + return () => { + scene.destroy(); + }; + }, [geoData]); + + return ( +
+ ); +}; diff --git a/dev-demos/gallery/scale/useLine.ts b/dev-demos/gallery/scale/useLine.ts new file mode 100644 index 0000000000..79a4631479 --- /dev/null +++ b/dev-demos/gallery/scale/useLine.ts @@ -0,0 +1,143 @@ +import { useEffect, useState } from 'react'; +import { LineLayer, PolygonLayer } from '@antv/l7'; +interface IData { + county: any; + state: any; + unemploymentdata: any; +} +interface IData2 { + country: any; + turnout: any; + } +export function useData() { + const [data, setData] = useState(undefined); + useEffect(() => { + Promise.all([ + fetch( + 'https://gw.alipayobjects.com/os/bmw-prod/5c4c7e02-a796-4c09-baba-629a99c909aa.json', + ).then((d) => d.json()), + // https://lab.isaaclin.cn/nCoV/api/area?latest=1 + fetch( + 'https://gw.alipayobjects.com/os/bmw-prod/738993d1-cc7e-4630-a318-80d6452fd125.csv', + ).then((d) => d.text()), + fetch( + ' https://gw.alipayobjects.com/os/bmw-prod/d13721bf-f0c2-4897-b6e6-633e6e022c09.json', + ).then((d) => d.json()), + ]).then(([county, unemployment, state]) => { + const unemploymentdata = unemployment + .split('\n') + .slice(0) + .map((line) => { + const item = line.split(','); + return { + id: item[0], + state: item[1], + county: item[2], + rate: item[3] * 1, + }; + }); + setData({ + county, + unemploymentdata, + state, + }); + }); + }, []); + + return { geoData: data }; +} + +export function useEuropeData() { + const [data, setData] = useState(undefined); + useEffect(() => { + Promise.all([ + fetch( + 'https://gw.alipayobjects.com/os/bmw-prod/01ff872b-99c6-4e41-bd3a-34c2134da597.json', + ).then((d) => d.json()), + // https://lab.isaaclin.cn/nCoV/api/area?latest=1 + fetch( + 'https://gw.alipayobjects.com/os/bmw-prod/64124f12-e086-4fe6-a900-bd57b410af69.csv', + ).then((d) => d.text()), + + ]).then(([country, turnoutData,]) => { + const turnout = turnoutData + .split('\n') + .slice(0) + .map((line) => { + const item = line.split(','); + return { + country: item[0], + turnout: item[1] *1, + }; + }); + setData({ + country, + turnout, + }); + }); + }, []); + + return { geoData: data }; + } + +export function addLayers(data: IData, scene, mainLayer) { + const linelayer = new PolygonLayer({}) + .source(data.county) + .size(0.5) + .shape('line') + .color('#fff') + .style({ + opacity: 1, + }); + const stateLayer = new PolygonLayer({}) + .source(data.state) + .size(1) + .shape('line') + .color('#fff') + .style({ + opacity: 1, + }); + + scene.addLayer(linelayer); + scene.addLayer(stateLayer); + addhightLayer(scene, mainLayer) + +} + +export function addEuropeLayers(data: IData2, scene, mainLayer) { + const linelayer = new PolygonLayer({}) + .source(data.country) + .size(0.5) + .shape('line') + .color('#fff') + .style({ + opacity: 1, + }); + + scene.addLayer(linelayer); + addhightLayer(scene, mainLayer) + } + + function addhightLayer(scene, mainLayer) { + const hightLayer = new LineLayer({ + zIndex: 4, // 设置显示层级 + name: 'hightlight', + }) + .source({ + type: 'FeatureCollection', + features: [], + }) + .shape('line') + .size(0.8) + .color('#000') + .style({ + opacity: 1, + }); + scene.addLayer(hightLayer); + mainLayer.on('click', (feature) => { + hightLayer.setData({ + type: 'FeatureCollection', + features: [feature.feature], + }); + }); + } \ No newline at end of file diff --git a/package.json b/package.json index 7b6aad0613..fef793d708 100644 --- a/package.json +++ b/package.json @@ -67,6 +67,7 @@ "babel-plugin-transform-inline-environment-variables": "^0.4.3", "babel-plugin-transform-node-env-inline": "^0.4.3", "babel-plugin-transform-postcss": "^0.3.0", + "babel-polyfill": "^6.26.0", "babel-preset-gatsby": "^1.12.0", "babel-template": "^6.26.0", "clean-webpack-plugin": "^3.0.0", @@ -79,6 +80,7 @@ "cz-conventional-changelog": "^3.0.2", "dat.gui": "^0.7.2", "dumi": "^1.1.0", + "element-remove": "^1.0.4", "enzyme": "^3.6.0", "enzyme-adapter-react-16": "^1.5.0", "enzyme-to-json": "^3.0.0-beta6", @@ -176,9 +178,9 @@ "release-beta": "yarn run prerelease && lerna publish --dist-tag beta from-package --force-publish && yarn sync", "release": "lerna publish from-package --force-publish && yarn sync", "release-cdn": "antv-bin upload -n @antv/l7", - "test": "umi-test", - "test-cover": "yarn run worker && umi-test --coverage", - "test-cover-viewer": "umi-test --coverage && cd coverage && http-server -p 6001 -o", + "test": "npm run worker && umi-test", + "test-cover": "npm run worker && umi-test --coverage", + "test-cover-viewer": "npm run worker && umi-test --coverage && cd coverage && http-server -p 6001 -o", "test-live": "umi-test --watch", "tsc": "tsc", "watch": "yarn clean && yarn worker && lerna run watch --parallel", diff --git a/packages/layers/src/plugins/FeatureScalePlugin.ts b/packages/layers/src/plugins/FeatureScalePlugin.ts index f88e4195bf..531d0f9c65 100644 --- a/packages/layers/src/plugins/FeatureScalePlugin.ts +++ b/packages/layers/src/plugins/FeatureScalePlugin.ts @@ -109,7 +109,6 @@ export default class FeatureScalePlugin implements ILayerPlugin { if (attribute.scale) { // 创建Scale const attributeScale = attribute.scale; - const type = attribute.name; attributeScale.names = this.parseFields(attribute!.scale!.field || []); const scales: IStyleScale[] = []; // 为每个字段创建 Scale diff --git a/packages/three/src/core/baseLayer.ts b/packages/three/src/core/baseLayer.ts index 0fe4295a2c..2305e45202 100644 --- a/packages/three/src/core/baseLayer.ts +++ b/packages/three/src/core/baseLayer.ts @@ -12,7 +12,6 @@ import { IThreeRenderService, ThreeRenderServiceType, } from './threeRenderService'; -const DEG2RAD = Math.PI / 180; type ILngLat = [number, number]; export default class ThreeJSLayer extends BaseLayer<{