diff --git a/.gitignore b/.gitignore index a0d55c96ad9..2f649d941f8 100644 --- a/.gitignore +++ b/.gitignore @@ -80,7 +80,7 @@ yarn-error.log *.min.js test-changelog.md *.tsbuildinfo -.venv + # Ignore package-lock in packages plugins/*/package-lock.json packages/*/package-lock.json diff --git a/UPDATING.md b/UPDATING.md index 78043de69d5..f36778beb48 100644 --- a/UPDATING.md +++ b/UPDATING.md @@ -24,7 +24,6 @@ assists people when migrating to a new version. ## Next -- [33603](https://github.com/apache/superset/pull/33603) OpenStreetView has been promoted as the new default for Deck.gl visualization since it can be enabled by default without requiring an API key. If you have Mapbox set up and want to disable OpenStreeView in your environment, please follow the steps documented here [https://superset.apache.org/docs/configuration/map-tiles]. - [33116](https://github.com/apache/superset/pull/33116) In Echarts Series charts (e.g. Line, Area, Bar, etc.) charts, the `x_axis_sort_series` and `x_axis_sort_series_ascending` form data items have been renamed with `x_axis_sort` and `x_axis_sort_asc`. There's a migration added that can potentially affect a significant number of existing charts. - [32317](https://github.com/apache/superset/pull/32317) The horizontal filter bar feature is now out of testing/beta development and its feature flag `HORIZONTAL_FILTER_BAR` has been removed. @@ -56,7 +55,6 @@ assists people when migrating to a new version. - [30284](https://github.com/apache/superset/pull/30284) Deprecated GLOBAL_ASYNC_QUERIES_REDIS_CONFIG in favor of the new GLOBAL_ASYNC_QUERIES_CACHE_BACKEND configuration. To leverage Redis Sentinel, set CACHE_TYPE to RedisSentinelCache, or use RedisCache for standalone Redis - [31961](https://github.com/apache/superset/pull/31961) Upgraded React from version 16.13.1 to 17.0.2. If you are using custom frontend extensions or plugins, you may need to update them to be compatible with React 17. - [31260](https://github.com/apache/superset/pull/31260) Docker images now use `uv pip install` instead of `pip install` to manage the python envrionment. Most docker-based deployments will be affected, whether you derive one of the published images, or have custom bootstrap script that install python libraries (drivers) -- [32432](https://github.com/apache/superset/pull/31260) Moves the List Roles FAB view to the frontend and requires `FAB_ADD_SECURITY_API` to be enabled in the configuration and `superset init` to be executed. ### Potential Downtime diff --git a/docs/docs/configuration/map-tiles.mdx b/docs/docs/configuration/map-tiles.mdx deleted file mode 100644 index e83608c38bb..00000000000 --- a/docs/docs/configuration/map-tiles.mdx +++ /dev/null @@ -1,78 +0,0 @@ ---- -title: Map Tiles -sidebar_position: 12 -version: 1 ---- - -# Map tiles - -Superset uses OSM and Mapbox tiles by default. OSM is free but you still need setting your MAPBOX_API_KEY if you want to use mapbox maps. - -## Setting map tiles - -Map tiles can be set with `DECKGL_BASE_MAP` in your `superset_config.py` or `superset_config_docker.py` -For adding your own map tiles, you can use the following format. - -```python -DECKGL_BASE_MAP = [ - ['tile://https://your_personal_url/{z}/{x}/{y}.png', 'MyTile'] -] -``` -Openstreetmap tiles url can be added without prefix. -```python -DECKGL_BASE_MAP = [ - ['https://c.tile.openstreetmap.org/{z}/{x}/{y}.png', 'OpenStreetMap'] -] -``` - -Default values are: -```python -DECKGL_BASE_MAP = [ - ['https://tile.openstreetmap.org/{z}/{x}/{y}.png', 'Streets (OSM)'], - ['https://tile.osm.ch/osm-swiss-style/{z}/{x}/{y}.png', 'Topography (OSM)'], - ['mapbox://styles/mapbox/streets-v9', 'Streets'], - ['mapbox://styles/mapbox/dark-v9', 'Dark'], - ['mapbox://styles/mapbox/light-v9', 'Light'], - ['mapbox://styles/mapbox/satellite-streets-v9', 'Satellite Streets'], - ['mapbox://styles/mapbox/satellite-v9', 'Satellite'], - ['mapbox://styles/mapbox/outdoors-v9', 'Outdoors'], -] -``` - -It is possible to set only mapbox by removing osm tiles and other way around. - -:::warning -Setting `DECKGL_BASE_MAP` overwrite default values -::: - -After defining your map tiles, set them in these variables: -- `CORS_OPTIONS` -- `connect-src` of `TALISMAN_CONFIG` and `TALISMAN_CONFIG_DEV` variables. - -```python -ENABLE_CORS = True -CORS_OPTIONS: dict[Any, Any] = { - "origins": [ - "https://tile.openstreetmap.org", - "https://tile.osm.ch", - "https://your_personal_url/{z}/{x}/{y}.png", - ] -} - -. -. - -TALISMAN_CONFIG = { - "content_security_policy": { - ... - "connect-src": [ - "'self'", - "https://api.mapbox.com", - "https://events.mapbox.com", - "https://tile.openstreetmap.org", - "https://tile.osm.ch", - "https://your_personal_url/{z}/{x}/{y}.png", - ], - ... -} -``` diff --git a/superset-frontend/package-lock.json b/superset-frontend/package-lock.json index f2f9b571d19..1caae0159c5 100644 --- a/superset-frontend/package-lock.json +++ b/superset-frontend/package-lock.json @@ -4046,12 +4046,30 @@ "ms": "^2.1.1" } }, + "node_modules/@deck.gl/aggregation-layers": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/@deck.gl/aggregation-layers/-/aggregation-layers-9.1.0.tgz", + "integrity": "sha512-h+ynSBeZL8keYyDS5N3pUxsNS5ZYsLpGGAg+05Qj9D5XCu/aACtCXMt3raPE6g+HqJBZyeUg4vmKBJnHxKw9LA==", + "license": "MIT", + "dependencies": { + "@luma.gl/constants": "^9.1.0", + "@luma.gl/shadertools": "^9.1.0", + "@math.gl/core": "^4.1.0", + "@math.gl/web-mercator": "^4.1.0", + "d3-hexbin": "^0.2.1" + }, + "peerDependencies": { + "@deck.gl/core": "^9.1.0", + "@deck.gl/layers": "^9.1.0", + "@luma.gl/core": "^9.1.0", + "@luma.gl/engine": "^9.1.0" + } + }, "node_modules/@deck.gl/core": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/@deck.gl/core/-/core-9.1.0.tgz", "integrity": "sha512-leocNGky9jZ0HUk8xm+HH4f+EL86PKiG8O4REpw4l/K1UbMssekR/L2moYwt7Bx98jSIibcUbhDWOBatKAUaTA==", "license": "MIT", - "peer": true, "dependencies": { "@loaders.gl/core": "^4.2.0", "@loaders.gl/images": "^4.2.0", @@ -4072,6 +4090,39 @@ "mjolnir.js": "^3.0.0" } }, + "node_modules/@deck.gl/layers": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/@deck.gl/layers/-/layers-9.1.0.tgz", + "integrity": "sha512-0GWZyHk5G48avEuGRbzHk60E0aNpo5h/3lSpzl0BSyqZnVTqsGa1jLJvjS17eaEkR51KIhUBf6NSCyGK1Tp22g==", + "license": "MIT", + "dependencies": { + "@loaders.gl/images": "^4.2.0", + "@loaders.gl/schema": "^4.2.0", + "@mapbox/tiny-sdf": "^2.0.5", + "@math.gl/core": "^4.1.0", + "@math.gl/polygon": "^4.1.0", + "@math.gl/web-mercator": "^4.1.0", + "earcut": "^2.2.4" + }, + "peerDependencies": { + "@deck.gl/core": "^9.1.0", + "@loaders.gl/core": "^4.2.0", + "@luma.gl/core": "^9.1.0", + "@luma.gl/engine": "^9.1.0" + } + }, + "node_modules/@deck.gl/react": { + "version": "9.1.4", + "resolved": "https://registry.npmjs.org/@deck.gl/react/-/react-9.1.4.tgz", + "integrity": "sha512-uZmgw3KDf+JSxOvruv6DsTIsatpYFOdBvB9nnMZLuUXlPyim3aw8UUnSUDz0oMD7aZptrc71iQIvUbeK0ZbsMA==", + "license": "MIT", + "peerDependencies": { + "@deck.gl/core": "^9.1.0", + "@deck.gl/widgets": "^9.1.0", + "react": ">=16.3.0", + "react-dom": ">=16.3.0" + } + }, "node_modules/@deck.gl/widgets": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/@deck.gl/widgets/-/widgets-9.1.0.tgz", @@ -7652,139 +7703,6 @@ "uuid": "dist/bin/uuid" } }, - "node_modules/@loaders.gl/3d-tiles": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@loaders.gl/3d-tiles/-/3d-tiles-4.3.4.tgz", - "integrity": "sha512-JQ3y3p/KlZP7lfobwON5t7H9WinXEYTvuo3SRQM8TBKhM+koEYZhvI2GwzoXx54MbBbY+s3fm1dq5UAAmaTsZw==", - "license": "MIT", - "dependencies": { - "@loaders.gl/compression": "4.3.4", - "@loaders.gl/crypto": "4.3.4", - "@loaders.gl/draco": "4.3.4", - "@loaders.gl/gltf": "4.3.4", - "@loaders.gl/images": "4.3.4", - "@loaders.gl/loader-utils": "4.3.4", - "@loaders.gl/math": "4.3.4", - "@loaders.gl/tiles": "4.3.4", - "@loaders.gl/zip": "4.3.4", - "@math.gl/core": "^4.1.0", - "@math.gl/culling": "^4.1.0", - "@math.gl/geospatial": "^4.1.0", - "@probe.gl/log": "^4.0.4", - "long": "^5.2.1" - }, - "peerDependencies": { - "@loaders.gl/core": "^4.3.0" - } - }, - "node_modules/@loaders.gl/3d-tiles/node_modules/@loaders.gl/loader-utils": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@loaders.gl/loader-utils/-/loader-utils-4.3.4.tgz", - "integrity": "sha512-tjMZvlKQSaMl2qmYTAxg+ySR6zd6hQn5n3XaU8+Ehp90TD3WzxvDKOMNDqOa72fFmIV+KgPhcmIJTpq4lAdC4Q==", - "license": "MIT", - "dependencies": { - "@loaders.gl/schema": "4.3.4", - "@loaders.gl/worker-utils": "4.3.4", - "@probe.gl/log": "^4.0.2", - "@probe.gl/stats": "^4.0.2" - }, - "peerDependencies": { - "@loaders.gl/core": "^4.3.0" - } - }, - "node_modules/@loaders.gl/3d-tiles/node_modules/@loaders.gl/schema": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@loaders.gl/schema/-/schema-4.3.4.tgz", - "integrity": "sha512-1YTYoatgzr/6JTxqBLwDiD3AVGwQZheYiQwAimWdRBVB0JAzych7s1yBuE0CVEzj4JDPKOzVAz8KnU1TiBvJGw==", - "license": "MIT", - "dependencies": { - "@types/geojson": "^7946.0.7" - }, - "peerDependencies": { - "@loaders.gl/core": "^4.3.0" - } - }, - "node_modules/@loaders.gl/3d-tiles/node_modules/@loaders.gl/worker-utils": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@loaders.gl/worker-utils/-/worker-utils-4.3.4.tgz", - "integrity": "sha512-EbsszrASgT85GH3B7jkx7YXfQyIYo/rlobwMx6V3ewETapPUwdSAInv+89flnk5n2eu2Lpdeh+2zS6PvqbL2RA==", - "license": "MIT", - "peerDependencies": { - "@loaders.gl/core": "^4.3.0" - } - }, - "node_modules/@loaders.gl/3d-tiles/node_modules/long": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", - "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", - "license": "Apache-2.0" - }, - "node_modules/@loaders.gl/compression": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@loaders.gl/compression/-/compression-4.3.4.tgz", - "integrity": "sha512-+o+5JqL9Sx8UCwdc2MTtjQiUHYQGJALHbYY/3CT+b9g/Emzwzez2Ggk9U9waRfdHiBCzEgRBivpWZEOAtkimXQ==", - "license": "MIT", - "dependencies": { - "@loaders.gl/loader-utils": "4.3.4", - "@loaders.gl/worker-utils": "4.3.4", - "@types/brotli": "^1.3.0", - "@types/pako": "^1.0.1", - "fflate": "0.7.4", - "lzo-wasm": "^0.0.4", - "pako": "1.0.11", - "snappyjs": "^0.6.1" - }, - "optionalDependencies": { - "brotli": "^1.3.2", - "lz4js": "^0.2.0", - "zstd-codec": "^0.1" - }, - "peerDependencies": { - "@loaders.gl/core": "^4.3.0" - } - }, - "node_modules/@loaders.gl/compression/node_modules/@loaders.gl/loader-utils": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@loaders.gl/loader-utils/-/loader-utils-4.3.4.tgz", - "integrity": "sha512-tjMZvlKQSaMl2qmYTAxg+ySR6zd6hQn5n3XaU8+Ehp90TD3WzxvDKOMNDqOa72fFmIV+KgPhcmIJTpq4lAdC4Q==", - "license": "MIT", - "dependencies": { - "@loaders.gl/schema": "4.3.4", - "@loaders.gl/worker-utils": "4.3.4", - "@probe.gl/log": "^4.0.2", - "@probe.gl/stats": "^4.0.2" - }, - "peerDependencies": { - "@loaders.gl/core": "^4.3.0" - } - }, - "node_modules/@loaders.gl/compression/node_modules/@loaders.gl/schema": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@loaders.gl/schema/-/schema-4.3.4.tgz", - "integrity": "sha512-1YTYoatgzr/6JTxqBLwDiD3AVGwQZheYiQwAimWdRBVB0JAzych7s1yBuE0CVEzj4JDPKOzVAz8KnU1TiBvJGw==", - "license": "MIT", - "dependencies": { - "@types/geojson": "^7946.0.7" - }, - "peerDependencies": { - "@loaders.gl/core": "^4.3.0" - } - }, - "node_modules/@loaders.gl/compression/node_modules/@loaders.gl/worker-utils": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@loaders.gl/worker-utils/-/worker-utils-4.3.4.tgz", - "integrity": "sha512-EbsszrASgT85GH3B7jkx7YXfQyIYo/rlobwMx6V3ewETapPUwdSAInv+89flnk5n2eu2Lpdeh+2zS6PvqbL2RA==", - "license": "MIT", - "peerDependencies": { - "@loaders.gl/core": "^4.3.0" - } - }, - "node_modules/@loaders.gl/compression/node_modules/fflate": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.7.4.tgz", - "integrity": "sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw==", - "license": "MIT" - }, "node_modules/@loaders.gl/core": { "version": "4.3.3", "resolved": "https://registry.npmjs.org/@loaders.gl/core/-/core-4.3.3.tgz", @@ -7797,260 +7715,18 @@ "@probe.gl/log": "^4.0.2" } }, - "node_modules/@loaders.gl/crypto": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@loaders.gl/crypto/-/crypto-4.3.4.tgz", - "integrity": "sha512-3VS5FgB44nLOlAB9Q82VOQnT1IltwfRa1miE0mpHCe1prYu1M/dMnEyynusbrsp+eDs3EKbxpguIS9HUsFu5dQ==", - "license": "MIT", - "dependencies": { - "@loaders.gl/loader-utils": "4.3.4", - "@loaders.gl/worker-utils": "4.3.4", - "@types/crypto-js": "^4.0.2" - }, - "peerDependencies": { - "@loaders.gl/core": "^4.3.0" - } - }, - "node_modules/@loaders.gl/crypto/node_modules/@loaders.gl/loader-utils": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@loaders.gl/loader-utils/-/loader-utils-4.3.4.tgz", - "integrity": "sha512-tjMZvlKQSaMl2qmYTAxg+ySR6zd6hQn5n3XaU8+Ehp90TD3WzxvDKOMNDqOa72fFmIV+KgPhcmIJTpq4lAdC4Q==", - "license": "MIT", - "dependencies": { - "@loaders.gl/schema": "4.3.4", - "@loaders.gl/worker-utils": "4.3.4", - "@probe.gl/log": "^4.0.2", - "@probe.gl/stats": "^4.0.2" - }, - "peerDependencies": { - "@loaders.gl/core": "^4.3.0" - } - }, - "node_modules/@loaders.gl/crypto/node_modules/@loaders.gl/schema": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@loaders.gl/schema/-/schema-4.3.4.tgz", - "integrity": "sha512-1YTYoatgzr/6JTxqBLwDiD3AVGwQZheYiQwAimWdRBVB0JAzych7s1yBuE0CVEzj4JDPKOzVAz8KnU1TiBvJGw==", - "license": "MIT", - "dependencies": { - "@types/geojson": "^7946.0.7" - }, - "peerDependencies": { - "@loaders.gl/core": "^4.3.0" - } - }, - "node_modules/@loaders.gl/crypto/node_modules/@loaders.gl/worker-utils": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@loaders.gl/worker-utils/-/worker-utils-4.3.4.tgz", - "integrity": "sha512-EbsszrASgT85GH3B7jkx7YXfQyIYo/rlobwMx6V3ewETapPUwdSAInv+89flnk5n2eu2Lpdeh+2zS6PvqbL2RA==", - "license": "MIT", - "peerDependencies": { - "@loaders.gl/core": "^4.3.0" - } - }, - "node_modules/@loaders.gl/draco": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@loaders.gl/draco/-/draco-4.3.4.tgz", - "integrity": "sha512-4Lx0rKmYENGspvcgV5XDpFD9o+NamXoazSSl9Oa3pjVVjo+HJuzCgrxTQYD/3JvRrolW/QRehZeWD/L/cEC6mw==", - "license": "MIT", - "dependencies": { - "@loaders.gl/loader-utils": "4.3.4", - "@loaders.gl/schema": "4.3.4", - "@loaders.gl/worker-utils": "4.3.4", - "draco3d": "1.5.7" - }, - "peerDependencies": { - "@loaders.gl/core": "^4.3.0" - } - }, - "node_modules/@loaders.gl/draco/node_modules/@loaders.gl/loader-utils": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@loaders.gl/loader-utils/-/loader-utils-4.3.4.tgz", - "integrity": "sha512-tjMZvlKQSaMl2qmYTAxg+ySR6zd6hQn5n3XaU8+Ehp90TD3WzxvDKOMNDqOa72fFmIV+KgPhcmIJTpq4lAdC4Q==", - "license": "MIT", - "dependencies": { - "@loaders.gl/schema": "4.3.4", - "@loaders.gl/worker-utils": "4.3.4", - "@probe.gl/log": "^4.0.2", - "@probe.gl/stats": "^4.0.2" - }, - "peerDependencies": { - "@loaders.gl/core": "^4.3.0" - } - }, - "node_modules/@loaders.gl/draco/node_modules/@loaders.gl/schema": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@loaders.gl/schema/-/schema-4.3.4.tgz", - "integrity": "sha512-1YTYoatgzr/6JTxqBLwDiD3AVGwQZheYiQwAimWdRBVB0JAzych7s1yBuE0CVEzj4JDPKOzVAz8KnU1TiBvJGw==", - "license": "MIT", - "dependencies": { - "@types/geojson": "^7946.0.7" - }, - "peerDependencies": { - "@loaders.gl/core": "^4.3.0" - } - }, - "node_modules/@loaders.gl/draco/node_modules/@loaders.gl/worker-utils": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@loaders.gl/worker-utils/-/worker-utils-4.3.4.tgz", - "integrity": "sha512-EbsszrASgT85GH3B7jkx7YXfQyIYo/rlobwMx6V3ewETapPUwdSAInv+89flnk5n2eu2Lpdeh+2zS6PvqbL2RA==", - "license": "MIT", - "peerDependencies": { - "@loaders.gl/core": "^4.3.0" - } - }, - "node_modules/@loaders.gl/gis": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@loaders.gl/gis/-/gis-4.3.4.tgz", - "integrity": "sha512-8xub38lSWW7+ZXWuUcggk7agRHJUy6RdipLNKZ90eE0ZzLNGDstGD1qiBwkvqH0AkG+uz4B7Kkiptyl7w2Oa6g==", - "license": "MIT", - "dependencies": { - "@loaders.gl/loader-utils": "4.3.4", - "@loaders.gl/schema": "4.3.4", - "@mapbox/vector-tile": "^1.3.1", - "@math.gl/polygon": "^4.1.0", - "pbf": "^3.2.1" - }, - "peerDependencies": { - "@loaders.gl/core": "^4.3.0" - } - }, - "node_modules/@loaders.gl/gis/node_modules/@loaders.gl/loader-utils": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@loaders.gl/loader-utils/-/loader-utils-4.3.4.tgz", - "integrity": "sha512-tjMZvlKQSaMl2qmYTAxg+ySR6zd6hQn5n3XaU8+Ehp90TD3WzxvDKOMNDqOa72fFmIV+KgPhcmIJTpq4lAdC4Q==", - "license": "MIT", - "dependencies": { - "@loaders.gl/schema": "4.3.4", - "@loaders.gl/worker-utils": "4.3.4", - "@probe.gl/log": "^4.0.2", - "@probe.gl/stats": "^4.0.2" - }, - "peerDependencies": { - "@loaders.gl/core": "^4.3.0" - } - }, - "node_modules/@loaders.gl/gis/node_modules/@loaders.gl/schema": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@loaders.gl/schema/-/schema-4.3.4.tgz", - "integrity": "sha512-1YTYoatgzr/6JTxqBLwDiD3AVGwQZheYiQwAimWdRBVB0JAzych7s1yBuE0CVEzj4JDPKOzVAz8KnU1TiBvJGw==", - "license": "MIT", - "dependencies": { - "@types/geojson": "^7946.0.7" - }, - "peerDependencies": { - "@loaders.gl/core": "^4.3.0" - } - }, - "node_modules/@loaders.gl/gis/node_modules/@loaders.gl/worker-utils": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@loaders.gl/worker-utils/-/worker-utils-4.3.4.tgz", - "integrity": "sha512-EbsszrASgT85GH3B7jkx7YXfQyIYo/rlobwMx6V3ewETapPUwdSAInv+89flnk5n2eu2Lpdeh+2zS6PvqbL2RA==", - "license": "MIT", - "peerDependencies": { - "@loaders.gl/core": "^4.3.0" - } - }, - "node_modules/@loaders.gl/gltf": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@loaders.gl/gltf/-/gltf-4.3.4.tgz", - "integrity": "sha512-EiUTiLGMfukLd9W98wMpKmw+hVRhQ0dJ37wdlXK98XPeGGB+zTQxCcQY+/BaMhsSpYt/OOJleHhTfwNr8RgzRg==", - "license": "MIT", - "dependencies": { - "@loaders.gl/draco": "4.3.4", - "@loaders.gl/images": "4.3.4", - "@loaders.gl/loader-utils": "4.3.4", - "@loaders.gl/schema": "4.3.4", - "@loaders.gl/textures": "4.3.4", - "@math.gl/core": "^4.1.0" - }, - "peerDependencies": { - "@loaders.gl/core": "^4.3.0" - } - }, - "node_modules/@loaders.gl/gltf/node_modules/@loaders.gl/loader-utils": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@loaders.gl/loader-utils/-/loader-utils-4.3.4.tgz", - "integrity": "sha512-tjMZvlKQSaMl2qmYTAxg+ySR6zd6hQn5n3XaU8+Ehp90TD3WzxvDKOMNDqOa72fFmIV+KgPhcmIJTpq4lAdC4Q==", - "license": "MIT", - "dependencies": { - "@loaders.gl/schema": "4.3.4", - "@loaders.gl/worker-utils": "4.3.4", - "@probe.gl/log": "^4.0.2", - "@probe.gl/stats": "^4.0.2" - }, - "peerDependencies": { - "@loaders.gl/core": "^4.3.0" - } - }, - "node_modules/@loaders.gl/gltf/node_modules/@loaders.gl/schema": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@loaders.gl/schema/-/schema-4.3.4.tgz", - "integrity": "sha512-1YTYoatgzr/6JTxqBLwDiD3AVGwQZheYiQwAimWdRBVB0JAzych7s1yBuE0CVEzj4JDPKOzVAz8KnU1TiBvJGw==", - "license": "MIT", - "dependencies": { - "@types/geojson": "^7946.0.7" - }, - "peerDependencies": { - "@loaders.gl/core": "^4.3.0" - } - }, - "node_modules/@loaders.gl/gltf/node_modules/@loaders.gl/worker-utils": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@loaders.gl/worker-utils/-/worker-utils-4.3.4.tgz", - "integrity": "sha512-EbsszrASgT85GH3B7jkx7YXfQyIYo/rlobwMx6V3ewETapPUwdSAInv+89flnk5n2eu2Lpdeh+2zS6PvqbL2RA==", - "license": "MIT", - "peerDependencies": { - "@loaders.gl/core": "^4.3.0" - } - }, "node_modules/@loaders.gl/images": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@loaders.gl/images/-/images-4.3.4.tgz", - "integrity": "sha512-qgc33BaNsqN9cWa/xvcGvQ50wGDONgQQdzHCKDDKhV2w/uptZoR5iofJfuG8UUV2vUMMd82Uk9zbopRx2rS4Ag==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@loaders.gl/images/-/images-4.3.3.tgz", + "integrity": "sha512-s4InjIXqEu0T7anZLj4OBUuDBt2BNnAD0GLzSexSkBfQZfpXY0XJNl4mMf5nUKb5NDfXhIKIqv8y324US+I28A==", "license": "MIT", "dependencies": { - "@loaders.gl/loader-utils": "4.3.4" + "@loaders.gl/loader-utils": "4.3.3" }, "peerDependencies": { "@loaders.gl/core": "^4.3.0" } }, - "node_modules/@loaders.gl/images/node_modules/@loaders.gl/loader-utils": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@loaders.gl/loader-utils/-/loader-utils-4.3.4.tgz", - "integrity": "sha512-tjMZvlKQSaMl2qmYTAxg+ySR6zd6hQn5n3XaU8+Ehp90TD3WzxvDKOMNDqOa72fFmIV+KgPhcmIJTpq4lAdC4Q==", - "license": "MIT", - "dependencies": { - "@loaders.gl/schema": "4.3.4", - "@loaders.gl/worker-utils": "4.3.4", - "@probe.gl/log": "^4.0.2", - "@probe.gl/stats": "^4.0.2" - }, - "peerDependencies": { - "@loaders.gl/core": "^4.3.0" - } - }, - "node_modules/@loaders.gl/images/node_modules/@loaders.gl/schema": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@loaders.gl/schema/-/schema-4.3.4.tgz", - "integrity": "sha512-1YTYoatgzr/6JTxqBLwDiD3AVGwQZheYiQwAimWdRBVB0JAzych7s1yBuE0CVEzj4JDPKOzVAz8KnU1TiBvJGw==", - "license": "MIT", - "dependencies": { - "@types/geojson": "^7946.0.7" - }, - "peerDependencies": { - "@loaders.gl/core": "^4.3.0" - } - }, - "node_modules/@loaders.gl/images/node_modules/@loaders.gl/worker-utils": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@loaders.gl/worker-utils/-/worker-utils-4.3.4.tgz", - "integrity": "sha512-EbsszrASgT85GH3B7jkx7YXfQyIYo/rlobwMx6V3ewETapPUwdSAInv+89flnk5n2eu2Lpdeh+2zS6PvqbL2RA==", - "license": "MIT", - "peerDependencies": { - "@loaders.gl/core": "^4.3.0" - } - }, "node_modules/@loaders.gl/loader-utils": { "version": "4.3.3", "resolved": "https://registry.npmjs.org/@loaders.gl/loader-utils/-/loader-utils-4.3.3.tgz", @@ -8066,110 +7742,6 @@ "@loaders.gl/core": "^4.3.0" } }, - "node_modules/@loaders.gl/math": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@loaders.gl/math/-/math-4.3.4.tgz", - "integrity": "sha512-UJrlHys1fp9EUO4UMnqTCqvKvUjJVCbYZ2qAKD7tdGzHJYT8w/nsP7f/ZOYFc//JlfC3nq+5ogvmdpq2pyu3TA==", - "license": "MIT", - "dependencies": { - "@loaders.gl/images": "4.3.4", - "@loaders.gl/loader-utils": "4.3.4", - "@math.gl/core": "^4.1.0" - }, - "peerDependencies": { - "@loaders.gl/core": "^4.3.0" - } - }, - "node_modules/@loaders.gl/math/node_modules/@loaders.gl/loader-utils": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@loaders.gl/loader-utils/-/loader-utils-4.3.4.tgz", - "integrity": "sha512-tjMZvlKQSaMl2qmYTAxg+ySR6zd6hQn5n3XaU8+Ehp90TD3WzxvDKOMNDqOa72fFmIV+KgPhcmIJTpq4lAdC4Q==", - "license": "MIT", - "dependencies": { - "@loaders.gl/schema": "4.3.4", - "@loaders.gl/worker-utils": "4.3.4", - "@probe.gl/log": "^4.0.2", - "@probe.gl/stats": "^4.0.2" - }, - "peerDependencies": { - "@loaders.gl/core": "^4.3.0" - } - }, - "node_modules/@loaders.gl/math/node_modules/@loaders.gl/schema": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@loaders.gl/schema/-/schema-4.3.4.tgz", - "integrity": "sha512-1YTYoatgzr/6JTxqBLwDiD3AVGwQZheYiQwAimWdRBVB0JAzych7s1yBuE0CVEzj4JDPKOzVAz8KnU1TiBvJGw==", - "license": "MIT", - "dependencies": { - "@types/geojson": "^7946.0.7" - }, - "peerDependencies": { - "@loaders.gl/core": "^4.3.0" - } - }, - "node_modules/@loaders.gl/math/node_modules/@loaders.gl/worker-utils": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@loaders.gl/worker-utils/-/worker-utils-4.3.4.tgz", - "integrity": "sha512-EbsszrASgT85GH3B7jkx7YXfQyIYo/rlobwMx6V3ewETapPUwdSAInv+89flnk5n2eu2Lpdeh+2zS6PvqbL2RA==", - "license": "MIT", - "peerDependencies": { - "@loaders.gl/core": "^4.3.0" - } - }, - "node_modules/@loaders.gl/mvt": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@loaders.gl/mvt/-/mvt-4.3.4.tgz", - "integrity": "sha512-9DrJX8RQf14htNtxsPIYvTso5dUce9WaJCWCIY/79KYE80Be6dhcEYMknxBS4w3+PAuImaAe66S5xo9B7Erm5A==", - "license": "MIT", - "dependencies": { - "@loaders.gl/gis": "4.3.4", - "@loaders.gl/images": "4.3.4", - "@loaders.gl/loader-utils": "4.3.4", - "@loaders.gl/schema": "4.3.4", - "@math.gl/polygon": "^4.1.0", - "@probe.gl/stats": "^4.0.0", - "pbf": "^3.2.1" - }, - "peerDependencies": { - "@loaders.gl/core": "^4.3.0" - } - }, - "node_modules/@loaders.gl/mvt/node_modules/@loaders.gl/loader-utils": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@loaders.gl/loader-utils/-/loader-utils-4.3.4.tgz", - "integrity": "sha512-tjMZvlKQSaMl2qmYTAxg+ySR6zd6hQn5n3XaU8+Ehp90TD3WzxvDKOMNDqOa72fFmIV+KgPhcmIJTpq4lAdC4Q==", - "license": "MIT", - "dependencies": { - "@loaders.gl/schema": "4.3.4", - "@loaders.gl/worker-utils": "4.3.4", - "@probe.gl/log": "^4.0.2", - "@probe.gl/stats": "^4.0.2" - }, - "peerDependencies": { - "@loaders.gl/core": "^4.3.0" - } - }, - "node_modules/@loaders.gl/mvt/node_modules/@loaders.gl/schema": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@loaders.gl/schema/-/schema-4.3.4.tgz", - "integrity": "sha512-1YTYoatgzr/6JTxqBLwDiD3AVGwQZheYiQwAimWdRBVB0JAzych7s1yBuE0CVEzj4JDPKOzVAz8KnU1TiBvJGw==", - "license": "MIT", - "dependencies": { - "@types/geojson": "^7946.0.7" - }, - "peerDependencies": { - "@loaders.gl/core": "^4.3.0" - } - }, - "node_modules/@loaders.gl/mvt/node_modules/@loaders.gl/worker-utils": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@loaders.gl/worker-utils/-/worker-utils-4.3.4.tgz", - "integrity": "sha512-EbsszrASgT85GH3B7jkx7YXfQyIYo/rlobwMx6V3ewETapPUwdSAInv+89flnk5n2eu2Lpdeh+2zS6PvqbL2RA==", - "license": "MIT", - "peerDependencies": { - "@loaders.gl/core": "^4.3.0" - } - }, "node_modules/@loaders.gl/schema": { "version": "4.3.3", "resolved": "https://registry.npmjs.org/@loaders.gl/schema/-/schema-4.3.3.tgz", @@ -8182,218 +7754,6 @@ "@loaders.gl/core": "^4.3.0" } }, - "node_modules/@loaders.gl/terrain": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@loaders.gl/terrain/-/terrain-4.3.4.tgz", - "integrity": "sha512-JszbRJGnxL5Fh82uA2U8HgjlsIpzYoCNNjy3cFsgCaxi4/dvjz3BkLlBilR7JlbX8Ka+zlb4GAbDDChiXLMJ/g==", - "license": "MIT", - "dependencies": { - "@loaders.gl/images": "4.3.4", - "@loaders.gl/loader-utils": "4.3.4", - "@loaders.gl/schema": "4.3.4", - "@mapbox/martini": "^0.2.0" - }, - "peerDependencies": { - "@loaders.gl/core": "^4.3.0" - } - }, - "node_modules/@loaders.gl/terrain/node_modules/@loaders.gl/loader-utils": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@loaders.gl/loader-utils/-/loader-utils-4.3.4.tgz", - "integrity": "sha512-tjMZvlKQSaMl2qmYTAxg+ySR6zd6hQn5n3XaU8+Ehp90TD3WzxvDKOMNDqOa72fFmIV+KgPhcmIJTpq4lAdC4Q==", - "license": "MIT", - "dependencies": { - "@loaders.gl/schema": "4.3.4", - "@loaders.gl/worker-utils": "4.3.4", - "@probe.gl/log": "^4.0.2", - "@probe.gl/stats": "^4.0.2" - }, - "peerDependencies": { - "@loaders.gl/core": "^4.3.0" - } - }, - "node_modules/@loaders.gl/terrain/node_modules/@loaders.gl/schema": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@loaders.gl/schema/-/schema-4.3.4.tgz", - "integrity": "sha512-1YTYoatgzr/6JTxqBLwDiD3AVGwQZheYiQwAimWdRBVB0JAzych7s1yBuE0CVEzj4JDPKOzVAz8KnU1TiBvJGw==", - "license": "MIT", - "dependencies": { - "@types/geojson": "^7946.0.7" - }, - "peerDependencies": { - "@loaders.gl/core": "^4.3.0" - } - }, - "node_modules/@loaders.gl/terrain/node_modules/@loaders.gl/worker-utils": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@loaders.gl/worker-utils/-/worker-utils-4.3.4.tgz", - "integrity": "sha512-EbsszrASgT85GH3B7jkx7YXfQyIYo/rlobwMx6V3ewETapPUwdSAInv+89flnk5n2eu2Lpdeh+2zS6PvqbL2RA==", - "license": "MIT", - "peerDependencies": { - "@loaders.gl/core": "^4.3.0" - } - }, - "node_modules/@loaders.gl/textures": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@loaders.gl/textures/-/textures-4.3.4.tgz", - "integrity": "sha512-arWIDjlE7JaDS6v9by7juLfxPGGnjT9JjleaXx3wq/PTp+psLOpGUywHXm38BNECos3MFEQK3/GFShWI+/dWPw==", - "license": "MIT", - "dependencies": { - "@loaders.gl/images": "4.3.4", - "@loaders.gl/loader-utils": "4.3.4", - "@loaders.gl/schema": "4.3.4", - "@loaders.gl/worker-utils": "4.3.4", - "@math.gl/types": "^4.1.0", - "ktx-parse": "^0.7.0", - "texture-compressor": "^1.0.2" - }, - "peerDependencies": { - "@loaders.gl/core": "^4.3.0" - } - }, - "node_modules/@loaders.gl/textures/node_modules/@loaders.gl/loader-utils": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@loaders.gl/loader-utils/-/loader-utils-4.3.4.tgz", - "integrity": "sha512-tjMZvlKQSaMl2qmYTAxg+ySR6zd6hQn5n3XaU8+Ehp90TD3WzxvDKOMNDqOa72fFmIV+KgPhcmIJTpq4lAdC4Q==", - "license": "MIT", - "dependencies": { - "@loaders.gl/schema": "4.3.4", - "@loaders.gl/worker-utils": "4.3.4", - "@probe.gl/log": "^4.0.2", - "@probe.gl/stats": "^4.0.2" - }, - "peerDependencies": { - "@loaders.gl/core": "^4.3.0" - } - }, - "node_modules/@loaders.gl/textures/node_modules/@loaders.gl/schema": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@loaders.gl/schema/-/schema-4.3.4.tgz", - "integrity": "sha512-1YTYoatgzr/6JTxqBLwDiD3AVGwQZheYiQwAimWdRBVB0JAzych7s1yBuE0CVEzj4JDPKOzVAz8KnU1TiBvJGw==", - "license": "MIT", - "dependencies": { - "@types/geojson": "^7946.0.7" - }, - "peerDependencies": { - "@loaders.gl/core": "^4.3.0" - } - }, - "node_modules/@loaders.gl/textures/node_modules/@loaders.gl/worker-utils": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@loaders.gl/worker-utils/-/worker-utils-4.3.4.tgz", - "integrity": "sha512-EbsszrASgT85GH3B7jkx7YXfQyIYo/rlobwMx6V3ewETapPUwdSAInv+89flnk5n2eu2Lpdeh+2zS6PvqbL2RA==", - "license": "MIT", - "peerDependencies": { - "@loaders.gl/core": "^4.3.0" - } - }, - "node_modules/@loaders.gl/tiles": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@loaders.gl/tiles/-/tiles-4.3.4.tgz", - "integrity": "sha512-oC0zJfyvGox6Ag9ABF8fxOkx9yEFVyzTa9ryHXl2BqLiQoR1v3p+0tIJcEbh5cnzHfoTZzUis1TEAZluPRsHBQ==", - "license": "MIT", - "dependencies": { - "@loaders.gl/loader-utils": "4.3.4", - "@loaders.gl/math": "4.3.4", - "@math.gl/core": "^4.1.0", - "@math.gl/culling": "^4.1.0", - "@math.gl/geospatial": "^4.1.0", - "@math.gl/web-mercator": "^4.1.0", - "@probe.gl/stats": "^4.0.2" - }, - "peerDependencies": { - "@loaders.gl/core": "^4.3.0" - } - }, - "node_modules/@loaders.gl/tiles/node_modules/@loaders.gl/loader-utils": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@loaders.gl/loader-utils/-/loader-utils-4.3.4.tgz", - "integrity": "sha512-tjMZvlKQSaMl2qmYTAxg+ySR6zd6hQn5n3XaU8+Ehp90TD3WzxvDKOMNDqOa72fFmIV+KgPhcmIJTpq4lAdC4Q==", - "license": "MIT", - "dependencies": { - "@loaders.gl/schema": "4.3.4", - "@loaders.gl/worker-utils": "4.3.4", - "@probe.gl/log": "^4.0.2", - "@probe.gl/stats": "^4.0.2" - }, - "peerDependencies": { - "@loaders.gl/core": "^4.3.0" - } - }, - "node_modules/@loaders.gl/tiles/node_modules/@loaders.gl/schema": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@loaders.gl/schema/-/schema-4.3.4.tgz", - "integrity": "sha512-1YTYoatgzr/6JTxqBLwDiD3AVGwQZheYiQwAimWdRBVB0JAzych7s1yBuE0CVEzj4JDPKOzVAz8KnU1TiBvJGw==", - "license": "MIT", - "dependencies": { - "@types/geojson": "^7946.0.7" - }, - "peerDependencies": { - "@loaders.gl/core": "^4.3.0" - } - }, - "node_modules/@loaders.gl/tiles/node_modules/@loaders.gl/worker-utils": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@loaders.gl/worker-utils/-/worker-utils-4.3.4.tgz", - "integrity": "sha512-EbsszrASgT85GH3B7jkx7YXfQyIYo/rlobwMx6V3ewETapPUwdSAInv+89flnk5n2eu2Lpdeh+2zS6PvqbL2RA==", - "license": "MIT", - "peerDependencies": { - "@loaders.gl/core": "^4.3.0" - } - }, - "node_modules/@loaders.gl/wms": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@loaders.gl/wms/-/wms-4.3.4.tgz", - "integrity": "sha512-yXF0wuYzJUdzAJQrhLIua6DnjOiBJusaY1j8gpvuH1VYs3mzvWlIRuZKeUd9mduQZKK88H2IzHZbj2RGOauq4w==", - "license": "MIT", - "dependencies": { - "@loaders.gl/images": "4.3.4", - "@loaders.gl/loader-utils": "4.3.4", - "@loaders.gl/schema": "4.3.4", - "@loaders.gl/xml": "4.3.4", - "@turf/rewind": "^5.1.5", - "deep-strict-equal": "^0.2.0" - }, - "peerDependencies": { - "@loaders.gl/core": "^4.3.0" - } - }, - "node_modules/@loaders.gl/wms/node_modules/@loaders.gl/loader-utils": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@loaders.gl/loader-utils/-/loader-utils-4.3.4.tgz", - "integrity": "sha512-tjMZvlKQSaMl2qmYTAxg+ySR6zd6hQn5n3XaU8+Ehp90TD3WzxvDKOMNDqOa72fFmIV+KgPhcmIJTpq4lAdC4Q==", - "license": "MIT", - "dependencies": { - "@loaders.gl/schema": "4.3.4", - "@loaders.gl/worker-utils": "4.3.4", - "@probe.gl/log": "^4.0.2", - "@probe.gl/stats": "^4.0.2" - }, - "peerDependencies": { - "@loaders.gl/core": "^4.3.0" - } - }, - "node_modules/@loaders.gl/wms/node_modules/@loaders.gl/schema": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@loaders.gl/schema/-/schema-4.3.4.tgz", - "integrity": "sha512-1YTYoatgzr/6JTxqBLwDiD3AVGwQZheYiQwAimWdRBVB0JAzych7s1yBuE0CVEzj4JDPKOzVAz8KnU1TiBvJGw==", - "license": "MIT", - "dependencies": { - "@types/geojson": "^7946.0.7" - }, - "peerDependencies": { - "@loaders.gl/core": "^4.3.0" - } - }, - "node_modules/@loaders.gl/wms/node_modules/@loaders.gl/worker-utils": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@loaders.gl/worker-utils/-/worker-utils-4.3.4.tgz", - "integrity": "sha512-EbsszrASgT85GH3B7jkx7YXfQyIYo/rlobwMx6V3ewETapPUwdSAInv+89flnk5n2eu2Lpdeh+2zS6PvqbL2RA==", - "license": "MIT", - "peerDependencies": { - "@loaders.gl/core": "^4.3.0" - } - }, "node_modules/@loaders.gl/worker-utils": { "version": "4.3.3", "resolved": "https://registry.npmjs.org/@loaders.gl/worker-utils/-/worker-utils-4.3.3.tgz", @@ -8403,121 +7763,17 @@ "@loaders.gl/core": "^4.3.0" } }, - "node_modules/@loaders.gl/xml": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@loaders.gl/xml/-/xml-4.3.4.tgz", - "integrity": "sha512-p+y/KskajsvyM3a01BwUgjons/j/dUhniqd5y1p6keLOuwoHlY/TfTKd+XluqfyP14vFrdAHCZTnFCWLblN10w==", - "license": "MIT", - "dependencies": { - "@loaders.gl/loader-utils": "4.3.4", - "@loaders.gl/schema": "4.3.4", - "fast-xml-parser": "^4.2.5" - }, - "peerDependencies": { - "@loaders.gl/core": "^4.3.0" - } - }, - "node_modules/@loaders.gl/xml/node_modules/@loaders.gl/loader-utils": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@loaders.gl/loader-utils/-/loader-utils-4.3.4.tgz", - "integrity": "sha512-tjMZvlKQSaMl2qmYTAxg+ySR6zd6hQn5n3XaU8+Ehp90TD3WzxvDKOMNDqOa72fFmIV+KgPhcmIJTpq4lAdC4Q==", - "license": "MIT", - "dependencies": { - "@loaders.gl/schema": "4.3.4", - "@loaders.gl/worker-utils": "4.3.4", - "@probe.gl/log": "^4.0.2", - "@probe.gl/stats": "^4.0.2" - }, - "peerDependencies": { - "@loaders.gl/core": "^4.3.0" - } - }, - "node_modules/@loaders.gl/xml/node_modules/@loaders.gl/schema": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@loaders.gl/schema/-/schema-4.3.4.tgz", - "integrity": "sha512-1YTYoatgzr/6JTxqBLwDiD3AVGwQZheYiQwAimWdRBVB0JAzych7s1yBuE0CVEzj4JDPKOzVAz8KnU1TiBvJGw==", - "license": "MIT", - "dependencies": { - "@types/geojson": "^7946.0.7" - }, - "peerDependencies": { - "@loaders.gl/core": "^4.3.0" - } - }, - "node_modules/@loaders.gl/xml/node_modules/@loaders.gl/worker-utils": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@loaders.gl/worker-utils/-/worker-utils-4.3.4.tgz", - "integrity": "sha512-EbsszrASgT85GH3B7jkx7YXfQyIYo/rlobwMx6V3ewETapPUwdSAInv+89flnk5n2eu2Lpdeh+2zS6PvqbL2RA==", - "license": "MIT", - "peerDependencies": { - "@loaders.gl/core": "^4.3.0" - } - }, - "node_modules/@loaders.gl/zip": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@loaders.gl/zip/-/zip-4.3.4.tgz", - "integrity": "sha512-bHY4XdKYJm3vl9087GMoxnUqSURwTxPPh6DlAGOmz6X9Mp3JyWuA2gk3tQ1UIuInfjXKph3WAUfGe6XRIs1sfw==", - "license": "MIT", - "dependencies": { - "@loaders.gl/compression": "4.3.4", - "@loaders.gl/crypto": "4.3.4", - "@loaders.gl/loader-utils": "4.3.4", - "jszip": "^3.1.5", - "md5": "^2.3.0" - }, - "peerDependencies": { - "@loaders.gl/core": "^4.3.0" - } - }, - "node_modules/@loaders.gl/zip/node_modules/@loaders.gl/loader-utils": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@loaders.gl/loader-utils/-/loader-utils-4.3.4.tgz", - "integrity": "sha512-tjMZvlKQSaMl2qmYTAxg+ySR6zd6hQn5n3XaU8+Ehp90TD3WzxvDKOMNDqOa72fFmIV+KgPhcmIJTpq4lAdC4Q==", - "license": "MIT", - "dependencies": { - "@loaders.gl/schema": "4.3.4", - "@loaders.gl/worker-utils": "4.3.4", - "@probe.gl/log": "^4.0.2", - "@probe.gl/stats": "^4.0.2" - }, - "peerDependencies": { - "@loaders.gl/core": "^4.3.0" - } - }, - "node_modules/@loaders.gl/zip/node_modules/@loaders.gl/schema": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@loaders.gl/schema/-/schema-4.3.4.tgz", - "integrity": "sha512-1YTYoatgzr/6JTxqBLwDiD3AVGwQZheYiQwAimWdRBVB0JAzych7s1yBuE0CVEzj4JDPKOzVAz8KnU1TiBvJGw==", - "license": "MIT", - "dependencies": { - "@types/geojson": "^7946.0.7" - }, - "peerDependencies": { - "@loaders.gl/core": "^4.3.0" - } - }, - "node_modules/@loaders.gl/zip/node_modules/@loaders.gl/worker-utils": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@loaders.gl/worker-utils/-/worker-utils-4.3.4.tgz", - "integrity": "sha512-EbsszrASgT85GH3B7jkx7YXfQyIYo/rlobwMx6V3ewETapPUwdSAInv+89flnk5n2eu2Lpdeh+2zS6PvqbL2RA==", - "license": "MIT", - "peerDependencies": { - "@loaders.gl/core": "^4.3.0" - } - }, "node_modules/@luma.gl/constants": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/@luma.gl/constants/-/constants-9.1.0.tgz", "integrity": "sha512-BIkRHF36eE1FoghbEKzBjbs7+tX6RUH7gI7ZFKzVJEgXvT6xg12HM7uk+6L54fR/rUxEMjgL+uRzIxprCOGjOg==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@luma.gl/core": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/@luma.gl/core/-/core-9.1.0.tgz", "integrity": "sha512-HkcqDlxal6gOP7Y6KTRcEjnPuxSFMy+oJYfk623TGIxrEbN3x5uLqvbNgqLMXhV60WWq5Fj0LG1gHs1NyJHrLg==", "license": "MIT", - "peer": true, "dependencies": { "@math.gl/types": "^4.1.0", "@probe.gl/env": "^4.0.8", @@ -8531,7 +7787,6 @@ "resolved": "https://registry.npmjs.org/@luma.gl/engine/-/engine-9.1.0.tgz", "integrity": "sha512-fKa4XqUqS/wmhAPlmkemjJ6YZM3QEzRWX1bZXtVCsydZOun8KCVZsSMpCj1W1+cpoAOBVIqvBqZFF8fZClj5XQ==", "license": "MIT", - "peer": true, "dependencies": { "@math.gl/core": "^4.1.0", "@math.gl/types": "^4.1.0", @@ -8543,28 +7798,11 @@ "@luma.gl/shadertools": "^9.1.0-beta.1" } }, - "node_modules/@luma.gl/gltf": { - "version": "9.1.9", - "resolved": "https://registry.npmjs.org/@luma.gl/gltf/-/gltf-9.1.9.tgz", - "integrity": "sha512-KgVBIFCtRO1oadgMDycMJA5s+q519l/fQBGAZpUcLfWsaEDQfdHW2NLdrK/00VDv46Ng8tN/O6uyH6E40uLcLw==", - "license": "MIT", - "dependencies": { - "@loaders.gl/core": "^4.2.0", - "@loaders.gl/textures": "^4.2.0", - "@math.gl/core": "^4.1.0" - }, - "peerDependencies": { - "@luma.gl/core": "^9.1.0", - "@luma.gl/engine": "^9.1.0", - "@luma.gl/shadertools": "^9.1.0" - } - }, "node_modules/@luma.gl/shadertools": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/@luma.gl/shadertools/-/shadertools-9.1.0.tgz", "integrity": "sha512-BRDKnf2g+Xq86f1OK00F2PA2QbmkcKiM8HJ/Iw8wZB3DvPu2jBKBaboHmEoo6gxq46P32vFGyvxso8umai5eJw==", "license": "MIT", - "peer": true, "dependencies": { "@math.gl/core": "^4.1.0", "@math.gl/types": "^4.1.0", @@ -8579,7 +7817,6 @@ "resolved": "https://registry.npmjs.org/@luma.gl/webgl/-/webgl-9.1.0.tgz", "integrity": "sha512-dTftLUfOnW6F9vYOl1ZvO2I28OYFdiqHkN7BpPd+8GPzepFT8OtEZwbcb/JjF9TsVhaeLyl1oDckQg2ckre3sw==", "license": "MIT", - "peer": true, "dependencies": { "@luma.gl/constants": "9.1.0", "@math.gl/types": "^4.1.0", @@ -8690,12 +7927,6 @@ "integrity": "sha512-2XghOwu16ZwPJLOFVuIOaLbN0iKMn867evzXFyf0P22dqugezfJwLmdanAgU25ITvz1TvOfVP4jsDImlDJzcWg==", "license": "BSD-3-Clause" }, - "node_modules/@mapbox/martini": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@mapbox/martini/-/martini-0.2.0.tgz", - "integrity": "sha512-7hFhtkb0KTLEls+TRw/rWayq5EeHtTaErgm/NskVoXmtgAQu/9D299aeyj6mzAR/6XUnYRp2lU+4IcrYRFjVsQ==", - "license": "ISC" - }, "node_modules/@mapbox/point-geometry": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/@mapbox/point-geometry/-/point-geometry-0.1.0.tgz", @@ -8741,26 +7972,6 @@ "@math.gl/types": "4.1.0" } }, - "node_modules/@math.gl/culling": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@math.gl/culling/-/culling-4.1.0.tgz", - "integrity": "sha512-jFmjFEACnP9kVl8qhZxFNhCyd47qPfSVmSvvjR0/dIL6R9oD5zhR1ub2gN16eKDO/UM7JF9OHKU3EBIfeR7gtg==", - "license": "MIT", - "dependencies": { - "@math.gl/core": "4.1.0", - "@math.gl/types": "4.1.0" - } - }, - "node_modules/@math.gl/geospatial": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@math.gl/geospatial/-/geospatial-4.1.0.tgz", - "integrity": "sha512-BzsUhpVvnmleyYF6qdqJIip6FtIzJmnWuPTGhlBuPzh7VBHLonCFSPtQpbkRuoyAlbSyaGXcVt6p6lm9eK2vtg==", - "license": "MIT", - "dependencies": { - "@math.gl/core": "4.1.0", - "@math.gl/types": "4.1.0" - } - }, "node_modules/@math.gl/polygon": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/@math.gl/polygon/-/polygon-4.1.0.tgz", @@ -15011,62 +14222,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@turf/boolean-clockwise": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/@turf/boolean-clockwise/-/boolean-clockwise-5.1.5.tgz", - "integrity": "sha512-FqbmEEOJ4rU4/2t7FKx0HUWmjFEVqR+NJrFP7ymGSjja2SQ7Q91nnBihGuT+yuHHl6ElMjQ3ttsB/eTmyCycxA==", - "license": "MIT", - "dependencies": { - "@turf/helpers": "^5.1.5", - "@turf/invariant": "^5.1.5" - } - }, - "node_modules/@turf/clone": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/@turf/clone/-/clone-5.1.5.tgz", - "integrity": "sha512-//pITsQ8xUdcQ9pVb4JqXiSqG4dos5Q9N4sYFoWghX21tfOV2dhc5TGqYOhnHrQS7RiKQL1vQ48kIK34gQ5oRg==", - "license": "MIT", - "dependencies": { - "@turf/helpers": "^5.1.5" - } - }, - "node_modules/@turf/helpers": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/@turf/helpers/-/helpers-5.1.5.tgz", - "integrity": "sha512-/lF+JR+qNDHZ8bF9d+Cp58nxtZWJ3sqFe6n3u3Vpj+/0cqkjk4nXKYBSY0azm+GIYB5mWKxUXvuP/m0ZnKj1bw==", - "license": "MIT" - }, - "node_modules/@turf/invariant": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@turf/invariant/-/invariant-5.2.0.tgz", - "integrity": "sha512-28RCBGvCYsajVkw2EydpzLdcYyhSA77LovuOvgCJplJWaNVyJYH6BOR3HR9w50MEkPqb/Vc/jdo6I6ermlRtQA==", - "license": "MIT", - "dependencies": { - "@turf/helpers": "^5.1.5" - } - }, - "node_modules/@turf/meta": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@turf/meta/-/meta-5.2.0.tgz", - "integrity": "sha512-ZjQ3Ii62X9FjnK4hhdsbT+64AYRpaI8XMBMcyftEOGSmPMUVnkbvuv3C9geuElAXfQU7Zk1oWGOcrGOD9zr78Q==", - "license": "MIT", - "dependencies": { - "@turf/helpers": "^5.1.5" - } - }, - "node_modules/@turf/rewind": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/@turf/rewind/-/rewind-5.1.5.tgz", - "integrity": "sha512-Gdem7JXNu+G4hMllQHXRFRihJl3+pNl7qY+l4qhQFxq+hiU1cQoVFnyoleIqWKIrdK/i2YubaSwc3SCM7N5mMw==", - "license": "MIT", - "dependencies": { - "@turf/boolean-clockwise": "^5.1.5", - "@turf/clone": "^5.1.5", - "@turf/helpers": "^5.1.5", - "@turf/invariant": "^5.1.5", - "@turf/meta": "^5.1.5" - } - }, "node_modules/@tybys/wasm-util": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.9.0.tgz", @@ -15148,15 +14303,6 @@ "@types/node": "*" } }, - "node_modules/@types/brotli": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/@types/brotli/-/brotli-1.3.4.tgz", - "integrity": "sha512-cKYjgaS2DMdCKF7R0F5cgx1nfBYObN2ihIuPGQ4/dlIY6RpV7OWNwe9L8V4tTVKL2eZqOkNM9FM/rgTvLf4oXw==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/cacheable-request": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", @@ -15260,12 +14406,6 @@ "@types/node": "*" } }, - "node_modules/@types/crypto-js": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-4.2.2.tgz", - "integrity": "sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ==", - "license": "MIT" - }, "node_modules/@types/d3": { "version": "3.5.53", "resolved": "https://registry.npmjs.org/@types/d3/-/d3-3.5.53.tgz", @@ -15889,12 +15029,6 @@ "integrity": "sha512-ieXiYmgSRXUDeOntE1InxjWyvEelZGP63M+cGuquuRLuIKKT1osnkXjxev9B7d1nXSug5vpunx+gNlbVxMlC9A==", "license": "MIT" }, - "node_modules/@types/pako": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@types/pako/-/pako-1.0.7.tgz", - "integrity": "sha512-YBtzT2ztNF6R/9+UXj2wTGFnC9NklAnASt3sC0h2m1bbH7G6FyBIkt4AN8ThZpNfxUo1b2iMVO0UawiJymEt8A==", - "license": "MIT" - }, "node_modules/@types/parse-json": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", @@ -20319,16 +19453,6 @@ "brfs": "bin/cmd.js" } }, - "node_modules/brotli": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/brotli/-/brotli-1.3.3.tgz", - "integrity": "sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==", - "license": "MIT", - "optional": true, - "dependencies": { - "base64-js": "^1.1.2" - } - }, "node_modules/browser-assert": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/browser-assert/-/browser-assert-1.2.1.tgz", @@ -20419,15 +19543,6 @@ "node": ">= 0.4.0" } }, - "node_modules/buf-compare": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buf-compare/-/buf-compare-1.0.1.tgz", - "integrity": "sha512-Bvx4xH00qweepGc43xFvMs5BKASXTbHaHm6+kDYIK9p/4iFwjATQkmPKHQSgJZzKbAymhztRbXUf1Nqhzl73/Q==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/buffer": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", @@ -21005,15 +20120,6 @@ "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", "license": "MIT" }, - "node_modules/charenc": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", - "integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==", - "license": "BSD-3-Clause", - "engines": { - "node": "*" - } - }, "node_modules/cheap-ruler": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/cheap-ruler/-/cheap-ruler-4.0.0.tgz", @@ -22218,19 +21324,6 @@ "node": ">=10.13.0" } }, - "node_modules/core-assert": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/core-assert/-/core-assert-0.2.1.tgz", - "integrity": "sha512-IG97qShIP+nrJCXMCgkNZgH7jZQ4n8RpPyPeXX++T6avR/KhLhgLiHKoEn5Rc1KjfycSfA9DMa6m+4C4eguHhw==", - "license": "MIT", - "dependencies": { - "buf-compare": "^1.0.0", - "is-error": "^2.2.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/core-js": { "version": "3.40.0", "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.40.0.tgz", @@ -22360,15 +21453,6 @@ "node": ">= 8" } }, - "node_modules/crypt": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", - "integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==", - "license": "BSD-3-Clause", - "engines": { - "node": "*" - } - }, "node_modules/crypto-random-string": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-4.0.0.tgz", @@ -23588,18 +22672,6 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "license": "MIT" }, - "node_modules/deep-strict-equal": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/deep-strict-equal/-/deep-strict-equal-0.2.0.tgz", - "integrity": "sha512-3daSWyvZ/zwJvuMGlzG1O+Ow0YSadGfb3jsh9xoCutv2tWyB9dA4YvR9L9/fSdDZa2dByYQe+TqapSGUrjnkoA==", - "license": "MIT", - "dependencies": { - "core-assert": "^0.2.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/deepmerge": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", @@ -24181,12 +23253,6 @@ "node": ">=12" } }, - "node_modules/draco3d": { - "version": "1.5.7", - "resolved": "https://registry.npmjs.org/draco3d/-/draco3d-1.5.7.tgz", - "integrity": "sha512-m6WCKt/erDXcw+70IJXnG7M3awwQPAsZvJGX5zY7beBqpELw6RDGkYVU0W43AFxye4pDZ5i2Lbyc/NNGqwjUVQ==", - "license": "Apache-2.0" - }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -29412,17 +28478,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/h3-js": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/h3-js/-/h3-js-4.2.1.tgz", - "integrity": "sha512-HYiUrq5qTRFqMuQu3jEHqxXLk1zsSJiby9Lja/k42wHjabZG7tN9rOuzT/PEFf+Wa7rsnHLMHRWIu0mgcJ0ewQ==", - "license": "Apache-2.0", - "engines": { - "node": ">=4", - "npm": ">=3", - "yarn": ">=1.3.0" - } - }, "node_modules/hammerjs": { "version": "2.0.8", "resolved": "https://registry.npmjs.org/hammerjs/-/hammerjs-2.0.8.tgz", @@ -30682,24 +29737,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/image-size": { - "version": "0.7.5", - "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.7.5.tgz", - "integrity": "sha512-Hiyv+mXHfFEP7LzUL/llg9RwFxxY+o9N3JVLIeG5E7iFIFAalxvRU9UZthBdYDEVnzHMgjnKJPPpay5BWf1g9g==", - "license": "MIT", - "bin": { - "image-size": "bin/image-size.js" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/immediate": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", - "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", - "license": "MIT" - }, "node_modules/immer": { "version": "10.1.1", "resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz", @@ -31479,12 +30516,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-error": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/is-error/-/is-error-2.2.2.tgz", - "integrity": "sha512-IOQqts/aHWbiisY5DuPJQ0gcbvaLFCa7fBa9xoLfxBZvQ+ZI/Zh9xoI7Gk+G64N0FdK4AbibytHht2tWgpJWLg==", - "license": "MIT" - }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -36223,54 +35254,6 @@ "node": ">=4.0" } }, - "node_modules/jszip": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", - "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", - "license": "(MIT OR GPL-3.0-or-later)", - "dependencies": { - "lie": "~3.3.0", - "pako": "~1.0.2", - "readable-stream": "~2.3.6", - "setimmediate": "^1.0.5" - } - }, - "node_modules/jszip/node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "license": "MIT" - }, - "node_modules/jszip/node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/jszip/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "license": "MIT" - }, - "node_modules/jszip/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, "node_modules/just-diff": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/just-diff/-/just-diff-6.0.2.tgz", @@ -36348,12 +35331,6 @@ "node": ">=6" } }, - "node_modules/ktx-parse": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/ktx-parse/-/ktx-parse-0.7.1.tgz", - "integrity": "sha512-FeA3g56ksdFNwjXJJsc1CCc7co+AJYDp6ipIp878zZ2bU8kWROatLYf39TQEd4/XRSUvBXovQ8gaVKWPXsCLEQ==", - "license": "MIT" - }, "node_modules/ky": { "version": "0.30.0", "resolved": "https://registry.npmjs.org/ky/-/ky-0.30.0.tgz", @@ -37146,15 +36123,6 @@ "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/lie": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", - "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", - "license": "MIT", - "dependencies": { - "immediate": "~3.0.5" - } - }, "node_modules/light-my-request": { "version": "5.14.0", "resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-5.14.0.tgz", @@ -37528,15 +36496,6 @@ "dev": true, "license": "MIT" }, - "node_modules/long": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/long/-/long-3.2.0.tgz", - "integrity": "sha512-ZYvPPOMqUwPoDsbJaR10iQJYnMuZhRTvHYl62ErLIEX7RgFlziSBUUvrt3OVfc47QlHHpzPZYP17g3Fv7oeJkg==", - "license": "Apache-2.0", - "engines": { - "node": ">=0.6" - } - }, "node_modules/longest-streak": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", @@ -37621,19 +36580,6 @@ "lz-string": "bin/bin.js" } }, - "node_modules/lz4js": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/lz4js/-/lz4js-0.2.0.tgz", - "integrity": "sha512-gY2Ia9Lm7Ep8qMiuGRhvUq0Q7qUereeldZPP1PMEJxPtEWHJLqw9pgX68oHajBH0nzJK4MaZEA/YNV3jT8u8Bg==", - "license": "ISC", - "optional": true - }, - "node_modules/lzo-wasm": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/lzo-wasm/-/lzo-wasm-0.0.4.tgz", - "integrity": "sha512-VKlnoJRFrB8SdJhlVKvW5vI1gGwcZ+mvChEXcSX6r2xDNc/Q2FD9esfBmGCuPZdrJ1feO+YcVFd2PTk0c137Gw==", - "license": "BSD-2-Clause" - }, "node_modules/magic-string": { "version": "0.30.17", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", @@ -37944,23 +36890,6 @@ "node": ">= 0.4" } }, - "node_modules/md5": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz", - "integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==", - "license": "BSD-3-Clause", - "dependencies": { - "charenc": "0.0.2", - "crypt": "0.0.2", - "is-buffer": "~1.1.6" - } - }, - "node_modules/md5/node_modules/is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "license": "MIT" - }, "node_modules/mdast-util-definitions": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-5.1.2.tgz", @@ -44851,6 +43780,7 @@ "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "dev": true, "license": "(MIT AND Zlib)" }, "node_modules/param-case": { @@ -51677,12 +50607,6 @@ "node": ">= 0.4" } }, - "node_modules/setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", - "license": "MIT" - }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -52158,12 +51082,6 @@ "tslib": "^2.0.3" } }, - "node_modules/snappyjs": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/snappyjs/-/snappyjs-0.6.1.tgz", - "integrity": "sha512-YIK6I2lsH072UE0aOFxxY1dPDCS43I5ktqHpeAsuLNYWkE5pGxRGWfDM4/vSUfNzXjC1Ivzt3qx31PCLmc9yqg==", - "license": "MIT" - }, "node_modules/sockjs": { "version": "0.3.24", "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", @@ -54080,19 +52998,6 @@ "url": "https://bevry.me/fund" } }, - "node_modules/texture-compressor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/texture-compressor/-/texture-compressor-1.0.2.tgz", - "integrity": "sha512-dStVgoaQ11mA5htJ+RzZ51ZxIZqNOgWKAIvtjLrW1AliQQLCmrDqNzQZ8Jh91YealQ95DXt4MEduLzJmbs6lig==", - "license": "MIT", - "dependencies": { - "argparse": "^1.0.10", - "image-size": "^0.7.4" - }, - "bin": { - "texture-compressor": "bin/texture-compressor.js" - } - }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", @@ -57454,8 +56359,7 @@ "version": "1.0.16", "resolved": "https://registry.npmjs.org/wgsl_reflect/-/wgsl_reflect-1.0.16.tgz", "integrity": "sha512-OE3urfXXbHMD5lhKZwxOxC9SFYynEGEkWXQmvi7B1gzzr5jb9+drh9A8MeBvVqKqznCoBuh8WOzVuSGSZs4CkQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/whatwg-encoding": { "version": "3.1.1", @@ -58488,13 +57392,6 @@ "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==", "license": "0BSD" }, - "node_modules/zstd-codec": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/zstd-codec/-/zstd-codec-0.1.5.tgz", - "integrity": "sha512-v3fyjpK8S/dpY/X5WxqTK3IoCnp/ZOLxn144GZVlNUjtwAchzrVo03h+oMATFhCIiJ5KTr4V3vDQQYz4RU684g==", - "license": "MIT", - "optional": true - }, "node_modules/zstddec": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/zstddec/-/zstddec-0.1.0.tgz", @@ -60941,11 +59838,10 @@ "version": "0.20.4", "license": "Apache-2.0", "dependencies": { - "@deck.gl/aggregation-layers": "^9.1.12", - "@deck.gl/core": "^9.1.12", - "@deck.gl/geo-layers": "^9.1.12", - "@deck.gl/layers": "^9.1.12", - "@deck.gl/react": "^9.1.12", + "@deck.gl/aggregation-layers": "^9.0.38", + "@deck.gl/core": "^9.0.37", + "@deck.gl/layers": "^9.0.38", + "@deck.gl/react": "^9.1.4", "@mapbox/geojson-extent": "^1.0.1", "@math.gl/web-mercator": "^4.1.0", "@types/d3-array": "^2.0.0", @@ -60977,214 +59873,6 @@ "react-map-gl": "^6.1.19" } }, - "plugins/legacy-preset-chart-deckgl/node_modules/@deck.gl/aggregation-layers": { - "version": "9.1.13", - "resolved": "https://registry.npmjs.org/@deck.gl/aggregation-layers/-/aggregation-layers-9.1.13.tgz", - "integrity": "sha512-eDuT4S7GRx8LWdPuxGIiK8MfBynfvj3PgNB5mB1uiXcp1OR2eZ17wr3QBp1Rdk4LUsx1P1CkDyyIvi5mn4+aQA==", - "license": "MIT", - "dependencies": { - "@luma.gl/constants": "^9.1.5", - "@luma.gl/shadertools": "^9.1.5", - "@math.gl/core": "^4.1.0", - "@math.gl/web-mercator": "^4.1.0", - "d3-hexbin": "^0.2.1" - }, - "peerDependencies": { - "@deck.gl/core": "^9.1.0", - "@deck.gl/layers": "^9.1.0", - "@luma.gl/core": "^9.1.5", - "@luma.gl/engine": "^9.1.5" - } - }, - "plugins/legacy-preset-chart-deckgl/node_modules/@deck.gl/core": { - "version": "9.1.13", - "resolved": "https://registry.npmjs.org/@deck.gl/core/-/core-9.1.13.tgz", - "integrity": "sha512-c15DpwUEvDjmt3+/azSjcfhVQ5L5HiIj6LJob1KAwQOnB5zgVdKWukN/21ELQ7ekppEkfT0x4byRv5k4QVocqQ==", - "license": "MIT", - "dependencies": { - "@loaders.gl/core": "^4.2.0", - "@loaders.gl/images": "^4.2.0", - "@luma.gl/constants": "^9.1.5", - "@luma.gl/core": "^9.1.5", - "@luma.gl/engine": "^9.1.5", - "@luma.gl/shadertools": "^9.1.5", - "@luma.gl/webgl": "^9.1.5", - "@math.gl/core": "^4.1.0", - "@math.gl/sun": "^4.1.0", - "@math.gl/types": "^4.1.0", - "@math.gl/web-mercator": "^4.1.0", - "@probe.gl/env": "^4.1.0", - "@probe.gl/log": "^4.1.0", - "@probe.gl/stats": "^4.1.0", - "@types/offscreencanvas": "^2019.6.4", - "gl-matrix": "^3.0.0", - "mjolnir.js": "^3.0.0" - } - }, - "plugins/legacy-preset-chart-deckgl/node_modules/@deck.gl/extensions": { - "version": "9.1.13", - "resolved": "https://registry.npmjs.org/@deck.gl/extensions/-/extensions-9.1.13.tgz", - "integrity": "sha512-Y6XCjXckcXyU+NhaDW4GA6nw9BAanFKNtltHcR+GUivGiK+QuBXlIggl+QLkWXD1EKp3os/DOM8kO0FmtAaC9A==", - "license": "MIT", - "peer": true, - "dependencies": { - "@luma.gl/constants": "^9.1.5", - "@luma.gl/shadertools": "^9.1.5", - "@math.gl/core": "^4.1.0" - }, - "peerDependencies": { - "@deck.gl/core": "^9.1.0", - "@luma.gl/core": "^9.1.5", - "@luma.gl/engine": "^9.1.5" - } - }, - "plugins/legacy-preset-chart-deckgl/node_modules/@deck.gl/geo-layers": { - "version": "9.1.13", - "resolved": "https://registry.npmjs.org/@deck.gl/geo-layers/-/geo-layers-9.1.13.tgz", - "integrity": "sha512-+6GLQacUzQHcGraKCuDV6z1U44mJ08eg2/gaQGDJYUwh+YUCiyW1uWey5GvV9nRcaS47UApYxEDQZGpSamZT+A==", - "license": "MIT", - "dependencies": { - "@loaders.gl/3d-tiles": "^4.2.0", - "@loaders.gl/gis": "^4.2.0", - "@loaders.gl/loader-utils": "^4.2.0", - "@loaders.gl/mvt": "^4.2.0", - "@loaders.gl/schema": "^4.2.0", - "@loaders.gl/terrain": "^4.2.0", - "@loaders.gl/tiles": "^4.2.0", - "@loaders.gl/wms": "^4.2.0", - "@luma.gl/gltf": "^9.1.5", - "@luma.gl/shadertools": "^9.1.5", - "@math.gl/core": "^4.1.0", - "@math.gl/culling": "^4.1.0", - "@math.gl/web-mercator": "^4.1.0", - "@types/geojson": "^7946.0.8", - "h3-js": "^4.1.0", - "long": "^3.2.0" - }, - "peerDependencies": { - "@deck.gl/core": "^9.1.0", - "@deck.gl/extensions": "^9.1.0", - "@deck.gl/layers": "^9.1.0", - "@deck.gl/mesh-layers": "^9.1.0", - "@loaders.gl/core": "^4.2.0", - "@luma.gl/core": "^9.1.5", - "@luma.gl/engine": "^9.1.5" - } - }, - "plugins/legacy-preset-chart-deckgl/node_modules/@deck.gl/layers": { - "version": "9.1.13", - "resolved": "https://registry.npmjs.org/@deck.gl/layers/-/layers-9.1.13.tgz", - "integrity": "sha512-2eD2uARmObtCXrc1Q051fqy+LS2w6a700qPerqtqz+J/bOWTHSEZxAdIoHawDU7g+fi4/1lti0m8bdp2X/kZLA==", - "license": "MIT", - "dependencies": { - "@loaders.gl/images": "^4.2.0", - "@loaders.gl/schema": "^4.2.0", - "@luma.gl/shadertools": "^9.1.5", - "@mapbox/tiny-sdf": "^2.0.5", - "@math.gl/core": "^4.1.0", - "@math.gl/polygon": "^4.1.0", - "@math.gl/web-mercator": "^4.1.0", - "earcut": "^2.2.4" - }, - "peerDependencies": { - "@deck.gl/core": "^9.1.0", - "@loaders.gl/core": "^4.2.0", - "@luma.gl/core": "^9.1.5", - "@luma.gl/engine": "^9.1.5" - } - }, - "plugins/legacy-preset-chart-deckgl/node_modules/@deck.gl/mesh-layers": { - "version": "9.1.13", - "resolved": "https://registry.npmjs.org/@deck.gl/mesh-layers/-/mesh-layers-9.1.13.tgz", - "integrity": "sha512-ujhe9FtB4qRRCXH/hY5p+IQ5VO/AC+/dtern6CTzYzjGnUnAvsbIgBZ3jxSlb1B/D3wlVE778W2cmv7MIToJJg==", - "license": "MIT", - "peer": true, - "dependencies": { - "@loaders.gl/gltf": "^4.2.0", - "@luma.gl/gltf": "^9.1.5", - "@luma.gl/shadertools": "^9.1.5" - }, - "peerDependencies": { - "@deck.gl/core": "^9.1.0", - "@luma.gl/core": "^9.1.5", - "@luma.gl/engine": "^9.1.5" - } - }, - "plugins/legacy-preset-chart-deckgl/node_modules/@deck.gl/react": { - "version": "9.1.13", - "resolved": "https://registry.npmjs.org/@deck.gl/react/-/react-9.1.13.tgz", - "integrity": "sha512-9tu5roGzvy4plLvUVUxDpEy3KHDxpAEBRdM/qC06Ijognfl/A9bECdQt4cqqGflkvg6VzQaw2Cy7eIRvzhXJbA==", - "license": "MIT", - "peerDependencies": { - "@deck.gl/core": "^9.1.0", - "@deck.gl/widgets": "^9.1.0", - "react": ">=16.3.0", - "react-dom": ">=16.3.0" - } - }, - "plugins/legacy-preset-chart-deckgl/node_modules/@luma.gl/constants": { - "version": "9.1.9", - "resolved": "https://registry.npmjs.org/@luma.gl/constants/-/constants-9.1.9.tgz", - "integrity": "sha512-yc9fml04OeTTcwK+7gmDMxoLQ67j4ZiAFXjmYvPomYyBVzS0NZxTDuwcCBmnxjLOiroOZW8FRRrVc/yOiFug2w==", - "license": "MIT" - }, - "plugins/legacy-preset-chart-deckgl/node_modules/@luma.gl/core": { - "version": "9.1.9", - "resolved": "https://registry.npmjs.org/@luma.gl/core/-/core-9.1.9.tgz", - "integrity": "sha512-1i9N7+I/UbFjx3axSMlc3/NufA+C2iBv/7mw51gRE/ypQPgvFmY/QqXBVZRe+nthF+OhlUMhO19TBndzYFTWhA==", - "license": "MIT", - "dependencies": { - "@math.gl/types": "^4.1.0", - "@probe.gl/env": "^4.0.8", - "@probe.gl/log": "^4.0.8", - "@probe.gl/stats": "^4.0.8", - "@types/offscreencanvas": "^2019.6.4" - } - }, - "plugins/legacy-preset-chart-deckgl/node_modules/@luma.gl/engine": { - "version": "9.1.9", - "resolved": "https://registry.npmjs.org/@luma.gl/engine/-/engine-9.1.9.tgz", - "integrity": "sha512-n1GLK1sUMFkWxdb+aZYn6ZBFltFEMi7X+6ZPxn2pBsNT6oeF4AyvH5AyqhOpvHvUnCLDt3Zsf1UIfx3MI//YSw==", - "license": "MIT", - "dependencies": { - "@math.gl/core": "^4.1.0", - "@math.gl/types": "^4.1.0", - "@probe.gl/log": "^4.0.8", - "@probe.gl/stats": "^4.0.8" - }, - "peerDependencies": { - "@luma.gl/core": "^9.1.0", - "@luma.gl/shadertools": "^9.1.0" - } - }, - "plugins/legacy-preset-chart-deckgl/node_modules/@luma.gl/shadertools": { - "version": "9.1.9", - "resolved": "https://registry.npmjs.org/@luma.gl/shadertools/-/shadertools-9.1.9.tgz", - "integrity": "sha512-Uqp2xfgIEunRMLXTeCJ4uEMlWcUGcYMZGJ8GAOrAeDzn4bMKVRKmZDC71vkuTctnaodM3UdrI9W6s1sJlrXsxw==", - "license": "MIT", - "dependencies": { - "@math.gl/core": "^4.1.0", - "@math.gl/types": "^4.1.0", - "wgsl_reflect": "^1.2.0" - }, - "peerDependencies": { - "@luma.gl/core": "^9.1.0" - } - }, - "plugins/legacy-preset-chart-deckgl/node_modules/@luma.gl/webgl": { - "version": "9.1.9", - "resolved": "https://registry.npmjs.org/@luma.gl/webgl/-/webgl-9.1.9.tgz", - "integrity": "sha512-jecHjhNSWkXH0v62rM6G5fIIkOmsrND27099iKgdutFvHIvd4QS4UzGWEEa9AEPlP0rTLqXkA6y6YL7f42ZkVg==", - "license": "MIT", - "dependencies": { - "@luma.gl/constants": "9.1.9", - "@math.gl/types": "^4.1.0", - "@probe.gl/env": "^4.0.8" - }, - "peerDependencies": { - "@luma.gl/core": "^9.1.0" - } - }, "plugins/legacy-preset-chart-deckgl/node_modules/d3-array": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.4.tgz", @@ -61261,12 +59949,6 @@ "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==", "license": "ISC" }, - "plugins/legacy-preset-chart-deckgl/node_modules/wgsl_reflect": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/wgsl_reflect/-/wgsl_reflect-1.2.1.tgz", - "integrity": "sha512-PY6MdLqLW1NFj/V6f5/9/Nb4ultwlAF7lCLyjKOAkdnlf7LlrGXNFXzHHEV7Okg1zy4C4TpBIcc/G3PXW4py8g==", - "license": "MIT" - }, "plugins/legacy-preset-chart-nvd3": { "name": "@superset-ui/legacy-preset-chart-nvd3", "version": "0.20.3", diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/types.ts b/superset-frontend/packages/superset-ui-chart-controls/src/types.ts index 8f6ed74d216..453e58d9437 100644 --- a/superset-frontend/packages/superset-ui-chart-controls/src/types.ts +++ b/superset-frontend/packages/superset-ui-chart-controls/src/types.ts @@ -161,6 +161,7 @@ export type InternalControlType = | 'DatasourceControl' | 'DateFilterControl' | 'FixedOrMetricControl' + | 'ColorBreakpointsControl' | 'HiddenControl' | 'SelectAsyncControl' | 'SelectControl' diff --git a/superset-frontend/packages/superset-ui-core/src/validator/validateMapboxStylesUrl.ts b/superset-frontend/packages/superset-ui-core/src/validator/validateMapboxStylesUrl.ts index 34cef4f98cc..bfbbaa7168d 100644 --- a/superset-frontend/packages/superset-ui-core/src/validator/validateMapboxStylesUrl.ts +++ b/superset-frontend/packages/superset-ui-core/src/validator/validateMapboxStylesUrl.ts @@ -19,28 +19,18 @@ import { t } from '../translation'; -const VALIDE_OSM_URLS = ['https://tile.osm', 'https://tile.openstreetmap']; - /** * Validate a [Mapbox styles URL](https://docs.mapbox.com/help/glossary/style-url/) - * or a title server URL. * @param v */ export default function validateMapboxStylesUrl(v: unknown) { - if (typeof v === 'string') { - const trimmed_v = v.trim(); - if ( - typeof v === 'string' && - trimmed_v.length > 0 && - (trimmed_v.startsWith('mapbox://styles/') || - trimmed_v.startsWith('tile://http') || - VALIDE_OSM_URLS.some(s => trimmed_v.startsWith(s))) - ) { - return false; - } + if ( + typeof v === 'string' && + v.trim().length > 0 && + v.trim().startsWith('mapbox://styles/') + ) { + return false; } - return t( - 'is expected to be a Mapbox/OSM URL (eg. mapbox://styles/...) or a tile server URL (eg. tile://http...)', - ); + return t('is expected to be a Mapbox URL'); } diff --git a/superset-frontend/packages/superset-ui-core/test/validator/validateMapboxStylesUrl.test.ts b/superset-frontend/packages/superset-ui-core/test/validator/validateMapboxStylesUrl.test.ts index 8572a9a0912..dbd5822666e 100644 --- a/superset-frontend/packages/superset-ui-core/test/validator/validateMapboxStylesUrl.test.ts +++ b/superset-frontend/packages/superset-ui-core/test/validator/validateMapboxStylesUrl.test.ts @@ -29,11 +29,6 @@ describe('validateMapboxStylesUrl', () => { 'mapbox://styles/foobar/clp2dr5r4008a01pcg4ad45m8', ), ).toEqual(false); - expect( - validateMapboxStylesUrl( - 'tile://https://c.tile.openstreetmap.org/{z}/{x}/{y}.png', - ), - ).toEqual(false); }); [ @@ -45,7 +40,7 @@ describe('validateMapboxStylesUrl', () => { ].forEach(value => { it(`should not validate ${value}`, () => { expect(validateMapboxStylesUrl(value)).toEqual( - 'is expected to be a Mapbox/OSM URL (eg. mapbox://styles/...) or a tile server URL (eg. tile://http...)', + 'is expected to be a Mapbox URL', ); }); }); diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/package.json b/superset-frontend/plugins/legacy-preset-chart-deckgl/package.json index 6ae7121c330..6aedec80a83 100644 --- a/superset-frontend/plugins/legacy-preset-chart-deckgl/package.json +++ b/superset-frontend/plugins/legacy-preset-chart-deckgl/package.json @@ -24,11 +24,10 @@ "lib" ], "dependencies": { - "@deck.gl/aggregation-layers": "^9.1.12", - "@deck.gl/core": "^9.1.12", - "@deck.gl/geo-layers": "^9.1.12", - "@deck.gl/layers": "^9.1.12", - "@deck.gl/react": "^9.1.12", + "@deck.gl/aggregation-layers": "^9.0.38", + "@deck.gl/core": "^9.0.37", + "@deck.gl/layers": "^9.0.38", + "@deck.gl/react": "^9.1.4", "@mapbox/geojson-extent": "^1.0.1", "@math.gl/web-mercator": "^4.1.0", "@types/d3-array": "^2.0.0", diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/CategoricalDeckGLContainer.tsx b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/CategoricalDeckGLContainer.tsx index 9522715a90c..0d17fb4de94 100644 --- a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/CategoricalDeckGLContainer.tsx +++ b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/CategoricalDeckGLContainer.tsx @@ -44,9 +44,12 @@ import { DeckGLContainerHandle, DeckGLContainerStyledWrapper, } from './DeckGLContainer'; -import { Point } from './types'; import { GetLayerType } from './factory'; +import { ColorBreakpointType, ColorType, Point } from './types'; import { TooltipProps } from './components/Tooltip'; +import { COLOR_SCHEME_TYPES, ColorSchemeType } from './utilities/utils'; +import { getColorBreakpointsBuckets } from './utils'; +import { DEFAULT_DECKGL_COLOR } from './utilities/Shared_DeckGL'; const { getScale } = CategoricalColorNamespace; @@ -55,18 +58,24 @@ function getCategories(fd: QueryFormData, data: JsonObject[]) { const fixedColor = [c.r, c.g, c.b, 255 * c.a]; const appliedScheme = fd.color_scheme; const colorFn = getScale(appliedScheme); - const categories: Record = {}; - data.forEach(d => { - if (d.cat_color != null && !categories.hasOwnProperty(d.cat_color)) { - let color; - if (fd.dimension) { - color = hexToRGB(colorFn(d.cat_color, fd.sliceId), c.a * 255); - } else { - color = fixedColor; + let categories: Record = {}; + + const colorSchemeType = fd.color_scheme_type; + if (colorSchemeType === COLOR_SCHEME_TYPES.color_breakpoints) { + categories = getColorBreakpointsBuckets(fd.color_breakpoints); + } else { + data.forEach(d => { + if (d.cat_color != null && !categories.hasOwnProperty(d.cat_color)) { + let color; + if (fd.dimension) { + color = hexToRGB(colorFn(d.cat_color, fd.sliceId), c.a * 255); + } else { + color = fixedColor; + } + categories[d.cat_color] = { color, enabled: true }; } - categories[d.cat_color] = { color, enabled: true }; - } - }); + }); + } return categories; } @@ -133,22 +142,73 @@ const CategoricalDeckGLContainer = (props: CategoricalDeckGLContainerProps) => { } }, []); - const addColor = useCallback((data: JsonObject[], fd: QueryFormData) => { - const c = fd.color_picker || { r: 0, g: 0, b: 0, a: 1 }; - const appliedScheme = fd.color_scheme; - const colorFn = getScale(appliedScheme); + const addColor = useCallback( + ( + data: JsonObject[], + fd: QueryFormData, + selectedColorScheme: ColorSchemeType, + ) => { + const appliedScheme = fd.color_scheme; + const colorFn = getScale(appliedScheme); + let color: ColorType; - return data.map(d => { - let color; - if (fd.dimension) { - color = hexToRGB(colorFn(d.cat_color, fd.slice_id), c.a * 255); + switch (selectedColorScheme) { + case COLOR_SCHEME_TYPES.fixed_color: { + color = fd.color_picker || { r: 0, g: 0, b: 0, a: 100 }; - return { ...d, color }; + return data.map(d => ({ + ...d, + color: [color.r, color.g, color.b, color.a * 255], + })); + } + case COLOR_SCHEME_TYPES.categorical_palette: { + return data.map(d => ({ + ...d, + color: hexToRGB(colorFn(d.cat_color, fd.slice_id)), + })); + } + case COLOR_SCHEME_TYPES.color_breakpoints: { + const defaultBreakpointColor = fd.deafult_breakpoint_color + ? [ + fd.deafult_breakpoint_color.r, + fd.deafult_breakpoint_color.g, + fd.deafult_breakpoint_color.b, + fd.deafult_breakpoint_color.a * 255, + ] + : [ + DEFAULT_DECKGL_COLOR.r, + DEFAULT_DECKGL_COLOR.g, + DEFAULT_DECKGL_COLOR.b, + DEFAULT_DECKGL_COLOR.a * 255, + ]; + return data.map(d => { + const breakpointForPoint: ColorBreakpointType = + fd.color_breakpoints?.find( + (breakpoint: ColorBreakpointType) => + d.metric >= breakpoint.minValue && + d.metric <= breakpoint.maxValue, + ); + + return { + ...d, + color: breakpointForPoint + ? [ + breakpointForPoint?.color.r, + breakpointForPoint?.color.g, + breakpointForPoint?.color.b, + breakpointForPoint?.color.a * 255, + ] + : defaultBreakpointColor, + }; + }); + } + default: { + return []; + } } - - return d; - }); - }, []); + }, + [], + ); const getLayers = useCallback(() => { const { @@ -163,8 +223,10 @@ const CategoricalDeckGLContainer = (props: CategoricalDeckGLContainerProps) => { } = props; let features = payload.data.features ? [...payload.data.features] : []; + const selectedColorScheme = fd.color_scheme_type; + // Add colors from categories or fixed color - features = addColor(features, fd); + features = addColor(features, fd, selectedColorScheme); // Apply user defined data mutator if defined if (fd.js_data_mutator) { diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/DeckGLContainer.tsx b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/DeckGLContainer.tsx index 98376660274..8e73cf3fb83 100644 --- a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/DeckGLContainer.tsx +++ b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/DeckGLContainer.tsx @@ -39,12 +39,6 @@ import { JsonObject, JsonValue, styled, usePrevious } from '@superset-ui/core'; import Tooltip, { TooltipProps } from './components/Tooltip'; import 'mapbox-gl/dist/mapbox-gl.css'; import { Viewport } from './utils/fitViewport'; -import { - MAPBOX_LAYER_PREFIX, - OSM_LAYER_KEYWORDS, - TILE_LAYER_PREFIX, - buildTileLayer, -} from './utils'; const TICK = 250; // milliseconds @@ -108,20 +102,6 @@ export const DeckGLContainer = memo( ); const layers = useCallback(() => { - if ( - (props.mapStyle?.startsWith(TILE_LAYER_PREFIX) || - OSM_LAYER_KEYWORDS.some(tilek => props.mapStyle?.includes(tilek))) && - props.layers.some( - l => typeof l !== 'function' && l?.id === 'tile-layer', - ) === false - ) { - props.layers.unshift( - buildTileLayer( - (props.mapStyle ?? '').replace(TILE_LAYER_PREFIX, ''), - 'tile-layer', - ), - ); - } // Support for layer factory if (props.layers.some(l => typeof l === 'function')) { return props.layers.map(l => @@ -130,7 +110,7 @@ export const DeckGLContainer = memo( } return props.layers as Layer[]; - }, [props.layers, props.mapStyle]); + }, [props.layers]); const { children = null, height, width } = props; @@ -154,13 +134,11 @@ export const DeckGLContainer = memo( glContextRef.current = context.gl; }} > - {props.mapStyle?.startsWith(MAPBOX_LAYER_PREFIX) && ( - - )} + {children} diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/components/Legend.tsx b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/components/Legend.tsx index 9e3eaaec4d4..42492895418 100644 --- a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/components/Legend.tsx +++ b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/components/Legend.tsx @@ -21,6 +21,7 @@ */ import { memo } from 'react'; import { formatNumber, styled } from '@superset-ui/core'; +import { Color } from '@deck.gl/core'; const StyledLegend = styled.div` ${({ theme }) => ` @@ -59,7 +60,7 @@ export type LegendProps = { format: string | null; forceCategorical?: boolean; position?: null | 'tl' | 'tr' | 'bl' | 'br'; - categories: Record; + categories: Record; toggleCategory?: (key: string) => void; showSingleCategory?: (key: string) => void; }; diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/factory.tsx b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/factory.tsx index 3f17222dad3..b617088c6e9 100644 --- a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/factory.tsx +++ b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/factory.tsx @@ -40,6 +40,8 @@ import CategoricalDeckGLContainer from './CategoricalDeckGLContainer'; import fitViewport, { Viewport } from './utils/fitViewport'; import { Point } from './types'; import { TooltipProps } from './components/Tooltip'; +import { getColorBreakpointsBuckets } from './utils'; +import Legend from './components/Legend'; type DeckGLComponentProps = { datasource: Datasource; @@ -103,6 +105,9 @@ export function createDeckGLComponent( } return props.viewport; }; + const [categories, setCategories] = useState( + getColorBreakpointsBuckets(props.formData.color_breakpoints) || [], + ); const [viewport, setViewport] = useState(getAdjustedViewport()); @@ -139,6 +144,14 @@ export function createDeckGLComponent( [setTooltip], ); + useEffect(() => { + const categories = getColorBreakpointsBuckets( + props.formData.color_breakpoints, + ); + + setCategories(categories); + }, [props]); + const [layer, setLayer] = useState(computeLayer(props)); useEffect(() => { @@ -161,17 +174,25 @@ export function createDeckGLComponent( const { formData, payload, setControlValue, height, width } = props; return ( - +
+ + +
); }); } diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Arc/Arc.tsx b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Arc/Arc.tsx index 8b3ae291d13..91cd6f4fdcd 100644 --- a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Arc/Arc.tsx +++ b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Arc/Arc.tsx @@ -18,6 +18,7 @@ */ import { ArcLayer } from '@deck.gl/layers'; import { JsonObject, QueryFormData, t } from '@superset-ui/core'; +import { COLOR_SCHEME_TYPES } from '../../utilities/utils'; import { commonLayerProps } from '../common'; import { GetLayerType, createCategoricalDeckGLComponent } from '../../factory'; import TooltipRow from '../../TooltipRow'; @@ -68,12 +69,24 @@ export const getLayer: GetLayerType = function ({ const sc = fd.color_picker; const tc = fd.target_color_picker; + const colorSchemeType = fd.color_scheme_type; + return new ArcLayer({ data, - getSourceColor: (d: any) => - d.sourceColor || d.color || [sc.r, sc.g, sc.b, 255 * sc.a], - getTargetColor: (d: any) => - d.targetColor || d.color || [tc.r, tc.g, tc.b, 255 * tc.a], + getSourceColor: (d: any) => { + if (colorSchemeType === COLOR_SCHEME_TYPES.fixed_color) { + return [sc.r, sc.g, sc.b, 255 * sc.a]; + } + + return d.targetColor || d.color; + }, + getTargetColor: (d: any) => { + if (colorSchemeType === COLOR_SCHEME_TYPES.fixed_color) { + return [tc.r, tc.g, tc.b, 255 * tc.a]; + } + + return d.targetColor || d.color; + }, id: `path-layer-${fd.slice_id}` as const, getWidth: fd.stroke_width ? fd.stroke_width : 3, ...commonLayerProps({ diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Arc/controlPanel.ts b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Arc/controlPanel.ts index 21ea9cc8cf3..dc6d726148c 100644 --- a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Arc/controlPanel.ts +++ b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Arc/controlPanel.ts @@ -22,11 +22,14 @@ import timeGrainSqlaAnimationOverrides, { columnChoices, PRIMARY_COLOR, } from '../../utilities/controls'; -import { formatSelectOptions } from '../../utilities/utils'; +import { + COLOR_SCHEME_TYPES, + formatSelectOptions, + isColorSchemeTypeVisible, +} from '../../utilities/utils'; import { filterNulls, autozoom, - dimension, jsColumns, jsDataMutator, jsTooltip, @@ -35,6 +38,9 @@ import { legendPosition, viewport, mapboxStyle, + deckGLCategoricalColor, + deckGLCategoricalColorSchemeSelect, + deckGLCategoricalColorSchemeTypeSelect, } from '../../utilities/Shared_DeckGL'; const config: ControlPanelConfig = { @@ -81,7 +87,37 @@ const config: ControlPanelConfig = { label: t('Arc'), controlSetRows: [ [ - 'color_picker', + { + name: 'color_scheme_type', + config: { + ...deckGLCategoricalColorSchemeTypeSelect.config, + choices: [ + [COLOR_SCHEME_TYPES.fixed_color, t('Fixed color')], + [ + COLOR_SCHEME_TYPES.categorical_palette, + t('Categorical palette'), + ], + ], + default: COLOR_SCHEME_TYPES.fixed_color, + }, + }, + ], + [ + { + name: 'color_picker', + config: { + label: t('Source Color'), + description: t('Color of the source location'), + type: 'ColorPickerControl', + default: PRIMARY_COLOR, + renderTrigger: true, + visibility: ({ controls }) => + isColorSchemeTypeVisible( + controls, + COLOR_SCHEME_TYPES.fixed_color, + ), + }, + }, { name: 'target_color_picker', config: { @@ -90,22 +126,16 @@ const config: ControlPanelConfig = { type: 'ColorPickerControl', default: PRIMARY_COLOR, renderTrigger: true, + visibility: ({ controls }) => + isColorSchemeTypeVisible( + controls, + COLOR_SCHEME_TYPES.fixed_color, + ), }, }, ], - [ - { - name: dimension.name, - config: { - ...dimension.config, - label: t('Categorical Color'), - description: t( - 'Pick a dimension from which categorical colors are defined', - ), - }, - }, - 'color_scheme', - ], + [deckGLCategoricalColor], + [deckGLCategoricalColorSchemeSelect], [ { name: 'stroke_width', @@ -119,9 +149,9 @@ const config: ControlPanelConfig = { choices: formatSelectOptions([1, 2, 3, 4, 5]), }, }, - legendPosition, ], - [legendFormat, null], + [legendPosition], + [legendFormat], ], }, { diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Grid/Grid.tsx b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Grid/Grid.tsx index a1522f8fb58..6c5aa9bea61 100644 --- a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Grid/Grid.tsx +++ b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Grid/Grid.tsx @@ -16,15 +16,19 @@ * specific language governing permissions and limitations * under the License. */ -import { Color } from '@deck.gl/core'; import { GridLayer } from '@deck.gl/aggregation-layers'; import { t, CategoricalColorNamespace, JsonObject } from '@superset-ui/core'; -import { commonLayerProps, getAggFunc } from '../common'; +import { + commonLayerProps, + getAggFunc, + getColorForBreakpoints, + getColorRange, +} from '../common'; import sandboxedEval from '../../utils/sandbox'; -import { hexToRGB } from '../../utils/colors'; import { createDeckGLComponent, GetLayerType } from '../../factory'; import TooltipRow from '../../TooltipRow'; +import { COLOR_SCHEME_TYPES } from '../../utilities/utils'; function setTooltipContent(o: JsonObject) { return ( @@ -55,9 +59,6 @@ export const getLayer: GetLayerType = function ({ const fd = formData; const appliedScheme = fd.color_scheme; const colorScale = CategoricalColorNamespace.getScale(appliedScheme); - const colorRange = colorScale - .range() - .map(color => hexToRGB(color)) as Color[]; let data = payload.data.features; if (fd.js_data_mutator) { @@ -66,19 +67,39 @@ export const getLayer: GetLayerType = function ({ data = jsFnMutator(data); } + const colorBreakpoints = fd.color_breakpoints; + + const colorSchemeType = fd.color_scheme_type; + const colorRange = getColorRange({ + defaultBreakpointsColor: fd.deafult_breakpoint_color, + colorSchemeType, + colorScale, + colorBreakpoints, + fixedColor: fd.color_picker, + }); + const aggFunc = getAggFunc(fd.js_agg_function, p => p.weight); + const colorAggFunc = + colorSchemeType === COLOR_SCHEME_TYPES.color_breakpoints + ? (p: number[]) => getColorForBreakpoints(aggFunc, p, colorBreakpoints) + : aggFunc; + return new GridLayer({ - id: `grid-layer-${fd.slice_id}` as const, + id: `grid-layer-${fd.slice_id}-${JSON.stringify(colorBreakpoints)}` as const, data, cellSize: fd.grid_size, extruded: fd.extruded, + colorDomain: + colorSchemeType === COLOR_SCHEME_TYPES.color_breakpoints && colorRange + ? [0, colorRange.length] + : undefined, colorRange, outline: false, // @ts-ignore getElevationValue: aggFunc, // @ts-ignore - getColorValue: aggFunc, + getColorValue: colorAggFunc, ...commonLayerProps({ formData: fd, setDataMask, diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Grid/controlPanel.ts b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Grid/controlPanel.ts index a86118e5706..02e0bc43412 100644 --- a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Grid/controlPanel.ts +++ b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Grid/controlPanel.ts @@ -33,7 +33,10 @@ import { viewport, spatial, mapboxStyle, + legendPosition, + generateDeckGLColorSchemeControls, } from '../../utilities/Shared_DeckGL'; +import { COLOR_SCHEME_TYPES } from '../../utilities/utils'; const config: ControlPanelConfig = { controlPanelSections: [ @@ -53,7 +56,11 @@ const config: ControlPanelConfig = { controlSetRows: [ [mapboxStyle], [viewport], - ['color_scheme'], + ...generateDeckGLColorSchemeControls({ + defaultSchemeType: COLOR_SCHEME_TYPES.categorical_palette, + disableCategoricalColumn: true, + }), + [legendPosition], [autozoom], [gridSize], [extruded], diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Heatmap/Heatmap.tsx b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Heatmap/Heatmap.tsx index 0b472100e8f..bcf21c39585 100644 --- a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Heatmap/Heatmap.tsx +++ b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Heatmap/Heatmap.tsx @@ -17,11 +17,10 @@ * under the License. */ import { HeatmapLayer } from '@deck.gl/aggregation-layers'; -import { Position, Color } from '@deck.gl/core'; +import { Position } from '@deck.gl/core'; import { t, getSequentialSchemeRegistry, JsonObject } from '@superset-ui/core'; -import { commonLayerProps } from '../common'; +import { commonLayerProps, getColorRange } from '../common'; import sandboxedEval from '../../utils/sandbox'; -import { hexToRGB } from '../../utils/colors'; import { GetLayerType, createDeckGLComponent } from '../../factory'; import TooltipRow from '../../TooltipRow'; @@ -63,10 +62,15 @@ export const getLayer: GetLayerType = ({ const colorScale = getSequentialSchemeRegistry() ?.get(colorScheme) ?.createLinearScale([0, 6]); - const colorRange = colorScale - ?.range() - ?.map(color => hexToRGB(color)) - ?.reverse() as Color[]; + + const colorSchemeType = fd.color_scheme_type; + const colorRange = getColorRange({ + defaultBreakpointsColor: fd.deafult_breakpoint_color, + colorBreakpoints: fd.color_breakpoints, + fixedColor: fd.color_picker, + colorSchemeType, + colorScale, + })?.reverse(); return new HeatmapLayer({ id: `heatmap-layer-${fd.slice_id}` as const, diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Heatmap/controlPanel.ts b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Heatmap/controlPanel.ts index c00c87fbbb8..05a337d3a8b 100644 --- a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Heatmap/controlPanel.ts +++ b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Heatmap/controlPanel.ts @@ -28,6 +28,9 @@ import { } from '@superset-ui/core'; import { autozoom, + deckGLCategoricalColorSchemeTypeSelect, + deckGLFixedColor, + deckGLLinearColorSchemeSelect, filterNulls, jsColumns, jsDataMutator, @@ -37,6 +40,7 @@ import { spatial, viewport, } from '../../utilities/Shared_DeckGL'; +import { COLOR_SCHEME_TYPES } from '../../utilities/utils'; const INTENSITY_OPTIONS = Array.from( { length: 10 }, @@ -99,7 +103,21 @@ const config: ControlPanelConfig = { controlSetRows: [ [mapboxStyle], [viewport], - ['linear_color_scheme'], + [ + { + name: 'color_scheme_type', + config: { + ...deckGLCategoricalColorSchemeTypeSelect.config, + choices: [ + [COLOR_SCHEME_TYPES.fixed_color, t('Fixed color')], + [COLOR_SCHEME_TYPES.linear_palette, t('Linear palette')], + ], + default: COLOR_SCHEME_TYPES.linear_palette, + }, + }, + ], + [deckGLFixedColor], + [deckGLLinearColorSchemeSelect], [autozoom], [ { diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Hex/Hex.tsx b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Hex/Hex.tsx index 15e72617b2b..8e6f6ad5ee3 100644 --- a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Hex/Hex.tsx +++ b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Hex/Hex.tsx @@ -16,13 +16,17 @@ * specific language governing permissions and limitations * under the License. */ -import { Color } from '@deck.gl/core'; import { HexagonLayer } from '@deck.gl/aggregation-layers'; import { t, CategoricalColorNamespace, JsonObject } from '@superset-ui/core'; -import { commonLayerProps, getAggFunc } from '../common'; +import { COLOR_SCHEME_TYPES } from '../../utilities/utils'; +import { + commonLayerProps, + getAggFunc, + getColorForBreakpoints, + getColorRange, +} from '../common'; import sandboxedEval from '../../utils/sandbox'; -import { hexToRGB } from '../../utils/colors'; import { GetLayerType, createDeckGLComponent } from '../../factory'; import TooltipRow from '../../TooltipRow'; @@ -54,9 +58,6 @@ export const getLayer: GetLayerType = function ({ const fd = formData; const appliedScheme = fd.color_scheme; const colorScale = CategoricalColorNamespace.getScale(appliedScheme); - const colorRange = colorScale - .range() - .map(color => hexToRGB(color)) as Color[]; let data = payload.data.features; if (fd.js_data_mutator) { @@ -64,19 +65,40 @@ export const getLayer: GetLayerType = function ({ const jsFnMutator = sandboxedEval(fd.js_data_mutator); data = jsFnMutator(data); } - const aggFunc = getAggFunc(fd.js_agg_function, p => p?.weight); + + const colorSchemeType = fd.color_scheme_type; + const colorRange = getColorRange({ + defaultBreakpointsColor: fd.deafult_breakpoint_color, + colorBreakpoints: fd.color_breakpoints, + fixedColor: fd.color_picker, + colorSchemeType, + colorScale, + }); + + const colorBreakpoints = fd.color_breakpoints; + + const aggFunc = getAggFunc(fd.js_agg_function, p => p.weight); + + const colorAggFunc = + colorSchemeType === COLOR_SCHEME_TYPES.color_breakpoints + ? (p: number[]) => getColorForBreakpoints(aggFunc, p, colorBreakpoints) + : aggFunc; return new HexagonLayer({ - id: `hex-layer-${fd.slice_id}` as const, + id: `hex-layer-${fd.slice_id}-${JSON.stringify(colorBreakpoints)}` as const, data, radius: fd.grid_size, extruded: fd.extruded, + colorDomain: + colorSchemeType === COLOR_SCHEME_TYPES.color_breakpoints && colorRange + ? [0, colorRange.length] + : undefined, colorRange, outline: false, // @ts-ignore getElevationValue: aggFunc, // @ts-ignore - getColorValue: aggFunc, + getColorValue: colorAggFunc, ...commonLayerProps({ formData: fd, setTooltip, diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Hex/controlPanel.ts b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Hex/controlPanel.ts index 109cef77daf..211e1c1df87 100644 --- a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Hex/controlPanel.ts +++ b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Hex/controlPanel.ts @@ -25,6 +25,7 @@ import { autozoom, extruded, filterNulls, + generateDeckGLColorSchemeControls, gridSize, jsColumns, jsDataMutator, @@ -52,7 +53,8 @@ const config: ControlPanelConfig = { label: t('Map'), controlSetRows: [ [mapboxStyle], - ['color_scheme', viewport], + ...generateDeckGLColorSchemeControls({}), + [viewport], [autozoom], [gridSize], [extruded], diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Polygon/Polygon.tsx b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Polygon/Polygon.tsx index c3270e19133..7d22811f25a 100644 --- a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Polygon/Polygon.tsx +++ b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Polygon/Polygon.tsx @@ -36,11 +36,16 @@ import { import { PolygonLayer } from '@deck.gl/layers'; +import { Color } from '@deck.gl/core'; import Legend from '../../components/Legend'; import TooltipRow from '../../TooltipRow'; -import { getBuckets, getBreakPointColorScaler } from '../../utils'; +import { + getBuckets, + getBreakPointColorScaler, + getColorBreakpointsBuckets, +} from '../../utils'; -import { commonLayerProps } from '../common'; +import { commonLayerProps, getColorForBreakpoints } from '../common'; import sandboxedEval from '../../utils/sandbox'; import getPointsFromPolygon from '../../utils/getPointsFromPolygon'; import fitViewport, { Viewport } from '../../utils/fitViewport'; @@ -50,6 +55,8 @@ import { } from '../../DeckGLContainer'; import { TooltipProps } from '../../components/Tooltip'; import { GetLayerType } from '../../factory'; +import { COLOR_SCHEME_TYPES } from '../../utilities/utils'; +import { DEFAULT_DECKGL_COLOR } from '../../utilities/Shared_DeckGL'; const DOUBLE_CLICK_THRESHOLD = 250; // milliseconds @@ -107,8 +114,11 @@ export const getLayer: GetLayerType = function ({ emitCrossFilters, }) { const fd = formData as PolygonFormData; - const fc = fd.fill_color_picker; - const sc = fd.stroke_color_picker; + const fc: { r: number; g: number; b: number; a: number } = + fd.fill_color_picker; + const sc: { r: number; g: number; b: number; a: number } = + fd.stroke_color_picker; + const defaultBreakpointColor = fd.deafult_breakpoint_color; let data = [...payload.data.features]; if (fd.js_data_mutator) { @@ -117,17 +127,62 @@ export const getLayer: GetLayerType = function ({ data = jsFnMutator(data); } + const colorSchemeType = fd.color_scheme_type; + const metricLabel = fd.metric ? fd.metric.label || fd.metric : null; const accessor = (d: JsonObject) => d[metricLabel]; - // base color for the polygons - const baseColorScaler = - fd.metric === null - ? () => [fc.r, fc.g, fc.b, 255 * fc.a] - : getBreakPointColorScaler(fd, data, accessor); + let baseColorScaler: (d: JsonObject) => Color; + + switch (colorSchemeType) { + case COLOR_SCHEME_TYPES.fixed_color: { + baseColorScaler = () => [fc.r, fc.g, fc.b, 255 * fc.a]; + break; + } + case COLOR_SCHEME_TYPES.linear_palette: { + baseColorScaler = + fd.metric === null + ? () => [fc.r, fc.g, fc.b, 255 * fc.a] + : getBreakPointColorScaler(fd, data, accessor); + break; + } + case COLOR_SCHEME_TYPES.color_breakpoints: { + const colorBreakpoints = fd.color_breakpoints; + baseColorScaler = data => { + const breakpointIndex = getColorForBreakpoints( + accessor, + data as number[], + colorBreakpoints, + ); + const breakpointColor = + breakpointIndex !== undefined && + colorBreakpoints[breakpointIndex - 1]?.color; + return breakpointColor + ? [breakpointColor.r, breakpointColor.g, breakpointColor.b, 255] + : defaultBreakpointColor + ? [ + defaultBreakpointColor.r, + defaultBreakpointColor.g, + defaultBreakpointColor.b, + defaultBreakpointColor.a * 255, + ] + : [ + DEFAULT_DECKGL_COLOR.r, + DEFAULT_DECKGL_COLOR.g, + DEFAULT_DECKGL_COLOR.b, + DEFAULT_DECKGL_COLOR.a * 255, + ]; + }; + break; + } + + default: + baseColorScaler = () => [fc.r, fc.g, fc.b, 255 * fc.a]; + break; + } // when polygons are selected, reduce the opacity of non-selected polygons const colorScaler = (d: JsonObject): [number, number, number, number] => { - const baseColor = (baseColorScaler?.(d) as [ + const baseColor = (baseColorScaler(d) as [ number, number, number, @@ -154,11 +209,11 @@ export const getLayer: GetLayerType = function ({ stroked: fd.stroked, getPolygon: getPointsFromPolygon, getFillColor: colorScaler, - getLineColor: [sc.r, sc.g, sc.b, 255 * sc.a], + getLineColor: sc ? [sc.r, sc.g, sc.b, 255 * sc.a] : undefined, getLineWidth: fd.line_width, extruded: fd.extruded, lineWidthUnits: fd.line_width_unit, - getElevation: (d: any) => getElevation(d, colorScaler), + getElevation: (d: JsonObject) => getElevation(d, colorScaler), elevationScale: fd.multiplier, fp64: true, ...commonLayerProps({ @@ -313,7 +368,10 @@ const DeckGLPolygon = (props: DeckGLPolygonProps) => { : null; const accessor = (d: JsonObject) => d[metricLabel]; - const buckets = getBuckets(formData, payload.data.features, accessor); + const colorSchemeType = formData.color_scheme_type; + const buckets = colorSchemeType + ? getColorBreakpointsBuckets(formData.color_breakpoints) + : getBuckets(formData, payload.data.features, accessor); return (
diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Polygon/controlPanel.ts b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Polygon/controlPanel.ts index 2e7ff080723..5d614e63413 100644 --- a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Polygon/controlPanel.ts +++ b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Polygon/controlPanel.ts @@ -22,7 +22,7 @@ import { } from '@superset-ui/chart-controls'; import { t } from '@superset-ui/core'; import timeGrainSqlaAnimationOverrides from '../../utilities/controls'; -import { formatSelectOptions } from '../../utilities/utils'; +import { COLOR_SCHEME_TYPES, formatSelectOptions } from '../../utilities/utils'; import { filterNulls, autozoom, @@ -44,6 +44,10 @@ import { lineType, reverseLongLat, mapboxStyle, + deckGLCategoricalColorSchemeTypeSelect, + deckGLLinearColorSchemeSelect, + deckGLColorBreakpointsSelect, + breakpointsDefaultColor, } from '../../utilities/Shared_DeckGL'; import { dndLineColumn } from '../../utilities/sharedDndControls'; @@ -96,7 +100,25 @@ const config: ControlPanelConfig = { label: t('Polygon Settings'), expanded: true, controlSetRows: [ - [fillColorPicker, strokeColorPicker], + [ + { + ...deckGLCategoricalColorSchemeTypeSelect, + config: { + ...deckGLCategoricalColorSchemeTypeSelect.config, + choices: [ + [COLOR_SCHEME_TYPES.fixed_color, t('Fixed color')], + [COLOR_SCHEME_TYPES.linear_palette, t('Linear palette')], + [COLOR_SCHEME_TYPES.color_breakpoints, t('Color breakpoints')], + ], + default: COLOR_SCHEME_TYPES.linear_palette, + }, + }, + fillColorPicker, + strokeColorPicker, + deckGLLinearColorSchemeSelect, + breakpointsDefaultColor, + deckGLColorBreakpointsSelect, + ], [filled, stroked], [extruded], [multiplier], @@ -116,7 +138,6 @@ const config: ControlPanelConfig = { }, }, ], - ['linear_color_scheme'], [ { name: 'opacity', diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Scatter/controlPanel.ts b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Scatter/controlPanel.ts index 3943249c4c0..997b65b3ea3 100644 --- a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Scatter/controlPanel.ts +++ b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Scatter/controlPanel.ts @@ -22,7 +22,6 @@ import timeGrainSqlaAnimationOverrides from '../../utilities/controls'; import { filterNulls, autozoom, - dimension, jsColumns, jsDataMutator, jsTooltip, @@ -34,7 +33,9 @@ import { pointRadiusFixed, multiplier, mapboxStyle, + generateDeckGLColorSchemeControls, } from '../../utilities/Shared_DeckGL'; +import { COLOR_SCHEME_TYPES } from '../../utilities/utils'; const config: ControlPanelConfig = { onInit: controlState => ({ @@ -127,22 +128,11 @@ const config: ControlPanelConfig = { { label: t('Point Color'), controlSetRows: [ - ['color_picker'], [legendPosition], [legendFormat], - [ - { - name: dimension.name, - config: { - ...dimension.config, - label: t('Categorical Color'), - description: t( - 'Pick a dimension from which categorical colors are defined', - ), - }, - }, - ], - ['color_scheme'], + ...generateDeckGLColorSchemeControls({ + defaultSchemeType: COLOR_SCHEME_TYPES.fixed_color, + }), ], }, { diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Screengrid/Screengrid.tsx b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Screengrid/Screengrid.tsx index 418fb423457..13ee94a8cca 100644 --- a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Screengrid/Screengrid.tsx +++ b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Screengrid/Screengrid.tsx @@ -20,9 +20,11 @@ */ import { ScreenGridLayer } from '@deck.gl/aggregation-layers'; -import { JsonObject, t } from '@superset-ui/core'; +import { CategoricalColorNamespace, JsonObject, t } from '@superset-ui/core'; +import { Color } from '@deck.gl/core'; +import { COLOR_SCHEME_TYPES, ColorSchemeType } from '../../utilities/utils'; import sandboxedEval from '../../utils/sandbox'; -import { commonLayerProps } from '../common'; +import { commonLayerProps, getColorRange } from '../common'; import TooltipRow from '../../TooltipRow'; import { GetLayerType, createDeckGLComponent } from '../../factory'; @@ -41,7 +43,7 @@ function setTooltipContent(o: JsonObject) {
); @@ -57,11 +59,9 @@ export const getLayer: GetLayerType = function ({ emitCrossFilters, }) { const fd = formData; - const c = fd.color_picker; - let data = payload.data.features.map((d: JsonObject) => ({ - ...d, - color: [c.r, c.g, c.b, 255 * c.a], - })); + const appliedScheme = fd.color_scheme; + const colorScale = CategoricalColorNamespace.getScale(appliedScheme); + let data = payload.data.features; if (fd.js_data_mutator) { // Applying user defined data mutator if defined @@ -69,16 +69,39 @@ export const getLayer: GetLayerType = function ({ data = jsFnMutator(data); } + const colorSchemeType = fd.color_scheme_type as ColorSchemeType & 'default'; + const colorRange = getColorRange({ + defaultBreakpointsColor: fd.deafult_breakpoint_color, + colorBreakpoints: fd.color_breakpoints, + fixedColor: fd.color_picker, + colorSchemeType, + colorScale, + }); + + const aggFunc = (d: JsonObject) => d.weight || 0; + + const defaultScreenGridColorRange = [ + [255, 255, 178], + [254, 217, 118], + [254, 178, 76], + [253, 141, 60], + [240, 59, 32], + [189, 0, 38], + ] as Color[]; + // Passing a layer creator function instead of a layer since the // layer needs to be regenerated at each render return new ScreenGridLayer({ id: `screengrid-layer-${fd.slice_id}` as const, data, cellSizePixels: fd.grid_size, - minColor: [c.r, c.g, c.b, 0], - maxColor: [c.r, c.g, c.b, 255 * c.a], + colorDomain: + colorSchemeType === COLOR_SCHEME_TYPES.color_breakpoints && colorRange + ? [0, colorRange.length] + : undefined, + colorRange: + colorSchemeType === 'default' ? defaultScreenGridColorRange : colorRange, outline: false, - getWeight: (d: any) => d.weight || 0, ...commonLayerProps({ formData: fd, setDataMask, @@ -88,6 +111,8 @@ export const getLayer: GetLayerType = function ({ onContextMenu, emitCrossFilters, }), + getWeight: aggFunc, + colorScaleType: colorSchemeType === 'default' ? 'linear' : 'quantize', }); }; diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Screengrid/controlPanel.ts b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Screengrid/controlPanel.ts index a1b49a23215..c7a637f55a4 100644 --- a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Screengrid/controlPanel.ts +++ b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Screengrid/controlPanel.ts @@ -33,7 +33,11 @@ import { viewport, spatial, mapboxStyle, + deckGLFixedColor, + deckGLCategoricalColorSchemeSelect, + deckGLCategoricalColorSchemeTypeSelect, } from '../../utilities/Shared_DeckGL'; +import { COLOR_SCHEME_TYPES } from '../../utilities/utils'; const config: ControlPanelConfig = { controlPanelSections: [ @@ -55,7 +59,28 @@ const config: ControlPanelConfig = { { label: t('Grid'), expanded: true, - controlSetRows: [[gridSize, 'color_picker']], + controlSetRows: [ + [gridSize], + [ + { + name: 'color_scheme_type', + config: { + ...deckGLCategoricalColorSchemeTypeSelect.config, + choices: [ + ['default', 'Default'], + [COLOR_SCHEME_TYPES.fixed_color, t('Fixed color')], + [ + COLOR_SCHEME_TYPES.categorical_palette, + t('Categorical palette'), + ], + ], + default: 'default', + }, + }, + ], + [deckGLFixedColor], + [deckGLCategoricalColorSchemeSelect], + ], }, { label: t('Advanced'), diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/common.test.ts b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/common.test.ts index d60c4da9389..d598f2f48d2 100644 --- a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/common.test.ts +++ b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/common.test.ts @@ -19,7 +19,15 @@ import { PickingInfo } from '@deck.gl/core'; import { JsonObject, QueryFormData } from '@superset-ui/core'; -import { getAggFunc, commonLayerProps } from './common'; +import { + getAggFunc, + commonLayerProps, + getColorForBreakpoints, + getColorRange, +} from './common'; +import { ColorBreakpointType } from '../types'; +import { COLOR_SCHEME_TYPES, ColorSchemeType } from '../utilities/utils'; +import { DEFAULT_DECKGL_COLOR } from '../utilities/Shared_DeckGL'; const partialformData: Partial = { viz_type: 'table', @@ -159,3 +167,106 @@ describe('commonLayerProps', () => { expect(mockOnSelect).toHaveBeenCalledWith('John Doe'); }); }); + +describe('getColorForBreakpoints', () => { + const colorBreakpoints: ColorBreakpointType[] = [ + { minValue: 0, maxValue: 10, color: { r: 255, g: 0, b: 0, a: 100 } }, + { minValue: 11, maxValue: 20, color: { r: 0, g: 255, b: 0, a: 100 } }, + { minValue: 21, maxValue: 30, color: { r: 0, g: 0, b: 255, a: 100 } }, + ]; + + it('returns correct breakpoint index for value in range', () => { + const aggFunc = (arr: number[]) => arr[0]; + expect(getColorForBreakpoints(aggFunc, [5], colorBreakpoints)).toBe(1); + expect(getColorForBreakpoints(aggFunc, [15], colorBreakpoints)).toBe(2); + expect(getColorForBreakpoints(aggFunc, [25], colorBreakpoints)).toBe(3); + }); + + it('returns 0 if value is not in any breakpoint', () => { + const aggFunc = () => 100; + expect(getColorForBreakpoints(aggFunc, [100], colorBreakpoints)).toBe(0); + }); + + it('returns undefined if aggFunc returns undefined', () => { + const aggFunc = () => undefined; + expect( + getColorForBreakpoints(aggFunc, [5], colorBreakpoints), + ).toBeUndefined(); + }); + + it('returns undefined if aggFunc returns array', () => { + const aggFunc = () => [1, 2]; + expect( + getColorForBreakpoints(aggFunc, [5], colorBreakpoints), + ).toBeUndefined(); + }); +}); + +describe('getColorRange', () => { + const fdBase: any = { + color_picker: { r: 10, g: 20, b: 30, a: 0.5 }, + color_breakpoints: [ + { minValue: 0, maxValue: 10, color: { r: 255, g: 0, b: 0, a: 1 } }, + { minValue: 11, maxValue: 20, color: { r: 0, g: 255, b: 0, a: 1 } }, + ], + }; + + it('returns color range for linear_palette', () => { + const colorScale = { range: () => ['#ff0000', '#00ff00'] } as any; + const result = getColorRange({ + defaultBreakpointsColor: DEFAULT_DECKGL_COLOR, + colorSchemeType: COLOR_SCHEME_TYPES.linear_palette, + colorScale, + }); + expect(result).toEqual([ + [255, 0, 0, 255], + [0, 255, 0, 255], + ]); + }); + + it('returns color range for categorical_palette', () => { + const colorScale = { range: () => ['#0000ff', '#00ffff'] } as any; + const result = getColorRange({ + defaultBreakpointsColor: DEFAULT_DECKGL_COLOR, + colorSchemeType: COLOR_SCHEME_TYPES.categorical_palette, + colorScale, + }); + expect(result).toEqual([ + [0, 0, 255, 255], + [0, 255, 255, 255], + ]); + }); + + it('returns color range for color_breakpoints', () => { + const result = getColorRange({ + colorBreakpoints: fdBase.color_breakpoints, + defaultBreakpointsColor: DEFAULT_DECKGL_COLOR, + colorSchemeType: COLOR_SCHEME_TYPES.color_breakpoints, + }); + expect(result).toEqual([ + [ + DEFAULT_DECKGL_COLOR.r, + DEFAULT_DECKGL_COLOR.g, + DEFAULT_DECKGL_COLOR.b, + DEFAULT_DECKGL_COLOR.a * 255, + ], + [255, 0, 0, 255], + [0, 255, 0, 255], + ]); + }); + + it('returns default color if color_picker is missing', () => { + const result = getColorRange({ + defaultBreakpointsColor: DEFAULT_DECKGL_COLOR, + colorSchemeType: 'unknown_type' as ColorSchemeType, + }); + expect(result).toEqual([ + [ + DEFAULT_DECKGL_COLOR.r, + DEFAULT_DECKGL_COLOR.g, + DEFAULT_DECKGL_COLOR.b, + DEFAULT_DECKGL_COLOR.a * 255, + ], + ]); + }); +}); diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/common.tsx b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/common.tsx index d7130f9a742..f5ede4421d7 100644 --- a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/common.tsx +++ b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/common.tsx @@ -29,6 +29,7 @@ import { deviation as d3deviation, } from 'd3-array'; import { + CategoricalColorScale, FilterState, HandlerFunction, JsonObject, @@ -36,10 +37,15 @@ import { QueryFormData, SetDataMaskHook, } from '@superset-ui/core'; -import { Layer, PickingInfo } from '@deck.gl/core'; +import { Layer, PickingInfo, Color } from '@deck.gl/core'; +import { ScaleLinear } from 'd3-scale'; +import { ColorBreakpointType } from '../types'; import sandboxedEval from '../utils/sandbox'; import { TooltipProps } from '../components/Tooltip'; import { getCrossFilterDataMask } from '../utils/crossFiltersDataMask'; +import { COLOR_SCHEME_TYPES, ColorSchemeType } from '../utilities/utils'; +import { hexToRGB } from '../utils/colors'; +import { DEFAULT_DECKGL_COLOR } from '../utilities/Shared_DeckGL'; export function commonLayerProps({ formData, @@ -181,3 +187,87 @@ export function getAggFunc( return (arr: number[]) => d3func(arr.map(x => accessor(x))); } + +export const getColorForBreakpoints = ( + aggFunc: (arr: number[]) => number | number[] | undefined, + point: number[], + colorBreakpoints: ColorBreakpointType[], +) => { + const aggResult = aggFunc(point); + + if (aggResult === undefined) return undefined; + + if (Array.isArray(aggResult)) return undefined; + + const breapointForPoint = colorBreakpoints.findIndex( + breakpoint => + aggResult >= breakpoint.minValue && aggResult <= breakpoint.maxValue, + ); + + return breapointForPoint + 1; +}; + +export const getColorRange = ({ + colorSchemeType, + fixedColor, + colorBreakpoints, + colorScale, + defaultBreakpointsColor, +}: { + colorSchemeType: ColorSchemeType; + defaultBreakpointsColor: { r: number; g: number; b: number; a: number }; + fixedColor?: { r: number; g: number; b: number; a: number }; + colorBreakpoints?: ColorBreakpointType[]; + colorScale?: CategoricalColorScale | ScaleLinear; +}) => { + let colorRange: Color[] | undefined; + switch (colorSchemeType) { + case COLOR_SCHEME_TYPES.linear_palette: + case COLOR_SCHEME_TYPES.categorical_palette: { + colorRange = colorScale?.range().map(color => hexToRGB(color)) as Color[]; + break; + } + case COLOR_SCHEME_TYPES.color_breakpoints: { + const defaultColorArray: Color = defaultBreakpointsColor + ? [ + defaultBreakpointsColor.r, + defaultBreakpointsColor.g, + defaultBreakpointsColor.b, + defaultBreakpointsColor.a * 255, + ] + : [ + DEFAULT_DECKGL_COLOR.r, + DEFAULT_DECKGL_COLOR.g, + DEFAULT_DECKGL_COLOR.b, + DEFAULT_DECKGL_COLOR.a * 255, + ]; + + colorRange = colorBreakpoints?.map( + (colorBreakpoint: ColorBreakpointType) => + colorBreakpoint.color + ? [ + colorBreakpoint.color.r, + colorBreakpoint.color.g, + colorBreakpoint.color.b, + colorBreakpoint.color.a * 255, + ] + : defaultColorArray, + ); + colorRange?.unshift(defaultColorArray); + + break; + } + default: { + const color = fixedColor || { + r: DEFAULT_DECKGL_COLOR.r, + g: DEFAULT_DECKGL_COLOR.g, + b: DEFAULT_DECKGL_COLOR.b, + a: DEFAULT_DECKGL_COLOR.a, + }; + + colorRange = [[color.r, color.g, color.b, color.a * 255]]; + } + } + + return colorRange; +}; diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/types.ts b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/types.ts index aadd859d775..ab7dac7cdc6 100644 --- a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/types.ts +++ b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/types.ts @@ -27,3 +27,9 @@ export interface ColorType { b: number; a: number; } + +export interface ColorBreakpointType { + color: ColorType; + minValue: number; + maxValue: number; +} diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/utilities/Shared_DeckGL.jsx b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/utilities/Shared_DeckGL.tsx similarity index 58% rename from superset-frontend/plugins/legacy-preset-chart-deckgl/src/utilities/Shared_DeckGL.jsx rename to superset-frontend/plugins/legacy-preset-chart-deckgl/src/utilities/Shared_DeckGL.tsx index 24201677c26..2cd96ccaadd 100644 --- a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/utilities/Shared_DeckGL.jsx +++ b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/utilities/Shared_DeckGL.tsx @@ -25,33 +25,28 @@ import { t, validateNonEmpty, validateMapboxStylesUrl, + getCategoricalSchemeRegistry, + getSequentialSchemeRegistry, + SequentialScheme, } from '@superset-ui/core'; -import { D3_FORMAT_OPTIONS, sharedControls } from '@superset-ui/chart-controls'; +import { + ControlPanelState, + CustomControlItem, + D3_FORMAT_OPTIONS, + getColorControlsProps, + sharedControls, +} from '@superset-ui/chart-controls'; import { columnChoices, PRIMARY_COLOR } from './controls'; +import { + COLOR_SCHEME_TYPES, + ColorSchemeType, + isColorSchemeTypeVisible, +} from './utils'; -let deckglTiles; +const categoricalSchemeRegistry = getCategoricalSchemeRegistry(); +const sequentialSchemeRegistry = getSequentialSchemeRegistry(); -export const DEFAULT_DECKGL_TILES = [ - ['https://tile.openstreetmap.org/{z}/{x}/{y}.png', 'Streets (OSM)'], - ['https://tile.osm.ch/osm-swiss-style/{z}/{x}/{y}.png', 'Topography (OSM)'], - ['mapbox://styles/mapbox/streets-v9', 'Streets (Mapbox)'], - ['mapbox://styles/mapbox/dark-v9', 'Dark (Mapbox)'], - ['mapbox://styles/mapbox/light-v9', 'Light (Mapbox)'], - ['mapbox://styles/mapbox/satellite-streets-v9', 'Satellite Streets (Mapbox)'], - ['mapbox://styles/mapbox/satellite-v9', 'Satellite (Mapbox)'], - ['mapbox://styles/mapbox/outdoors-v9', 'Outdoors (Mapbox)'], -]; - -const getDeckGLTiles = () => { - if (!deckglTiles) { - const appContainer = document.getElementById('app'); - const { common } = JSON.parse( - appContainer?.getAttribute('data-bootstrap') || '{}', - ); - deckglTiles = common?.deckgl_tiles ?? DEFAULT_DECKGL_TILES; - } - return deckglTiles; -}; +export const DEFAULT_DECKGL_COLOR = { r: 158, g: 158, b: 158, a: 1 }; const DEFAULT_VIEWPORT = { longitude: 6.85236157047845, @@ -75,8 +70,8 @@ const jsFunctionInfo = ( ); function jsFunctionControl( - label, - description, + label: string, + description: string, extraDescr = null, height = 100, defaultText = '', @@ -127,7 +122,7 @@ export const autozoom = { }, }; -export const dimension = { +export const dimension: CustomControlItem = { name: 'dimension', config: { ...sharedControls.groupby, @@ -220,7 +215,7 @@ export const lineColumn = { label: t('Lines column'), default: null, description: t('The database columns that contains lines information'), - mapStateToProps: state => ({ + mapStateToProps: (state: ControlPanelState) => ({ choices: columnChoices(state.datasource), }), validators: [validateNonEmpty], @@ -239,7 +234,7 @@ export const lineWidth = { }, }; -export const fillColorPicker = { +export const fillColorPicker: CustomControlItem = { name: 'fill_color_picker', config: { label: t('Fill Color'), @@ -249,10 +244,12 @@ export const fillColorPicker = { type: 'ColorPickerControl', default: PRIMARY_COLOR, renderTrigger: true, + visibility: ({ controls }) => + isColorSchemeTypeVisible(controls, COLOR_SCHEME_TYPES.fixed_color), }, }; -export const strokeColorPicker = { +export const strokeColorPicker: CustomControlItem = { name: 'stroke_color_picker', config: { label: t('Stroke Color'), @@ -262,6 +259,8 @@ export const strokeColorPicker = { type: 'ColorPickerControl', default: PRIMARY_COLOR, renderTrigger: true, + visibility: ({ controls }) => + isColorSchemeTypeVisible(controls, COLOR_SCHEME_TYPES.fixed_color), }, }; @@ -331,7 +330,7 @@ export const spatial = { label: t('Longitude & Latitude'), validators: [validateNonEmpty], description: t('Point to your spatial columns'), - mapStateToProps: state => ({ + mapStateToProps: (state: ControlPanelState) => ({ choices: columnChoices(state.datasource), }), }, @@ -344,7 +343,7 @@ export const pointRadiusFixed = { label: t('Point Size'), default: { type: 'fix', value: 1000 }, description: t('Fixed point radius'), - mapStateToProps: state => ({ + mapStateToProps: (state: ControlPanelState) => ({ datasource: state.datasource, }), }, @@ -396,10 +395,17 @@ export const mapboxStyle = { renderTrigger: true, freeForm: true, validators: [validateMapboxStylesUrl], - choices: getDeckGLTiles(), - default: getDeckGLTiles()[0][0], + choices: [ + ['mapbox://styles/mapbox/streets-v9', t('Streets')], + ['mapbox://styles/mapbox/dark-v9', t('Dark')], + ['mapbox://styles/mapbox/light-v9', t('Light')], + ['mapbox://styles/mapbox/satellite-streets-v9', t('Satellite Streets')], + ['mapbox://styles/mapbox/satellite-v9', t('Satellite')], + ['mapbox://styles/mapbox/outdoors-v9', t('Outdoors')], + ], + default: 'mapbox://styles/mapbox/light-v9', description: t( - 'Mapbox base layer map style (see Mapbox documentation: %s) or tile server URL.', + 'Base layer map style. See Mapbox documentation: %s', 'https://docs.mapbox.com/help/glossary/style-url/', ), }, @@ -412,8 +418,163 @@ export const geojsonColumn = { label: t('GeoJson Column'), validators: [validateNonEmpty], description: t('Select the geojson column'), - mapStateToProps: state => ({ + mapStateToProps: (state: ControlPanelState) => ({ choices: columnChoices(state.datasource), }), }, }; + +export const deckGLCategoricalColorSchemeTypeSelect: CustomControlItem = { + name: 'color_scheme_type', + config: { + type: 'SelectControl', + label: t('Color Scheme Type'), + description: t('Select the type of color scheme to use.'), + clearable: false, + renderTrigger: true, + validators: [], + choices: [ + [COLOR_SCHEME_TYPES.fixed_color, t('Fixed color')], + [COLOR_SCHEME_TYPES.categorical_palette, t('Categorical palette')], + [COLOR_SCHEME_TYPES.color_breakpoints, t('Color breakpoints')], + ], + default: COLOR_SCHEME_TYPES.categorical_palette, + }, +}; + +export const deckGLFixedColor: CustomControlItem = { + name: 'color_picker', + config: { + type: 'ColorPickerControl', + label: t('Fixed Color'), + default: PRIMARY_COLOR, + renderTrigger: true, + description: t('Select the fixed color'), + visibility: ({ controls }) => + isColorSchemeTypeVisible(controls, COLOR_SCHEME_TYPES.fixed_color), + }, +}; + +export const deckGLCategoricalColor: CustomControlItem = { + name: dimension.name, + config: { + ...dimension.config, + label: t('Categorical Color'), + description: t( + 'Pick a dimension from which categorical colors are defined', + ), + visibility: ({ controls }) => + isColorSchemeTypeVisible( + controls, + COLOR_SCHEME_TYPES.categorical_palette, + ), + }, +}; + +export const deckGLCategoricalColorSchemeSelect: CustomControlItem = { + name: 'color_scheme', + config: { + type: 'ColorSchemeControl', + label: t('Color Scheme'), + default: categoricalSchemeRegistry.getDefaultKey(), + renderTrigger: true, + choices: () => categoricalSchemeRegistry.keys().map(s => [s, s]), + description: t('The color scheme for rendering chart'), + schemes: () => categoricalSchemeRegistry.getMap(), + visibility: ({ controls }) => + isColorSchemeTypeVisible( + controls, + COLOR_SCHEME_TYPES.categorical_palette, + ), + }, +}; + +export const deckGLLinearColorSchemeSelect: CustomControlItem = { + name: 'linear_color_scheme', + config: { + type: 'ColorSchemeControl', + label: t('Linear Color Scheme'), + choices: () => + (sequentialSchemeRegistry.values() as SequentialScheme[]).map(value => [ + value.id, + value.label, + ]), + default: sequentialSchemeRegistry.getDefaultKey(), + clearable: false, + description: t('Select a linear color scheme'), + renderTrigger: true, + schemes: () => sequentialSchemeRegistry.getMap(), + isLinear: true, + mapStateToProps: state => getColorControlsProps(state), + visibility: ({ controls }) => + isColorSchemeTypeVisible(controls, COLOR_SCHEME_TYPES.linear_palette), + }, +}; + +export const deckGLColorBreakpointsSelect: CustomControlItem = { + name: 'color_breakpoints', + config: { + label: t('Color breakpoints'), + type: 'ColorBreakpointsControl', + description: t('Define color breakpoints for the data'), + renderTrigger: true, + visibility: ({ controls }) => + isColorSchemeTypeVisible(controls, COLOR_SCHEME_TYPES.color_breakpoints), + }, +}; + +export const breakpointsDefaultColor: CustomControlItem = { + name: 'deafult_breakpoint_color', + config: { + label: t('Default color'), + type: 'ColorPickerControl', + description: t( + "The color used when a value doesn't match any defined breakpoints.", + ), + default: DEFAULT_DECKGL_COLOR, + renderTrigger: true, + visibility: ({ controls }) => + isColorSchemeTypeVisible(controls, COLOR_SCHEME_TYPES.color_breakpoints), + }, +}; + +export const deckGLCategoricalColorSchemeControls = [ + [deckGLCategoricalColorSchemeTypeSelect], + [deckGLFixedColor], + [deckGLCategoricalColor], + [deckGLCategoricalColorSchemeSelect], + [deckGLColorBreakpointsSelect], +]; + +export const generateDeckGLColorSchemeControls = ({ + defaultSchemeType, + disableCategoricalColumn = false, +}: { + defaultSchemeType?: ColorSchemeType; + disableCategoricalColumn?: boolean; +}) => [ + [ + { + name: 'color_scheme_type', + config: { + type: 'SelectControl', + label: t('Color Scheme Type'), + description: t('Select the type of color scheme to use.'), + clearable: false, + renderTrigger: true, + validators: [], + choices: [ + [COLOR_SCHEME_TYPES.fixed_color, t('Fixed color')], + [COLOR_SCHEME_TYPES.categorical_palette, t('Categorical palette')], + [COLOR_SCHEME_TYPES.color_breakpoints, t('Color breakpoints')], + ], + default: defaultSchemeType || COLOR_SCHEME_TYPES.categorical_palette, + }, + }, + ], + [deckGLFixedColor], + disableCategoricalColumn ? [] : [deckGLCategoricalColor], + [deckGLCategoricalColorSchemeSelect], + [breakpointsDefaultColor], + [deckGLColorBreakpointsSelect], +]; diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/utilities/utils.ts b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/utilities/utils.ts index 48c164eb5da..b48cfa80c81 100644 --- a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/utilities/utils.ts +++ b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/utilities/utils.ts @@ -16,8 +16,25 @@ * specific language governing permissions and limitations * under the License. */ +import { ControlStateMapping } from '@superset-ui/chart-controls'; + +export const COLOR_SCHEME_TYPES = { + fixed_color: 'fixed_color', + categorical_palette: 'categorical_palette', + linear_palette: 'linear_palette', + color_breakpoints: 'color_breakpoints', +} as const; + +export type ColorSchemeType = + (typeof COLOR_SCHEME_TYPES)[keyof typeof COLOR_SCHEME_TYPES]; + /* eslint camelcase: 0 */ export function formatSelectOptions(options: (string | number)[]) { return options.map(opt => [opt, opt.toString()]); } + +export const isColorSchemeTypeVisible = ( + controls: ControlStateMapping, + colorSchemeType: ColorSchemeType, +) => controls.color_scheme_type.value === colorSchemeType; diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/utils.test.ts b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/utils.test.ts new file mode 100644 index 00000000000..ad009ea94ad --- /dev/null +++ b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/utils.test.ts @@ -0,0 +1,46 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { getColorBreakpointsBuckets } from './utils'; +import { ColorBreakpointType } from './types'; + +describe('getColorBreakpointsBuckets', () => { + it('returns correct buckets for multiple breakpoints', () => { + const color_breakpoints: ColorBreakpointType[] = [ + { minValue: 0, maxValue: 10, color: { r: 255, g: 0, b: 0, a: 100 } }, + { minValue: 11, maxValue: 20, color: { r: 0, g: 255, b: 0, a: 100 } }, + { minValue: 21, maxValue: 30, color: { r: 0, g: 0, b: 255, a: 100 } }, + ]; + const result = getColorBreakpointsBuckets(color_breakpoints); + expect(result).toEqual({ + '0 - 10': { color: [255, 0, 0], enabled: true }, + '11 - 20': { color: [0, 255, 0], enabled: true }, + '21 - 30': { color: [0, 0, 255], enabled: true }, + }); + }); + + it('returns empty object if color_breakpoints is empty', () => { + const result = getColorBreakpointsBuckets([]); + expect(result).toEqual({}); + }); + + it('returns empty object if color_breakpoints is missing', () => { + const result = getColorBreakpointsBuckets({} as any); + expect(result).toEqual({}); + }); +}); diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/utils.ts b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/utils.ts index 2cfcc3c2e82..4d10e4c5ea1 100644 --- a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/utils.ts +++ b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/utils.ts @@ -25,16 +25,12 @@ import { QueryFormData, SequentialScheme, } from '@superset-ui/core'; -import { GeoBoundingBox, TileLayer } from '@deck.gl/geo-layers'; -import { BitmapLayer, PathLayer } from '@deck.gl/layers'; +import { Color } from '@deck.gl/core'; import { hexToRGB } from './utils/colors'; +import { ColorBreakpointType } from './types'; const DEFAULT_NUM_BUCKETS = 10; -export const MAPBOX_LAYER_PREFIX = 'mapbox://'; -export const TILE_LAYER_PREFIX = 'tile://'; -export const OSM_LAYER_KEYWORDS = ['openstreetmap', 'osm']; - export type Buckets = { break_points: string[]; num_buckets: string; @@ -99,7 +95,7 @@ export function getBreakPointColorScaler( }: BucketsWithColorScale, features: JsonObject[], accessor: (value: JsonObject) => number | undefined, -) { +): (data?: JsonObject) => Color { const breakPoints = formDataBreakPoints || formDataNumBuckets ? getBreakPoints( @@ -119,7 +115,7 @@ export function getBreakPointColorScaler( : getSequentialSchemeRegistry().get(linearColorScheme); if (!colorScheme) { - return null; + return () => [0, 0, 0, 0]; } let scaler: ScaleLinear | ScaleThreshold; let maskPoint: (v: number | undefined) => boolean; @@ -155,7 +151,7 @@ export function getBreakPointColorScaler( maskPoint = () => false; } - return (d: JsonObject): [number, number, number, number] => { + return (d: JsonObject): Color => { const v = accessor(d); if (!v) { return [0, 0, 0, 0]; @@ -180,7 +176,7 @@ export function getBuckets( const colorScaler = getBreakPointColorScaler(fd, features, accessor); const buckets: Record< string, - { color: [number, number, number, number] | undefined; enabled: boolean } + { color: Color | undefined; enabled: boolean } > = {}; breakPoints.slice(1).forEach((_, i) => { const range = `${breakPoints[i]} - ${breakPoints[i + 1]}`; @@ -197,41 +193,25 @@ export function getBuckets( return buckets; } -export function buildTileLayer(url: string, id: string) { - interface TileLayerProps { - id: string; - data: string; - minZoom: number; - maxZoom: number; - tileSize: number; - renderSubLayers: (props: any) => (BitmapLayer | PathLayer)[]; +export function getColorBreakpointsBuckets( + colorBreakpoints: ColorBreakpointType[], +) { + const breakpoints = colorBreakpoints || []; + + const buckets: Record = {}; + + if (!breakpoints || !breakpoints.length) { + return buckets; } - interface RenderSubLayerProps { - tile: { - bbox: GeoBoundingBox; + breakpoints.forEach((breakpoint: ColorBreakpointType) => { + const range = `${breakpoint.minValue} - ${breakpoint.maxValue}`; + + buckets[range] = { + color: [breakpoint.color.r, breakpoint.color.g, breakpoint.color.b], + enabled: true, }; - data: any; - } + }); - return new TileLayer({ - data: url, - id, - minZoom: 0, - maxZoom: 19, - tileSize: 256, - - renderSubLayers: (props: RenderSubLayerProps): BitmapLayer[] => { - const { west, north, east, south } = props.tile.bbox as GeoBoundingBox; - - // Ajouter une BitmapLayer - const bitmapLayer = new BitmapLayer(props, { - data: undefined, - image: props.data, - bounds: [west, south, east, north], - }); - - return [bitmapLayer]; - }, - } as TileLayerProps); + return buckets; } diff --git a/superset-frontend/src/explore/components/controls/ColorBreakpointsControl/ColorBreakpointOption.test.tsx b/superset-frontend/src/explore/components/controls/ColorBreakpointsControl/ColorBreakpointOption.test.tsx new file mode 100644 index 00000000000..5049aaca7ef --- /dev/null +++ b/superset-frontend/src/explore/components/controls/ColorBreakpointsControl/ColorBreakpointOption.test.tsx @@ -0,0 +1,125 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { + render, + screen, + userEvent, + waitFor, +} from 'spec/helpers/testing-library'; +import ColorBreakpointOption from './ColorBreakpointOption'; +import { ColorBreakpointType, ColorBreakpointOptionProps } from './types'; + +const mockBreakpoint: ColorBreakpointType = { + id: 0, + color: { r: 255, g: 0, b: 0, a: 1 }, + minValue: 0, + maxValue: 100, +}; + +const mockBreakpoints: ColorBreakpointType[] = [mockBreakpoint]; + +const createProps = (): ColorBreakpointOptionProps => ({ + breakpoint: mockBreakpoint, + colorBreakpoints: mockBreakpoints, + index: 0, + saveColorBreakpoint: jest.fn(), + onClose: jest.fn(), + onShift: jest.fn(), +}); + +const renderComponent = (props: Partial = {}) => + render(, { + useDnd: true, + }); + +describe('ColorBreakpointOption', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + test('should render', async () => { + const { container } = renderComponent(); + await waitFor(() => expect(container).toBeInTheDocument()); + }); + + test('should render the breakpoint range text', async () => { + renderComponent(); + expect(await screen.findByText('0 - 100')).toBeInTheDocument(); + }); + + test('should render the remove button', async () => { + renderComponent(); + const removeBtn = await screen.findByTestId('remove-control-button'); + expect(removeBtn).toBeInTheDocument(); + }); + + test('should render the color preview', async () => { + renderComponent(); + const colorPreview = await screen.findByTestId('color-preview'); + expect(colorPreview).toBeInTheDocument(); + }); + + test('should call onClose when remove button is clicked', async () => { + const onClose = jest.fn(); + renderComponent({ onClose }); + + const removeBtn = await screen.findByTestId('remove-control-button'); + userEvent.click(removeBtn); + + expect(onClose).toHaveBeenCalledWith(0); + }); + + test('should open popover when clicked', async () => { + renderComponent(); + + const breakpointOption = await screen.findByTestId( + 'color-breakpoint-trigger', + ); + userEvent.click(breakpointOption); + + expect(screen.getByRole('dialog')).toBeInTheDocument(); + }); + + test('should render different color values correctly', async () => { + const blueBreakpoint: ColorBreakpointType = { + id: 1, + color: { r: 0, g: 0, b: 255, a: 1 }, + minValue: 50, + maxValue: 150, + }; + + renderComponent({ breakpoint: blueBreakpoint }); + expect(await screen.findByText('50 - 150')).toBeInTheDocument(); + + const colorPreview = await screen.findByTestId('color-preview'); + expect(colorPreview).toBeInTheDocument(); + }); + + test('should handle decimal values', async () => { + const decimalBreakpoint: ColorBreakpointType = { + id: 2, + color: { r: 128, g: 128, b: 128, a: 1 }, + minValue: 0.5, + maxValue: 99.9, + }; + + renderComponent({ breakpoint: decimalBreakpoint }); + expect(await screen.findByText('0.5 - 99.9')).toBeInTheDocument(); + }); +}); diff --git a/superset-frontend/src/explore/components/controls/ColorBreakpointsControl/ColorBreakpointOption.tsx b/superset-frontend/src/explore/components/controls/ColorBreakpointsControl/ColorBreakpointOption.tsx new file mode 100644 index 00000000000..b280248cfa3 --- /dev/null +++ b/superset-frontend/src/explore/components/controls/ColorBreakpointsControl/ColorBreakpointOption.tsx @@ -0,0 +1,66 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { styled } from '@superset-ui/core'; +import { ColorBreakpointOptionProps } from './types'; +import ColorBreakpointPopoverTrigger from './ColorBreakpointPopoverTrigger'; +import { DragContainer } from '../OptionControls'; +import Option from '../DndColumnSelectControl/Option'; + +const BreakpointColorPreview = styled.div` + width: ${({ theme }) => theme.sizeUnit * 3}px; + height: ${({ theme }) => theme.sizeUnit * 3}px; + border-radius: ${({ theme }) => theme.sizeUnit / 2}px; + background: ${(props: { color: string }) => props.color}; + margin-right: ${({ theme }) => theme.sizeUnit}px; +`; + +const ColorBreakpointOption = ({ + breakpoint, + colorBreakpoints, + index, + saveColorBreakpoint, + onClose, +}: ColorBreakpointOptionProps) => { + const { color, minValue, maxValue } = breakpoint; + + const formattedColor = color + ? `rgba(${color.r}, ${color.g}, ${color.b}, 1)` + : ''; + + return ( + + + + + + ); +}; + +export default ColorBreakpointOption; diff --git a/superset-frontend/src/explore/components/controls/ColorBreakpointsControl/ColorBreakpointPopoverControl.test.tsx b/superset-frontend/src/explore/components/controls/ColorBreakpointsControl/ColorBreakpointPopoverControl.test.tsx new file mode 100644 index 00000000000..edec0a8c5e7 --- /dev/null +++ b/superset-frontend/src/explore/components/controls/ColorBreakpointsControl/ColorBreakpointPopoverControl.test.tsx @@ -0,0 +1,312 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { render, screen, waitFor } from 'spec/helpers/testing-library'; +import userEvent from '@testing-library/user-event'; +import ColorBreakpointPopoverControl from './ColorBreakpointPopoverControl'; +import { + ColorBreakpointType, + ColorBreakpointsPopoverControlProps, +} from './types'; + +const mockBreakpoint: ColorBreakpointType = { + id: 0, + color: { r: 255, g: 0, b: 0, a: 100 }, + minValue: 0, + maxValue: 100, +}; + +const mockBreakpoints: ColorBreakpointType[] = [mockBreakpoint]; + +// Couldn't test the color select - providing color instead of interacting with the color select component +const mockEmptyBreakpoint: ColorBreakpointType = { + id: 0, + color: { r: 0, g: 0, b: 0, a: 0 }, +}; + +const createProps = (): ColorBreakpointsPopoverControlProps => ({ + value: mockBreakpoint, + onSave: jest.fn(), + onClose: jest.fn(), + colorBreakpoints: mockBreakpoints, +}); + +const renderComponent = ( + props: Partial = {}, +) => render(); + +describe('ColorBreakpointPopoverControl', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + test('should render with default props', () => { + renderComponent(); + + expect(screen.getByText('Color for breakpoint')).toBeInTheDocument(); + expect(screen.getByText('Min value')).toBeInTheDocument(); + expect(screen.getByText('Max value')).toBeInTheDocument(); + }); + + test('should render close and save buttons', () => { + renderComponent(); + + expect(screen.getByTestId('close-button')).toBeInTheDocument(); + expect(screen.getByTestId('save-button')).toBeInTheDocument(); + }); + + test('should render with existing breakpoint values', () => { + renderComponent(); + + expect(screen.getByDisplayValue('0')).toBeInTheDocument(); + expect(screen.getByDisplayValue('100')).toBeInTheDocument(); + }); + + test('should call onClose when close button is clicked', async () => { + const onClose = jest.fn(); + renderComponent({ onClose }); + + const closeButton = screen.getByTestId('close-button'); + userEvent.click(closeButton); + + expect(onClose).toHaveBeenCalled(); + }); + + test('should disable save button when form is incomplete', () => { + renderComponent({ value: undefined }); + + const saveButton = screen.getByTestId('save-button'); + expect(saveButton).toBeDisabled(); + }); + + test('should enable save button when form is complete', async () => { + renderComponent({ value: mockEmptyBreakpoint }); + + const minInput = screen.getByTestId('min-value-input'); + const maxInput = screen.getByTestId('max-value-input'); + + userEvent.type(minInput, '10'); + userEvent.type(maxInput, '90'); + + await waitFor(() => { + const saveButton = screen.getByTestId('save-button'); + expect(saveButton).toBeEnabled(); + }); + }); + + test('should call onSave with correct values when save button is clicked', async () => { + const onSave = jest.fn(); + const onClose = jest.fn(); + + renderComponent({ + onSave, + onClose, + value: mockEmptyBreakpoint, + }); + + const minInput = screen.getByTestId('min-value-input'); + const maxInput = screen.getByTestId('max-value-input'); + + userEvent.type(minInput, '10'); + userEvent.type(maxInput, '90'); + + await waitFor(() => { + const saveButton = screen.getByTestId('save-button'); + expect(saveButton).toBeEnabled(); + }); + + const saveButton = screen.getByTestId('save-button'); + userEvent.click(saveButton); + + expect(onSave).toHaveBeenCalledWith( + expect.objectContaining({ + minValue: 10, + maxValue: 90, + color: expect.objectContaining({ + r: expect.any(Number), + g: expect.any(Number), + b: expect.any(Number), + a: expect.any(Number), + }), + }), + ); + expect(onClose).toHaveBeenCalled(); + }); + + test('should disable save button when min value >= max value', async () => { + renderComponent({ value: undefined }); + + const minInput = screen.getByTestId('min-value-input'); + const maxInput = screen.getByTestId('max-value-input'); + + userEvent.type(minInput, '100'); + userEvent.type(maxInput, '50'); + + const saveButton = screen.getByTestId('save-button'); + expect(saveButton).toBeDisabled(); + }); + + test('should disable save button when breakpoint overlaps with existing ones', async () => { + const existingBreakpoints: ColorBreakpointType[] = [ + { + id: 0, + color: { r: 255, g: 0, b: 0, a: 100 }, + minValue: 0, + maxValue: 50, + }, + { + id: 1, + color: { r: 0, g: 255, b: 0, a: 100 }, + minValue: 100, + maxValue: 200, + }, + ]; + + renderComponent({ + colorBreakpoints: existingBreakpoints, + value: undefined, + }); + + const minInput = screen.getByTestId('min-value-input'); + const maxInput = screen.getByTestId('max-value-input'); + + userEvent.type(minInput, '25'); + userEvent.type(maxInput, '75'); + + const saveButton = screen.getByTestId('save-button'); + expect(saveButton).toBeDisabled(); + }); + + test('should handle non-numeric input validation', async () => { + renderComponent({ value: undefined }); + + const minInput = screen.getByTestId('min-value-input'); + const maxInput = screen.getByTestId('max-value-input'); + + userEvent.type(minInput, 'abc'); + userEvent.type(maxInput, '100'); + + await waitFor(() => { + const saveButton = screen.getByTestId('save-button'); + expect(saveButton).toBeDisabled(); + }); + }); + + test('should update min value when input changes', async () => { + renderComponent(); + + const minInput = screen.getByDisplayValue('0'); + userEvent.clear(minInput); + userEvent.type(minInput, '20'); + + expect(screen.getByDisplayValue('20')).toBeInTheDocument(); + }); + + test('should update max value when input changes', async () => { + renderComponent(); + + const maxInput = screen.getByDisplayValue('100'); + userEvent.clear(maxInput); + userEvent.type(maxInput, '200'); + + expect(screen.getByDisplayValue('200')).toBeInTheDocument(); + }); + + test('should handle zero values correctly', async () => { + renderComponent({ + value: mockEmptyBreakpoint, + }); + + const minInput = screen.getByTestId('min-value-input'); + const maxInput = screen.getByTestId('max-value-input'); + + userEvent.type(minInput, '0'); + userEvent.type(maxInput, '10'); + + await waitFor(() => { + const saveButton = screen.getByTestId('save-button'); + expect(saveButton).toBeEnabled(); + }); + }); + + test('should handle negative values correctly', async () => { + renderComponent({ + value: mockEmptyBreakpoint, + }); + + const minInput = screen.getByTestId('min-value-input'); + const maxInput = screen.getByTestId('max-value-input'); + + userEvent.type(minInput, '-10'); + userEvent.type(maxInput, '10'); + + await waitFor(() => { + const saveButton = screen.getByTestId('save-button'); + expect(saveButton).toBeEnabled(); + }); + }); + + test('should handle decimal values correctly', async () => { + renderComponent({ + value: mockEmptyBreakpoint, + }); + + const minInput = screen.getByTestId('min-value-input'); + const maxInput = screen.getByTestId('max-value-input'); + + userEvent.type(minInput, '0.5'); + userEvent.type(maxInput, '99.9'); + + await waitFor(() => { + const saveButton = screen.getByTestId('save-button'); + expect(saveButton).toBeEnabled(); + }); + }); + + test('should not show overlap error when editing existing breakpoint', async () => { + const existingBreakpoints: ColorBreakpointType[] = [ + { + id: 0, + color: { r: 255, g: 0, b: 0, a: 100 }, + minValue: 0, + maxValue: 50, + }, + { + id: 1, + color: { r: 0, g: 255, b: 0, a: 100 }, + minValue: 100, + maxValue: 200, + }, + ]; + + const editingBreakpoint = existingBreakpoints[0]; + renderComponent({ + colorBreakpoints: existingBreakpoints, + value: editingBreakpoint, + }); + + const minInput = screen.getByDisplayValue('0'); + userEvent.clear(minInput); + userEvent.type(minInput, '10'); + + await waitFor(() => { + const saveButton = screen.getByTestId('save-button'); + expect(saveButton).toBeEnabled(); + }); + }); +}); diff --git a/superset-frontend/src/explore/components/controls/ColorBreakpointsControl/ColorBreakpointPopoverControl.tsx b/superset-frontend/src/explore/components/controls/ColorBreakpointsControl/ColorBreakpointPopoverControl.tsx new file mode 100644 index 00000000000..48ddb11fd0d --- /dev/null +++ b/superset-frontend/src/explore/components/controls/ColorBreakpointsControl/ColorBreakpointPopoverControl.tsx @@ -0,0 +1,265 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { useState, useMemo } from 'react'; +import { Button, Row, Col, InputNumber } from '@superset-ui/core/components'; +import { styled, t, validateNumber } from '@superset-ui/core'; +import ControlHeader from '../../ControlHeader'; +import ColorPickerControl from '../ColorPickerControl'; +import { + ColorBreakpointsPopoverControlProps, + ColorType, + ColorBreakpointType, + ErrorMapType, +} from './types'; + +const ColorBreakpointActionsContainer = styled.div` + margin-top: ${({ theme }) => theme.sizeUnit * 8}px; + display: flex; + justify-content: flex-end; +`; + +const StyledRow = styled(Row)` + gap: ${({ theme }) => theme.sizeUnit * 2}px; +`; + +const ValuesRow = styled(Row)` + gap: ${({ theme }) => theme.sizeUnit * 2}px; + display: flex; + align-items: flex-end; +`; + +const NumberControlsDivider = styled.div` + padding: 6px 0; +`; + +const FullWidthInputNumber = styled(InputNumber)` + width: 100%; +`; + +const determineErrorMap = ( + colorBreakpoint: ColorBreakpointType, + colorBreakpoints: ColorBreakpointType[], +) => { + const errorMap: ErrorMapType = { + minValue: [], + maxValue: [], + color: [], + }; + const minValueError = validateNumber(colorBreakpoint.minValue); + if (minValueError) errorMap.minValue.push(minValueError); + + const maxValueError = validateNumber(colorBreakpoint.maxValue); + if (maxValueError) errorMap.maxValue.push(maxValueError); + + if (minValueError || maxValueError) return errorMap; + + const newMinValue = Number(colorBreakpoint.minValue); + const newMaxValue = Number(colorBreakpoint.maxValue); + + if (Number.isNaN(newMinValue) || Number.isNaN(newMaxValue)) { + return errorMap; + } + + if (newMinValue >= newMaxValue) { + errorMap.minValue.push( + t('Min value should be smaller or equal to max value'), + ); + } + + const otherBreakpoints = colorBreakpoints.filter( + breakpoint => breakpoint.id !== colorBreakpoint.id, + ); + + const isBreakpointDuplicate = !!otherBreakpoints?.find( + breakpoint => + Number(breakpoint.minValue) <= newMaxValue && + Number(breakpoint.maxValue) >= newMinValue, + ); + + if (isBreakpointDuplicate) { + const overlapMsg = t('The values overlap other breakpoint values'); + + errorMap.minValue.push(overlapMsg); + errorMap.maxValue.push(overlapMsg); + } + + const validColor = + typeof colorBreakpoint.color === 'object' && + 'r' in colorBreakpoint.color && + typeof colorBreakpoint.color.r === 'number' && + 'g' in colorBreakpoint.color && + typeof colorBreakpoint.color.g === 'number' && + 'b' in colorBreakpoint.color && + typeof colorBreakpoint.color.b === 'number' && + 'a' in colorBreakpoint.color && + typeof colorBreakpoint.color.a === 'number'; + + if (!validColor) { + errorMap.color.push(t('Invalid color')); + } + + return errorMap; +}; + +const convertColorBreakpointToNumeric = ( + colorBreakpoint: ColorBreakpointType, +) => { + const formattedColorBreakpoint = { + color: colorBreakpoint.color, + minValue: Number(colorBreakpoint.minValue), + maxValue: Number(colorBreakpoint.maxValue), + }; + return formattedColorBreakpoint; +}; + +const DEFAULT_COLOR_BREAKPOINT: ColorBreakpointType = { + id: undefined, + minValue: undefined, + maxValue: undefined, + color: { r: 0, g: 0, b: 0, a: 100 }, +}; + +const ColorBreakpointsPopoverControl = ({ + value: initialValue, + onSave, + onClose, + colorBreakpoints, +}: ColorBreakpointsPopoverControlProps) => { + const [colorBreakpoint, setColorBreakpoint] = useState( + initialValue || DEFAULT_COLOR_BREAKPOINT, + ); + + const useValidationErrors = ( + colorBreakpoint: ColorBreakpointType, + colorBreakpoints: ColorBreakpointType[], + ) => + useMemo( + () => determineErrorMap(colorBreakpoint, colorBreakpoints), + [colorBreakpoint, colorBreakpoints], + ); + + const validationErrors = useValidationErrors( + colorBreakpoint, + colorBreakpoints, + ); + + const updateColor = (rgb: ColorType) => { + setColorBreakpoint({ + ...colorBreakpoint, + color: { ...rgb }, + }); + }; + + const updateMinValue = (value: number) => { + setColorBreakpoint({ + ...colorBreakpoint, + minValue: value, + }); + }; + + const updateMaxValue = (value: number) => { + setColorBreakpoint({ + ...colorBreakpoint, + maxValue: value, + }); + }; + + const containsErrors = Object.values(validationErrors).some( + errors => errors.length > 0, + ); + + const handleSave = () => { + if (!containsErrors && onSave) { + onSave(convertColorBreakpointToNumeric(colorBreakpoint)); + if (onClose) onClose(); + } + }; + + return ( +
+ + + + + + + + + + + + - + + + + + + + + + +
+ ); +}; + +export default ColorBreakpointsPopoverControl; diff --git a/superset-frontend/src/explore/components/controls/ColorBreakpointsControl/ColorBreakpointPopoverTrigger.test.tsx b/superset-frontend/src/explore/components/controls/ColorBreakpointsControl/ColorBreakpointPopoverTrigger.test.tsx new file mode 100644 index 00000000000..7bd36f97236 --- /dev/null +++ b/superset-frontend/src/explore/components/controls/ColorBreakpointsControl/ColorBreakpointPopoverTrigger.test.tsx @@ -0,0 +1,236 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { render, screen, userEvent } from 'spec/helpers/testing-library'; +import ColorBreakpointPopoverTrigger from './ColorBreakpointPopoverTrigger'; +import { + ColorBreakpointType, + ColorBreakpointsPopoverTriggerProps, +} from './types'; + +const mockBreakpoint: ColorBreakpointType = { + id: 0, + color: { r: 255, g: 0, b: 0, a: 1 }, + minValue: 0, + maxValue: 100, +}; + +// Couldn't test the color select - providing color instead of interacting with the color select component +const mockEmptyBreakpoint: ColorBreakpointType = { + id: 0, + color: { r: 0, g: 0, b: 0, a: 0 }, +}; + +const mockBreakpoints: ColorBreakpointType[] = [mockBreakpoint]; + +const createProps = (): ColorBreakpointsPopoverTriggerProps => ({ + value: mockBreakpoint, + saveColorBreakpoint: jest.fn(), + colorBreakpoints: mockBreakpoints, +}); + +const renderComponent = ( + props: Partial = {}, +) => + render( + + Click to add new breakpoint + , + ); + +describe('ColorBreakpointPopoverTrigger', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + test('should render', () => { + const { container } = renderComponent(); + expect(container).toBeInTheDocument(); + }); + + test('should render children', () => { + renderComponent(); + expect(screen.getByText('Click to add new breakpoint')).toBeInTheDocument(); + }); + + test('should render the popover on click when uncontrolled', async () => { + renderComponent(); + + expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); + + const triggerButton = screen.getByText('Click to add new breakpoint'); + userEvent.click(triggerButton); + + expect(screen.queryByRole('dialog')).toBeInTheDocument(); + }); + + test('should be visible when controlled and visible is true', async () => { + const controlledProps = { + isControlled: true, + visible: true, + toggleVisibility: jest.fn(), + }; + + renderComponent(controlledProps); + + expect(await screen.findByRole('dialog')).toBeInTheDocument(); + }); + + test('should NOT be visible when controlled and visible is false', () => { + const controlledProps = { + isControlled: true, + visible: false, + toggleVisibility: jest.fn(), + }; + + renderComponent(controlledProps); + expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); + }); + + test('should call toggleVisibility when controlled and popover state changes', async () => { + const toggleVisibility = jest.fn(); + const controlledProps = { + isControlled: true, + visible: false, + toggleVisibility, + }; + + renderComponent(controlledProps); + + const triggerButton = screen.getByText('Click to add new breakpoint'); + await userEvent.click(triggerButton); + + expect(toggleVisibility).toHaveBeenCalledWith(true); + }); + + test('should render popover content with form elements', async () => { + renderComponent(); + + const triggerButton = screen.getByText('Click to add new breakpoint'); + userEvent.click(triggerButton); + + expect(screen.getByText('Color for breakpoint')).toBeInTheDocument(); + expect(screen.getByText('Min value')).toBeInTheDocument(); + expect(screen.getByText('Max value')).toBeInTheDocument(); + }); + + test('should close popover when save is called', async () => { + renderComponent({ value: mockEmptyBreakpoint }); + + const triggerButton = screen.getByText('Click to add new breakpoint'); + userEvent.click(triggerButton); + + const minInput = screen.getAllByRole('spinbutton')[0]; + const maxInput = screen.getAllByRole('spinbutton')[1]; + + userEvent.type(minInput, '10'); + userEvent.type(maxInput, '90'); + + const saveButton = screen.getByTestId('save-button'); + + expect(saveButton).toBeEnabled(); + userEvent.click(saveButton); + + expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); + }); + + test('should close popover when close button is clicked', async () => { + renderComponent(); + + const triggerButton = screen.getByText('Click to add new breakpoint'); + userEvent.click(triggerButton); + + const closeButton = screen.getByTestId('close-button'); + userEvent.click(closeButton); + + expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); + }); + + test('should handle undefined value prop', async () => { + renderComponent({ value: undefined }); + + const triggerButton = screen.getByText('Click to add new breakpoint'); + userEvent.click(triggerButton); + + expect(screen.queryByRole('dialog')).toBeInTheDocument(); + expect(screen.getByText('Color for breakpoint')).toBeInTheDocument(); + }); + + test('should handle popover open state changes correctly', async () => { + renderComponent(); + + const triggerButton = screen.getByText('Click to add new breakpoint'); + + userEvent.click(triggerButton); + expect(screen.queryByRole('dialog')).toBeInTheDocument(); + + userEvent.click(triggerButton); + expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); + }); + + test('should maintain controlled state when toggleVisibility is provided', async () => { + const toggleVisibility = jest.fn(); + const controlledProps = { + isControlled: true, + visible: true, + toggleVisibility, + }; + + renderComponent(controlledProps); + + expect(screen.queryByRole('dialog')).toBeInTheDocument(); + + const closeButton = screen.getByTestId('close-button'); + userEvent.click(closeButton); + + expect(toggleVisibility).toHaveBeenCalledWith(false); + }); + + test('should pass colorBreakpoints to popover content', async () => { + const colorBreakpoints = [ + { id: 0, color: { r: 255, g: 0, b: 0, a: 1 }, minValue: 0, maxValue: 50 }, + { + id: 1, + color: { r: 0, g: 255, b: 0, a: 1 }, + minValue: 50, + maxValue: 100, + }, + ]; + + renderComponent({ colorBreakpoints }); + + const triggerButton = screen.getByText('Click to add new breakpoint'); + userEvent.click(triggerButton); + + expect(screen.queryByRole('dialog')).toBeInTheDocument(); + }); + + test('should handle destroyOnHidden prop', async () => { + renderComponent(); + + const triggerButton = screen.getByText('Click to add new breakpoint'); + userEvent.click(triggerButton); + + expect(screen.queryByRole('dialog')).toBeInTheDocument(); + + const closeButton = screen.getByTestId('close-button'); + userEvent.click(closeButton); + + expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); + }); +}); diff --git a/superset-frontend/src/explore/components/controls/ColorBreakpointsControl/ColorBreakpointPopoverTrigger.tsx b/superset-frontend/src/explore/components/controls/ColorBreakpointsControl/ColorBreakpointPopoverTrigger.tsx new file mode 100644 index 00000000000..f4b85dd631b --- /dev/null +++ b/superset-frontend/src/explore/components/controls/ColorBreakpointsControl/ColorBreakpointPopoverTrigger.tsx @@ -0,0 +1,62 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { useState } from 'react'; +import ControlPopover from '../ControlPopover/ControlPopover'; +import { ColorBreakpointsPopoverTriggerProps } from './types'; +import ColorBreakpointPopoverControl from './ColorBreakpointPopoverControl'; + +const ColorBreakpointsPopoverTrigger = ({ + value: initialValue, + saveColorBreakpoint, + isControlled, + visible: controlledVisibility, + toggleVisibility, + colorBreakpoints, + ...props +}: ColorBreakpointsPopoverTriggerProps) => { + const [isVisible, setIsVisible] = useState(false); + + const visible = isControlled ? controlledVisibility : isVisible; + const setVisibility = + isControlled && toggleVisibility ? toggleVisibility : setIsVisible; + + const popoverContent = ( + setVisibility(false)} + /> + ); + + return ( + + {props.children} + + ); +}; + +export default ColorBreakpointsPopoverTrigger; diff --git a/superset-frontend/src/explore/components/controls/ColorBreakpointsControl/ColorBreakpointsControl.test.tsx b/superset-frontend/src/explore/components/controls/ColorBreakpointsControl/ColorBreakpointsControl.test.tsx new file mode 100644 index 00000000000..cfce7e529a7 --- /dev/null +++ b/superset-frontend/src/explore/components/controls/ColorBreakpointsControl/ColorBreakpointsControl.test.tsx @@ -0,0 +1,225 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { render, screen, waitFor } from 'spec/helpers/testing-library'; +import userEvent from '@testing-library/user-event'; +import ColorBreakpointsControl from '.'; +import { ColorBreakpointType, ColorBreakpointsControlProps } from './types'; + +interface Props extends ColorBreakpointsControlProps { + name: string; + label: string; + value: ColorBreakpointType[]; + onChange: jest.Mock; + breakpoints: ColorBreakpointType[]; +} + +const createProps = (): Props => ({ + name: 'ColorBreakpointsControl', + label: 'Color Breakpoints', + value: [], + onChange: jest.fn(), + breakpoints: [], + actions: { + setControlValue: jest.fn(), + }, + type: 'ColorBreakpointsControl', +}); + +const renderComponent = (props: Partial = {}) => + render(, { + useDnd: true, + }); + +describe('ColorBreakpointsControl', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should render with default props', () => { + renderComponent(); + expect(screen.getByText('Click to add new breakpoint')).toBeInTheDocument(); + }); + + test('should render existing breakpoints', () => { + const existingBreakpoint: ColorBreakpointType = { + id: 0, + color: { r: 255, g: 0, b: 0, a: 1 }, + minValue: 0, + maxValue: 100, + }; + + renderComponent({ value: [existingBreakpoint] }); + expect(screen.getByText('0 - 100')).toBeInTheDocument(); + }); + + test('should handle empty breakpoints array', () => { + renderComponent({ value: [] }); + expect(screen.getByText('Click to add new breakpoint')).toBeInTheDocument(); + }); + + test('should handle multiple breakpoints', () => { + const breakpoints: ColorBreakpointType[] = [ + { + id: 0, + color: { r: 255, g: 0, b: 0, a: 1 }, + minValue: 0, + maxValue: 50, + }, + { + id: 1, + color: { r: 0, g: 255, b: 0, a: 1 }, + minValue: 50, + maxValue: 100, + }, + ]; + + renderComponent({ value: breakpoints }); + expect(screen.getByText('0 - 50')).toBeInTheDocument(); + expect(screen.getByText('50 - 100')).toBeInTheDocument(); + }); + + test('should call onChange when component state updates', () => { + const onChange = jest.fn(); + renderComponent({ onChange }); + + expect(onChange).toHaveBeenCalledWith([]); + }); + + test('should show new breakpoint button when no breakpoints exist', () => { + renderComponent(); + const ghostButton = screen.getByText('Click to add new breakpoint'); + expect(ghostButton).toBeInTheDocument(); + }); + + test('should handle new breakpoint button click and popover visibility state', async () => { + renderComponent(); + + expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); + + const addButton = screen.getByText('Click to add new breakpoint'); + userEvent.click(addButton); + + expect(screen.getByRole('dialog')).toBeInTheDocument(); + }); + + test('should save new breakpoint and update state', async () => { + const onChange = jest.fn(); + renderComponent({ onChange }); + + const addButton = screen.getByText('Click to add new breakpoint'); + userEvent.click(addButton); + + const minInput = screen.getByTestId('min-value-input'); + const maxInput = screen.getByTestId('max-value-input'); + + userEvent.type(minInput, '10'); + userEvent.type(maxInput, '90'); + + await waitFor(() => { + const saveButton = screen.getByTestId('save-button'); + expect(saveButton).toBeEnabled(); + }); + + const saveButton = screen.getByTestId('save-button'); + userEvent.click(saveButton); + + expect(onChange).toHaveBeenCalledWith( + expect.arrayContaining([ + expect.objectContaining({ + minValue: 10, + maxValue: 90, + id: 0, + }), + ]), + ); + + await waitFor(() => { + expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); + }); + }); + + test('should remove breakpoint when delete is triggered', async () => { + const existingBreakpoint: ColorBreakpointType = { + id: 0, + color: { r: 255, g: 0, b: 0, a: 1 }, + minValue: 0, + maxValue: 100, + }; + const onChange = jest.fn(); + + renderComponent({ value: [existingBreakpoint], onChange }); + + const removeButton = screen.getByTestId('remove-control-button'); + userEvent.click(removeButton); + + expect(onChange).toHaveBeenCalledWith([]); + }); + + test('should edit existing breakpoint when clicked', async () => { + const existingBreakpoint: ColorBreakpointType = { + id: 0, + color: { r: 255, g: 0, b: 0, a: 1 }, + minValue: 0, + maxValue: 100, + }; + const onChange = jest.fn(); + + renderComponent({ value: [existingBreakpoint], onChange }); + + const breakpointOption = screen.getByText('0 - 100'); + userEvent.click(breakpointOption); + + expect(screen.getByRole('dialog')).toBeInTheDocument(); + expect(screen.getByDisplayValue('0')).toBeInTheDocument(); + expect(screen.getByDisplayValue('100')).toBeInTheDocument(); + }); + + test('should handle DndSelectLabel props correctly', () => { + renderComponent(); + + const dndSelectLabel = screen + .getByText('Click to add new breakpoint') + .closest('div'); + expect(dndSelectLabel).toBeInTheDocument(); + }); + + test('should assign incremental IDs to new breakpoints', async () => { + const onChange = jest.fn(); + renderComponent({ onChange }); + + const addButton = screen.getByText('Click to add new breakpoint'); + userEvent.click(addButton); + + const minInput = screen.getByTestId('min-value-input'); + const maxInput = screen.getByTestId('max-value-input'); + + userEvent.type(minInput, '0'); + userEvent.type(maxInput, '50'); + + await waitFor(() => { + const saveButton = screen.getByTestId('save-button'); + expect(saveButton).toBeEnabled(); + }); + + const saveButton = screen.getByTestId('save-button'); + userEvent.click(saveButton); + + expect(onChange).toHaveBeenCalledWith([expect.objectContaining({ id: 0 })]); + }); +}); diff --git a/superset-frontend/src/explore/components/controls/ColorBreakpointsControl/index.tsx b/superset-frontend/src/explore/components/controls/ColorBreakpointsControl/index.tsx new file mode 100644 index 00000000000..a33ee21f50f --- /dev/null +++ b/superset-frontend/src/explore/components/controls/ColorBreakpointsControl/index.tsx @@ -0,0 +1,127 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { useState, useEffect } from 'react'; +import { styled, t } from '@superset-ui/core'; +import DndSelectLabel from 'src/explore/components/controls/DndColumnSelectControl/DndSelectLabel'; +import ColorBreakpointOption from './ColorBreakpointOption'; +import { ColorBreakpointType, ColorBreakpointsControlProps } from './types'; +import ColorBreakpointPopoverTrigger from './ColorBreakpointPopoverTrigger'; + +const DEFAULT_COLOR_BREAKPOINTS: ColorBreakpointType[] = []; + +const NewColorBreakpointFormatPlaceholder = styled('div')` + position: relative; + width: calc(100% - ${({ theme }) => theme.sizeUnit}px); + bottom: ${({ theme }) => theme.sizeUnit * 4}px; + left: 0; +`; + +const ColorBreakpointsControl = ({ + onChange, + ...props +}: ColorBreakpointsControlProps) => { + const [popoverVisible, setPopoverVisible] = useState(false); + const [colorBreakpoints, setColorBreakpoints] = useState< + ColorBreakpointType[] + >(props?.value ? props?.value : DEFAULT_COLOR_BREAKPOINTS); + + useEffect(() => { + onChange?.(colorBreakpoints); + }, [colorBreakpoints, onChange]); + + const togglePopover = (visible: boolean) => { + setPopoverVisible(visible); + }; + + const handleClickGhostButton = () => { + togglePopover(true); + }; + + const saveColorBreakpoint = (breakpoint: ColorBreakpointType) => { + setColorBreakpoints([ + ...colorBreakpoints, + { + ...breakpoint, + id: colorBreakpoints.length, + }, + ]); + togglePopover(false); + }; + + const removeColorBreakpoint = (index: number) => { + const newBreakpoints = [...colorBreakpoints]; + newBreakpoints.splice(index, 1); + setColorBreakpoints(newBreakpoints); + }; + + const editColorBreakpoint = ( + breakpoint: ColorBreakpointType, + index: number, + ) => { + const newBreakpoints = [...colorBreakpoints]; + newBreakpoints[index] = { + ...breakpoint, + id: index, + }; + setColorBreakpoints(newBreakpoints); + }; + + const valuesRenderer = () => + colorBreakpoints.map((breakpoint, index) => ( + + editColorBreakpoint(newBreakpoint, index) + } + breakpoint={breakpoint} + colorBreakpoints={colorBreakpoints} + index={index} + onClose={removeColorBreakpoint} + onShift={() => {}} + /> + )); + + const ghostButtonText = t('Click to add new breakpoint'); + + return ( + <> + {}} + canDrop={() => false} + valuesRenderer={valuesRenderer} + accept={[]} + ghostButtonText={ghostButtonText} + onClickGhostButton={handleClickGhostButton} + {...props} + /> + + + + + ); +}; + +export default ColorBreakpointsControl; diff --git a/superset-frontend/src/explore/components/controls/ColorBreakpointsControl/types.ts b/superset-frontend/src/explore/components/controls/ColorBreakpointsControl/types.ts new file mode 100644 index 00000000000..e64b640a8b3 --- /dev/null +++ b/superset-frontend/src/explore/components/controls/ColorBreakpointsControl/types.ts @@ -0,0 +1,76 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { ReactNode } from 'react'; +import { OptionValueType } from 'src/explore/components/controls/DndColumnSelectControl/types'; +import { ControlComponentProps } from 'src/explore/components/Control'; + +export interface ColorType { + r: number; + g: number; + b: number; + a: number; +} + +export interface ColorBreakpointType { + id?: number; + color?: ColorType; + minValue?: number; + maxValue?: number; +} + +export interface ErrorMapType { + color: string[]; + minValue: string[]; + maxValue: string[]; +} + +export interface ColorBreakpointsControlProps + extends ControlComponentProps { + breakpoints: ColorBreakpointType[]; +} + +export interface ColorBreakpointsPopoverTriggerProps { + description?: string; + hovered?: boolean; + value?: ColorBreakpointType; + children?: ReactNode; + saveColorBreakpoint: (colorBreakpoint: ColorBreakpointType) => void; + isControlled?: boolean; + visible?: boolean; + toggleVisibility?: (visibility: boolean) => void; + colorBreakpoints: ColorBreakpointType[]; +} + +export interface ColorBreakpointsPopoverControlProps { + description?: string; + hovered?: boolean; + value?: ColorBreakpointType; + onSave?: (colorBreakpoint: ColorBreakpointType) => void; + onClose?: () => void; + colorBreakpoints: ColorBreakpointType[]; +} + +export interface ColorBreakpointOptionProps { + breakpoint: ColorBreakpointType; + colorBreakpoints: ColorBreakpointType[]; + index: number; + saveColorBreakpoint: (colorBreakpoint: ColorBreakpointType) => void; + onClose: (index: number) => void; + onShift: (hoverIndex: number, dragIndex: number) => void; +} diff --git a/superset-frontend/src/explore/components/controls/ContourControl/ContourOption.tsx b/superset-frontend/src/explore/components/controls/ContourControl/ContourOption.tsx index 1064bbb7601..c058cb6553e 100644 --- a/superset-frontend/src/explore/components/controls/ContourControl/ContourOption.tsx +++ b/superset-frontend/src/explore/components/controls/ContourControl/ContourOption.tsx @@ -52,7 +52,7 @@ const ContourOption = ({ const formattedColor = color ? `rgba(${color.r}, ${color.g}, ${color.b}, 1)` - : 'undefined'; + : 'rgba(0,0,0,0)'; const formatIsoline = (threshold: number, width: number) => `${t('Threshold')}: ${threshold}, ${t('color')}: ${formattedColor}, ${t( diff --git a/superset-frontend/src/explore/components/controls/index.js b/superset-frontend/src/explore/components/controls/index.js index a36a73ab2a2..14e52d16a87 100644 --- a/superset-frontend/src/explore/components/controls/index.js +++ b/superset-frontend/src/explore/components/controls/index.js @@ -56,6 +56,7 @@ import MapViewControl from './MapViewControl/MapViewControl'; import ZoomConfigControl from './ZoomConfigControl/ZoomConfigControl'; import NumberControl from './NumberControl'; import TimeRangeControl from './TimeRangeControl'; +import ColorBreakpointsControl from './ColorBreakpointsControl'; const extensionsRegistry = getExtensionsRegistry(); const DateFilterControlExtension = extensionsRegistry.get( @@ -79,6 +80,7 @@ const controlMap = { DndFilterSelect, DndMetricSelect, FixedOrMetricControl, + ColorBreakpointsControl, HiddenControl, LayerConfigsControl, MapViewControl, diff --git a/superset/config.py b/superset/config.py index a2e92c52bf4..c1eb50dda45 100644 --- a/superset/config.py +++ b/superset/config.py @@ -422,29 +422,6 @@ class D3Format(TypedDict, total=False): D3_FORMAT: D3Format = {} -# Override the default mapbox tiles -# Default values are equivalent to -# DECKGL_BASE_MAP = [ -# ['https://tile.openstreetmap.org/{z}/{x}/{y}.png', 'Streets (OSM)'], -# ['https://tile.osm.ch/osm-swiss-style/{z}/{x}/{y}.png', 'Topography (OSM)'], -# ['mapbox://styles/mapbox/streets-v9', 'Streets'], -# ['mapbox://styles/mapbox/dark-v9', 'Dark'], -# ['mapbox://styles/mapbox/light-v9', 'Light'], -# ['mapbox://styles/mapbox/satellite-streets-v9', 'Satellite Streets'], -# ['mapbox://styles/mapbox/satellite-v9', 'Satellite'], -# ['mapbox://styles/mapbox/outdoors-v9', 'Outdoors'], -# ] -# for adding your own map tiles, you can use the following format: -# - tile:// + your_personal_url or openstreetmap_url -# example: -# DECKGL_BASE_MAP = [ -# ['tile://https://c.tile.openstreetmap.org/{z}/{x}/{y}.png', 'OpenStreetMap'] -# ] -# Enable CORS and set map url in origins option. -# Add also map url in connect-src of TALISMAN_CONFIG variable -DECKGL_BASE_MAP: list[list[str, str]] = None - - # Override the default d3 locale for time format # Default values are equivalent to # D3_TIME_FORMAT = { @@ -857,13 +834,8 @@ STORE_CACHE_KEYS_IN_METADATA_DB = False # CORS Options # NOTE: enabling this requires installing the cors-related python dependencies # `pip install .[cors]` or `pip install apache_superset[cors]`, depending -ENABLE_CORS = True -CORS_OPTIONS: dict[Any, Any] = { - "origins": [ - "https://tile.openstreetmap.org", - "https://tile.osm.ch", - ] -} +ENABLE_CORS = False +CORS_OPTIONS: dict[Any, Any] = {} # Sanitizes the HTML content used in markdowns to allow its rendering in a safe manner. # Disabling this option is not recommended for security reasons. If you wish to allow @@ -1692,8 +1664,6 @@ TALISMAN_CONFIG = { "'self'", "https://api.mapbox.com", "https://events.mapbox.com", - "https://tile.openstreetmap.org", - "https://tile.osm.ch", ], "object-src": "'none'", "style-src": [ @@ -1726,8 +1696,6 @@ TALISMAN_DEV_CONFIG = { "'self'", "https://api.mapbox.com", "https://events.mapbox.com", - "https://tile.openstreetmap.org", - "https://tile.osm.ch", ], "object-src": "'none'", "style-src": [ diff --git a/superset/examples/deck.py b/superset/examples/deck.py index 988e7dc6939..137d14434f0 100644 --- a/superset/examples/deck.py +++ b/superset/examples/deck.py @@ -194,7 +194,7 @@ def load_deck_dash() -> None: # pylint: disable=too-many-statements "datasource": "5__table", "granularity_sqla": None, "groupby": [], - "mapbox_style": "https://tile.openstreetmap.org/{z}/{x}/{y}.png", + "mapbox_style": "mapbox://styles/mapbox/light-v9", "multiplier": 10, "point_radius_fixed": {"type": "metric", "value": "count"}, "point_unit": "square_m", @@ -229,7 +229,7 @@ def load_deck_dash() -> None: # pylint: disable=too-many-statements "point_unit": "square_m", "row_limit": 5000, "spatial": {"type": "latlong", "lonCol": "LON", "latCol": "LAT"}, - "mapbox_style": "https://tile.openstreetmap.org/{z}/{x}/{y}.png", + "mapbox_style": "mapbox://styles/mapbox/dark-v9", "granularity_sqla": None, "size": "count", "viz_type": "deck_screengrid", @@ -263,7 +263,7 @@ def load_deck_dash() -> None: # pylint: disable=too-many-statements slice_data = { "spatial": {"type": "latlong", "lonCol": "LON", "latCol": "LAT"}, "row_limit": 5000, - "mapbox_style": "https://tile.openstreetmap.org/{z}/{x}/{y}.png", + "mapbox_style": "mapbox://styles/mapbox/streets-v9", "granularity_sqla": None, "size": "count", "viz_type": "deck_hex", @@ -300,7 +300,7 @@ def load_deck_dash() -> None: # pylint: disable=too-many-statements "autozoom": False, "spatial": {"type": "latlong", "lonCol": "LON", "latCol": "LAT"}, "row_limit": 5000, - "mapbox_style": "https://tile.openstreetmap.org/{z}/{x}/{y}.png", + "mapbox_style": "mapbox://styles/mapbox/satellite-streets-v9", "granularity_sqla": None, "size": "count", "viz_type": "deck_grid", @@ -367,7 +367,7 @@ def load_deck_dash() -> None: # pylint: disable=too-many-statements }, "line_type": "json", "linear_color_scheme": "oranges", - "mapbox_style": "https://tile.openstreetmap.org/{z}/{x}/{y}.png", + "mapbox_style": "mapbox://styles/mapbox/light-v9", "viewport": { "longitude": -122.43388541747726, "latitude": 37.752020331384834, @@ -442,7 +442,7 @@ def load_deck_dash() -> None: # pylint: disable=too-many-statements "lonCol": "LONGITUDE_DEST", }, "row_limit": 5000, - "mapbox_style": "https://tile.openstreetmap.org/{z}/{x}/{y}.png", + "mapbox_style": "mapbox://styles/mapbox/light-v9", "viewport": { "altitude": 1.5, "bearing": 8.546256357301871, @@ -486,7 +486,7 @@ def load_deck_dash() -> None: # pylint: disable=too-many-statements "line_column": "path_json", "line_type": "json", "row_limit": 5000, - "mapbox_style": "https://tile.openstreetmap.org/{z}/{x}/{y}.png", + "mapbox_style": "mapbox://styles/mapbox/light-v9", "viewport": { "longitude": -122.18885402582598, "latitude": 37.73671752604488, diff --git a/superset/examples/long_lat.py b/superset/examples/long_lat.py index 4a3da1bd0e6..0cab63be567 100644 --- a/superset/examples/long_lat.py +++ b/superset/examples/long_lat.py @@ -107,18 +107,18 @@ def load_long_lat_data(only_metadata: bool = False, force: bool = False) -> None "granularity_sqla": "day", "since": "2014-01-01", "until": "now", - "viz_type": "osm", + "viz_type": "mapbox", "all_columns_x": "LON", "all_columns_y": "LAT", - "mapbox_style": "https://tile.openstreetmap.org/{z}/{x}/{y}.png", + "mapbox_style": "mapbox://styles/mapbox/light-v9", "all_columns": ["occupancy"], "row_limit": 500000, } logger.debug("Creating a slice") slc = Slice( - slice_name="OSM Long/Lat", - viz_type="osm", + slice_name="Mapbox Long/Lat", + viz_type="mapbox", datasource_type=DatasourceType.TABLE, datasource_id=tbl.id, params=get_slice_json(slice_data), diff --git a/superset/examples/misc_dashboard.py b/superset/examples/misc_dashboard.py index 75ad8147f7f..1e896f8c7ff 100644 --- a/superset/examples/misc_dashboard.py +++ b/superset/examples/misc_dashboard.py @@ -47,7 +47,7 @@ def load_misc_dashboard() -> None: "meta": { "chartId": 3969, "height": 69, - "sliceName": "OSM Long/Lat", + "sliceName": "Mapbox Long/Lat", "uuid": "164efe31-295b-4408-aaa6-2f4bfb58a212", "width": 4 }, diff --git a/superset/views/base.py b/superset/views/base.py index 8eb201d5860..8d315617759 100644 --- a/superset/views/base.py +++ b/superset/views/base.py @@ -368,7 +368,6 @@ def cached_common_bootstrap_data( # pylint: disable=unused-argument "d3_format": conf.get("D3_FORMAT"), "d3_time_format": conf.get("D3_TIME_FORMAT"), "currencies": conf.get("CURRENCIES"), - "deckgl_tiles": conf.get("DECKGL_BASE_MAP"), "feature_flags": get_feature_flags(), "extra_sequential_color_schemes": conf["EXTRA_SEQUENTIAL_COLOR_SCHEMES"], "extra_categorical_color_schemes": conf["EXTRA_CATEGORICAL_COLOR_SCHEMES"],