Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/49 support cluster maps #50

Merged
merged 34 commits into from
Nov 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
e36394d
[#49] Install leaflet.markercluster
ifirmawan Nov 18, 2024
4844d3e
[#49] Set maxZoom in LeafletProvider
ifirmawan Nov 18, 2024
14566cd
[#49] Add clusterLayer in MapView
ifirmawan Nov 18, 2024
d2383e3
[#49] Create MarkerIcon
ifirmawan Nov 18, 2024
a32c1f6
[#49] Create MarkerClusterGroup wrapper component
ifirmawan Nov 18, 2024
f6c67bf
[#49] Replace useEffect w/ useLayoutEffect in LeafletProvider
ifirmawan Nov 19, 2024
55255ba
[#49] Remove clusterGroup & update test in MapView
ifirmawan Nov 19, 2024
8094f87
[#49] Add new Map Utils - getGeoJSONList
ifirmawan Nov 19, 2024
b52117a
[#49] Add bindPopup in MarkerClusterGroup
ifirmawan Nov 19, 2024
b01443c
[#49] Add View and export all Map components
ifirmawan Nov 19, 2024
41b1281
[#49] Add new menu: Cluster Map in example page
ifirmawan Nov 19, 2024
4780c5a
[#49] Add new mapHelper: renderReactToDiv
ifirmawan Nov 19, 2024
ae5d0bb
[#49] Create fnMarker & handle MarkerIcon props
ifirmawan Nov 19, 2024
3b0640c
[#49] Implement fnMarker in Marker component
ifirmawan Nov 19, 2024
7d1f5af
[#49] Implement fnMarker in MarkerClusterGroup & fix some options
ifirmawan Nov 19, 2024
6582ebb
[#49] Create clusterExampleData for Cluster map
ifirmawan Nov 19, 2024
0358c5d
[#49] Finalizing basic example of Cluster Map
ifirmawan Nov 19, 2024
20274aa
[#49] Create MapCluster component for default cluster map
ifirmawan Nov 20, 2024
333b7b9
[#49] Fix & polishing Marker & MarkerClusterGroup component
ifirmawan Nov 20, 2024
e347214
[#49] Create basic test: MapCluster
ifirmawan Nov 20, 2024
8d25abb
[#49] Update dist to add MapCluster & apply all Map component changes
ifirmawan Nov 20, 2024
c9f7fd2
[#49] Change enum value: CLUSTER_MAP
ifirmawan Nov 20, 2024
16a200b
[#49] Add new global state: customMap & defaultMapConfig
ifirmawan Nov 20, 2024
086bbe4
[#49] Implement customMap in JsonDataEditor
ifirmawan Nov 20, 2024
f24c4a2
[#49] Implement customMap in Editor
ifirmawan Nov 20, 2024
967d6ba
[#49] Simplifying code-block & set type as required param
ifirmawan Nov 20, 2024
0b43367
[#49] Remove chartDispatch for clusterMap
ifirmawan Nov 20, 2024
4228e8d
[#49] Replace custom Map w/ MapCluster in ChartDisplay
ifirmawan Nov 20, 2024
27bb8c2
[#49] Rename View to Container for Map component
ifirmawan Nov 20, 2024
edbae7c
[#49] Update dist to rename Map.View w/ Map.Container
ifirmawan Nov 20, 2024
9ee19cf
[#49] Add new global state: reRender
ifirmawan Nov 20, 2024
493909b
[#49] Implement reRender in ChartDisplay when option & fullScreen sta…
ifirmawan Nov 20, 2024
a7e671c
[#49] Update string attributes in code-block
ifirmawan Nov 20, 2024
fb91c83
[#49] Update documentation for MapCluster & other Map components
ifirmawan Nov 20, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
286 changes: 277 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,23 @@ The `akvo-charts` library allows you to create a variety of charts by leveraging
- [Props](#props-7)
- [Example usage of StackLine chart](#example-usage-of-stackline-chart)
- [MapView](#mapview)
- [Tile](#tile)
- [Layer](#layer)
- [Data](#data-1)
- [Config](#config-1)
- [Example usage of MapView](#example-usage-of-mapview)
- [Example usage with Choropleth Mapping](#example-usage-with-choropleth-mapping)

- [MapCluster](#mapcluster)
- [markerIcon](#markericon)
- [clusterIcon](#clustericon)
- [Additional properties](#additional-properties)
- [Fully Customized Map](#fully-customized-map)
- [Container](#map-container)
- [GeoJson](#map-geojson)
- [LegendControl](#map-legend-control)
- [Marker](#map-marker)
- [MarkerClusterGroup](#map-marker-cluster-group)
- [TileLayer](#map-tile-layer)
- [Utils](#map-utils)
---

## Installation
Expand Down Expand Up @@ -1107,13 +1117,10 @@ export default StackLineChartExample;
The `MapView` component provides an easy way to render a map in your React application using [Leaflet](https://leafletjs.com/). You can customize the map's appearance, add layers, plot points, and handle user interactions such as clicks. The map configuration is passed via the `config`, `data`, `tile`, and `layer` props.


#### tile
<a id="map-config"></a>
#### config


| Prop | Type | Description |
|-------|------|--------------|
| `url` | string | A tile layer URL |
| `maxZoom` | number | The maximum zoom level up to which this layer will be displayed (inclusive). |
| `attribution` | string | Display attribution data in a small text box on a map |

#### layer

Expand Down Expand Up @@ -1275,4 +1282,265 @@ const ChoroPlethExample = () => {
export default ChoroPlethExample;
```

---
### MapCluster
The `MapCluster` component provides an initial implementation for map clustering in Akvo project use cases. It leverages [Container](#map-container), [MarkerClusterGroup](#map-marker-cluster-group), and [Marker](#map-marker) to group and display markers on a map.

#### Props

##### `markerIcon`
Defines the styling and attributes for individual marker icons.
| Prop | Type | Description |
|--------------|--------------------|---------------------------------------------|
| `className` | `string` | CSS class for styling the marker icon. |
| `iconSize` | `[number, number]` | Dimensions of the marker icon `[width, height]`. |
| `html` | `boolean` | If `true`, uses a default HTML circle icon. |

##### `clusterIcon`
Defines the styling and attributes for cluster icons.
| Prop | Type | Description |
|--------------|----------------|---------------------------------------------|
| `className` | `string` | CSS class for styling the cluster icon. |
| `iconSize` | `number` | Size of the cluster icon in pixels. |

##### Additional Properties
| Prop | Type | Description |
|----------------|------------------------------------|-----------------------------------------------------------------------------|
| `groupKey` | `string` | Key used to group data when `type` is set to `"circle"`. |
| `type` | `enum`(default&#124;circle) |Defines the clustering mode: |
| | | - `"default"`: Uses default styles from [Leaflet.markercluster](https://github.com/Leaflet/Leaflet.markercluster). |
| | | - `"circle"`: Uses predefined styles specific to common Akvo project use cases. |
| `renderPopup` | `function` | Function to render a custom child component in the marker popup. |

---

#### Example Usage

```jsx
import React from "react";
import MapCluster from "./MapCluster";

const markerIcon = {
className: "custom-marker",
iconSize: [32, 32],
html: true,
};

const clusterIcon = {
className: "custom-marker-cluster",
iconSize: 60,
};

const tile = {
url: "https://tile.openstreetmap.org/{z}/{x}/{y}.png",
maxZoom: 19,
attribution: "© OpenStreetMap",
};

const config = {
// Additional configuration options for the map can go here.
};

const data = [
// Provide the array of data points for the map here.
];

const Chart = () => {
return (
<div>
<MapCluster
tile={tile}
config={config}
data={data}
markerIcon={markerIcon}
clusterIcon={clusterIcon}
groupKey={"serviceLevel"}
type={"circle"}
/>
</div>
);
};

export default Chart;
```

#### MapCluster Notes
- When using `"default"` for `type`, the component applies styles from [Leaflet.markercluster](https://github.com/Leaflet/Leaflet.markercluster).
- When using `"circle"` for `type`, the component applies Akvo-specific predefined styles.
- Ensure the `data` prop is formatted correctly to include the required latitude and longitude coordinates for each marker.
- Customize `renderPopup` to display additional information for markers as needed.


---


### Fully Customized Map
If the [MapView](#mapview) or [MapCluster](#mapcluster) components do not meet your specific requirements, you can use this fully customizable map component to create a tailored solution.


### Components

#### <a id="map-container"></a>Container
The `Container` component serves as the base for rendering a map.

| Prop | Type | Description |
|--------------|----------------|-----------------------------------------------|
| `children` | `node` | Valid map or React components to be rendered inside the container. |
| `tile` | `object` | Accepts all valid [Tile Layer options](#map-tile-layer). |
| `config` | `object` | Accepts all valid [config options](#map-config). |

---

#### <a id="map-geojson"></a>GeoJson
The `GeoJson` component allows you to render GeoJSON data as layers on the map.

| Prop | Type | Description |
|--------------|----------------|-----------------------------------------------|
| `onClick` | `function` | Event handler for click events on the layer. |
| `onMouseOver`| `function` | Event handler for mouseover events on the layer. |
| `data` | `object` | Accepts valid [GeoJSON](https://en.wikipedia.org/wiki/GeoJSON) objects. |
| `style` | `object` | Styles the layer, e.g., for choropleth maps, using React inline CSS format. |

---

#### <a id="map-legend-control"></a>LegendControl
The `LegendControl` component creates a legend control based on color ranges.

| Prop | Type | Description |
|--------------|----------------|-----------------------------------------------|
| `data` | `array` | Single-dimension array defining legend data. |
| `color` | `array` | Array of colors corresponding to the data ranges. |

---

#### <a id="map-marker"></a>Marker
The `Marker` component is used to display individual points on the map.

| Prop | Type | Description |
|--------------|----------------|-----------------------------------------------|
| `children` | `node` | Valid React components to display in the marker popup. |
| `latlng` | `[number, number]` | Latitude and longitude of the marker `[latitude, longitude]`. |
| `label` | `string` | Label or title for the marker. |

It also supports all [Marker options](https://leafletjs.com/reference.html#marker-option).

---

#### <a id="map-marker-cluster-group"></a>MarkerClusterGroup
The `MarkerClusterGroup` component groups multiple markers into clusters.

| Prop | Type | Description |
|-----------------|----------------|-----------------------------------------------|
| `children` | `node` | Valid React components to be clustered. |
| `iconCreateFn` | `function` | Function to customize the cluster icon. |
| `onClick` | `function` | Event handler for click events on the cluster. |
| `onMarkerClick` | `function` | Event handler for click events on individual markers. |

It supports all [Leaflet.markercluster options](https://github.com/Leaflet/Leaflet.markercluster#all-options).

---

#### <a id="map-tile-layer"></a>TileLayer
The `TileLayer` component is used to display map tiles.

| Prop | Type | Description |
|--------------|----------------|-----------------------------------------------|
| `url` | `string` | The URL template for the tile layer. |
| `maxZoom` | `number` | The maximum zoom level for the layer. |
| `attribution`| `string` | Attribution text to display on the map. |

It supports all [TileLayer options](https://leafletjs.com/reference.html#tilelayer-option).

---

#### <a id="map-utils"></a>Utils

* `getGeoJSONList`: This utility function converts TopoJSON data to GeoJSON using the [topojson-client library](https://www.npmjs.com/package/topojson-client).

---

#### Example Usage

```jsx
import { Map } from 'akvo-charts';

const { getGeoJSONList } = Map;

const DisplayMap = () => {
const mapConfig = {
config: {
center: [-6.2, 106.816666],
zoom: 8,
height: "100vh",
width: "100%",
},
};

const iconCreateFn = (cluster) => (
<div className="custom-cluster-icon">
{cluster.getChildCount()}
</div>
);

const geoProps = {
style: {
fillColor: "#f28c28",
weight: 2,
opacity: 1,
color: "white",
fillOpacity: 0.7,
},
onClick: (e) => console.log("Layer clicked!", e),
};
const data = [
// Provide the array of data points for the map here.
];
const geoData = {
// Provide the object of geojson data for the map here.
}

return (
<Map.Container {...mapConfig}>
{getGeoJSONList(geoData).map((gd, gx) => (
<Map.GeoJson key={gx} data={gd} {...geoProps} />
))}
<Map.MarkerClusterGroup iconCreateFn={iconCreateFn}>
{data
?.filter((d) => d?.point)
?.map((d, dx) => (
<Map.Marker
key={dx}
latlng={d.point}
icon={{
className: 'custom-marker',
iconSize: [32, 32],
html: `<span style="background-color:${d.color}; border:2px solid #fff;" />`,
}}
>
<ul className="w-full text-base space-y-1">
<li>
<strong>School: </strong>
<span>{d.label}</span>
</li>
<li>
<strong>Service Level: </strong>
<span>{d.serviceLevel}</span>
</li>
</ul>
</Map.Marker>
))}
</Map.MarkerClusterGroup>
</Map.Container>
);
};

export default DisplayMap;
```

#### Map components Notes
- Customize `iconCreateFn` to define your cluster icons dynamically.
- Use `getGeoJSONList` to handle TopoJSON conversions and simplify rendering GeoJSON layers.
- Always define `style` properties for `GeoJson` to ensure proper rendering of layers.

---

[Back to top](#akvo-charts)
Loading
Loading