Compare commits

..

10 Commits

Author SHA1 Message Date
Luccas Mateus de Medeiros Gomes
66710c7324 [storybook][xs] - fix build 2023-07-07 11:20:11 -03:00
Luccas Mateus de Medeiros Gomes
26309f646b [@portaljs/components][m] - map components 2023-07-07 11:14:44 -03:00
João Demenech
5f1d8151f5 [maps,tests][xs]: add default export to OpenLayers component 2023-07-07 10:47:44 -03:00
Luccas Mateus de Medeiros Gomes
188706e02d [openspending][xs] - add map 2023-07-07 10:31:26 -03:00
Luccas Mateus de Medeiros Gomes
5deddeb164 [openspending][xs] - change order drd 2023-07-07 10:20:54 -03:00
Luccas Mateus de Medeiros Gomes
c14372f229 [openspending][xs] - testing 2023-07-07 10:17:22 -03:00
Luccas Mateus de Medeiros Gomes
b9ec9db9e5 [openspending][xs] - testing 2023-07-07 10:14:12 -03:00
Luccas Mateus
a1170e9239 Feature/openlayers map (#967)
* [maps][xl] - working with swc equals to minify

* [maps][xs] - fixing height
2023-07-06 21:02:19 -03:00
João Demenech
299477a717 [openspending, maps][m]: fix build for leaflet map 2023-07-06 17:13:37 -03:00
João Demenech
5e72711629 [components,maps][l]: implements Leaflet map component -- #963 2023-07-03 18:55:43 -03:00
7 changed files with 90 additions and 193 deletions

View File

@@ -15,6 +15,17 @@ helps to bring this data to life. It maps out the city's annual spending across
the education slice has grown noticeably over the years, indicating Frankfurt's intention to prioritize this space.
The city's commitment to education is abundantly clear.
<Map data="https://openlayers.org/data/vector/ecoregions.json" />
<OpenLayers
layers={[
{
url: 'https://openlayers.org/data/vector/ecoregions.json',
name: 'Teste',
},
]}
/>
<VegaLite
spec={{
$schema: 'https://vega.github.io/schema/vega-lite/v5.json',

View File

@@ -1,11 +1,5 @@
# @portaljs/components
## 0.2.0
### Minor Changes
- [`80c6221a`](https://github.com/datopian/portaljs/commit/80c6221a05733f8c1dd0431bed4d72b1f9d7d636) Thanks [@luccasmmg](https://github.com/luccasmmg)! - Added the OpenLayers and the Map(Leaflet based) component
## 0.1.12
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@portaljs/components",
"version": "0.2.0",
"version": "0.1.12",
"type": "module",
"description": "https://portaljs.org",
"keywords": [

View File

@@ -6,86 +6,93 @@ import {
MapContainer,
TileLayer,
GeoJSON as GeoJSONLayer,
LayersControl
} from 'react-leaflet';
import * as L from 'leaflet';
export type MapProps = {
layers: {
data: string | GeoJSON.GeoJSON;
name: string;
title?: string;
colorScale?: {
starting: string;
ending: string;
};
tooltip?:
| {
propNames: string[];
}
| boolean;
_id?: number;
}[];
title?: string;
center?: { latitude: number | undefined; longitude: number | undefined };
zoom?: number;
tooltip?: {
prop: string;
};
};
export function Map({
layers = [
{
data: null,
name: null,
colorScale: { starting: 'blue', ending: 'red' },
tooltip: true,
},
],
data,
title = '',
colorScale = { starting: 'blue', ending: 'red' },
center = { latitude: 45, longitude: 45 },
zoom = 2,
title = '',
tooltip = {
prop: '',
},
}: MapProps) {
const [isLoading, setIsLoading] = useState<boolean>(false);
const [layersData, setLayersData] = useState<any>([]);
// By default, assumes data is an Array...
const [geoJsonData, setGeoJsonData] = useState<any>(null);
useEffect(() => {
const loadDataPromises = layers.map(async (layer) => {
let layerData: any;
if (typeof layer.data === 'string') {
// If "data" is string, assume it's a URL
// If data is string, assume it's a URL
if (typeof data === 'string') {
setIsLoading(true);
layerData = await loadData(layer.data).then((res: any) => {
return JSON.parse(res);
});
} else {
// Else, expect raw GeoJSON
layerData = layer.data;
}
if (layer.colorScale) {
loadData(data).then((res: any) => {
const geoJsonObject = JSON.parse(res);
const colorScaleAr = chroma
.scale([layer.colorScale.starting, layer.colorScale.ending])
.scale([colorScale.starting, colorScale.ending])
.mode('lch')
.colors(layerData.features.length);
.colors(geoJsonObject.features.length);
layerData.features.forEach((feature, i) => {
// Only style if the feature doesn't have a color prop
geoJsonObject.features.forEach((feature, i) => {
if (feature.color === undefined) {
feature.color = colorScaleAr[i];
}
});
}
return { name: layer.name, data: layerData };
});
Promise.all(loadDataPromises).then((values) => {
setLayersData(values);
setGeoJsonData(geoJsonObject);
setIsLoading(false);
});
} else {
setGeoJsonData(data);
}
}, []);
return isLoading ? (
const onEachFeature = (feature, layer) => {
const geometryType = feature.type;
if (tooltip.prop)
layer.bindTooltip(feature.properties[tooltip.prop], {
direction: 'center',
});
layer.on({
mouseover: (event) => {
if (['Polygon', 'MultiPolygon'].includes(geometryType)) {
event.target.setStyle({
fillColor: '#B85042',
});
}
},
mouseout: (event) => {
if (['Polygon', 'MultiPolygon'].includes(geometryType)) {
event.target.setStyle({
fillColor: '#A7BEAE',
});
}
},
});
};
return isLoading || !geoJsonData ? (
<div className="w-full flex items-center justify-center w-[600px] h-[300px]">
<LoadingSpinner />
</div>
@@ -97,10 +104,8 @@ export function Map({
className="h-80 w-full"
// @ts-ignore
whenReady={(map: any) => {
// Enable zoom using scroll wheel
map.target.scrollWheelZoom.enable();
// Create the title box
var info = new L.Control() as any;
info.onAdd = function () {
@@ -114,98 +119,21 @@ export function Map({
};
if (title) info.addTo(map.target);
setTimeout(() => map.target.invalidateSize(), 5000);
}}
>
<GeoJSONLayer
data={geoJsonData}
style={(geoJsonFeature: any) => {
return { color: geoJsonFeature?.color };
}}
onEachFeature={onEachFeature}
/>
<TileLayer
attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
<LayersControl position="bottomright">
{layers.map((layer) => {
const data = layersData.find(
(layerData) => layerData.name === layer.name
)?.data;
return (
data && (
<LayersControl.Overlay key={layer.name} checked name={layer.name}>
<GeoJSONLayer
data={data}
style={(geoJsonFeature: any) => {
// Set the fill color of each feature when appliable
if (
!['Point', 'MultiPoint'].includes(geoJsonFeature.type)
) {
return { color: geoJsonFeature?.color };
}
}}
eventHandlers={{
add: (e) => {
const featureGroup = e.target;
const tooltip = layer.tooltip;
featureGroup.eachLayer((featureLayer) => {
const feature = featureLayer.feature;
const geometryType = feature.geometry.type;
if (tooltip) {
const featurePropNames = Object.keys(
feature.properties
);
let includedFeaturePropNames;
if (tooltip === true) {
includedFeaturePropNames = featurePropNames;
} else {
includedFeaturePropNames = tooltip.propNames.filter(
(name) => featurePropNames.includes(name)
);
}
if (includedFeaturePropNames) {
const tooltipContent = includedFeaturePropNames
.map(
(name) =>
`<b>${name}:</b> ${feature.properties[name]}`
)
.join('<br />');
featureLayer.bindTooltip(tooltipContent, {
direction: 'center',
});
}
}
featureLayer.on({
mouseover: (event) => {
if (
['Polygon', 'MultiPolygon'].includes(geometryType)
) {
event.target.setStyle({
fillOpacity: 0.5,
});
}
},
mouseout: (event) => {
if (
['Polygon', 'MultiPolygon'].includes(geometryType)
) {
event.target.setStyle({
fillOpacity: 0.2,
});
}
},
});
});
},
}}
/>
;
</LayersControl.Overlay>
)
);
})}
</LayersControl>
</MapContainer>
);
}

View File

@@ -8,7 +8,7 @@ const meta: Meta = {
component: Map,
tags: ['autodocs'],
argTypes: {
layers: {
data: {
description:
'Data to be displayed.\n\n GeoJSON Object \n\nOR\n\n URL to GeoJSON Object',
},
@@ -21,6 +21,9 @@ const meta: Meta = {
zoom: {
description: 'Zoom level',
},
tooltip: {
description: 'Tooltip settings'
}
},
};
@@ -32,60 +35,21 @@ type Story = StoryObj<MapProps>;
export const GeoJSONPolygons: Story = {
name: 'GeoJSON polygons map',
args: {
layers: [
{
data: 'https://d2ad6b4ur7yvpq.cloudfront.net/naturalearth-3.3.0/ne_10m_geography_marine_polys.geojson',
name: 'Polygons',
tooltip: { propNames: ['name'] },
colorScale: {
starting: '#ff0000',
ending: '#00ff00',
},
},
],
title: 'Seas and Oceans Map',
center: { latitude: 45, longitude: 0 },
zoom: 2,
tooltip: { prop: 'name' },
},
};
export const GeoJSONPoints: Story = {
name: 'GeoJSON points map',
args: {
layers: [
{
data: 'https://opendata.arcgis.com/datasets/9c58741995174fbcb017cf46c8a42f4b_25.geojson',
name: 'Points',
tooltip: { propNames: ['Location'] },
},
],
title: 'Roads in York',
center: { latitude: 53.9614, longitude: -1.0739 },
zoom: 12,
},
};
export const GeoJSONMultipleLayers: Story = {
name: 'GeoJSON polygons and points map',
args: {
layers: [
{
data: 'https://opendata.arcgis.com/datasets/9c58741995174fbcb017cf46c8a42f4b_25.geojson',
name: 'Points',
tooltip: true,
},
{
data: 'https://d2ad6b4ur7yvpq.cloudfront.net/naturalearth-3.3.0/ne_10m_geography_marine_polys.geojson',
name: 'Polygons',
tooltip: true,
colorScale: {
starting: '#ff0000',
ending: '#00ff00',
},
},
],
title: 'Polygons and points',
center: { latitude: 45, longitude: 0 },
zoom: 2,
tooltip: { prop: 'Location' },
},
};

View File

@@ -1,4 +1,4 @@
import react from '@vitejs/plugin-react';
import react from '@vitejs/plugin-react-swc';
import path from 'node:path';
import { defineConfig } from 'vitest/config';
import dts from 'vite-plugin-dts';

View File

@@ -63,7 +63,7 @@ Congratulations, your new app is now running at http://localhost:3000.
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fdatopian%2Fportaljs%2Ftree%2Fmain%2Fexamples%2Fgithub-backed-catalog)
By clicking on this button, you will be redirected to a page which will allow you to clone the example into your own GitHub/GitLab/BitBucket account and automatically deploy it.
By clicking on this button, you will be redirected to a page which will allows you to clone the example into your own GitHub/GitLab/BitBucket account and automatically deploy it.
### GitHub token
@@ -101,7 +101,7 @@ It has
You can also add
- A `description` which is useful if you have more than one dataset for each repo, if not provided we are just going to use the repo description
- A `Name` which is useful if you want to give your dataset a nice name, if not provided we are going to use the junction of the `owner` the `repo` + the path of the README, in the example above it will be `fivethirtyeight/data/nba-raptor`
- A `Name` which is useful if you want to give your dataset a nice name, if not provided we are going to use the junction of the `owner` the `repo` + the path of the README, in the exaple above it will be `fivethirtyeight/data/nba-raptor`
## Extra commands