mirror of
https://github.com/tabler/tabler.git
synced 2026-10-13 13:47:00 +04:00
Restyle the maps after mapcn and add a 3D globe demo
This commit is contained in:
1 parent
9af4173f86
commit
9fae876516
9 files changed
+243
-86
No files matched your search
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@tabler/core": minor
|
||||
"@tabler/preview": minor
|
||||
"@tabler/docs": patch
|
||||
---
|
||||
|
||||
Updated the map look: gray basemap, dot markers, popups with no arrow, `.map-tooltip`, `.map-marker-label`, and a 3D globe demo.
|
||||
@@ -1281,7 +1281,7 @@ $ribbon-bookmark-notch-width: 0.5rem !default;
|
||||
|
||||
// scss-docs-start map-marker-variables
|
||||
$map-marker-size: 1.75rem !default;
|
||||
$map-marker-dot-size: 0.875rem !default;
|
||||
$map-marker-dot-size: 1rem !default;
|
||||
$map-marker-bg: var(--primary) !default;
|
||||
$map-marker-color: var(--primary-fg) !default;
|
||||
$map-marker-border-width: 2px !default;
|
||||
|
||||
@@ -52,6 +52,7 @@ exports[`css custom-property prefixing > leaves vendor-owned names alone and pre
|
||||
"--tblr-border-color-translucent",
|
||||
"--tblr-border-radius",
|
||||
"--tblr-border-radius-pill",
|
||||
"--tblr-border-radius-sm",
|
||||
"--tblr-border-style",
|
||||
"--tblr-border-width",
|
||||
"--tblr-card-bg",
|
||||
|
||||
Vendored
+40
-33
@@ -65,9 +65,9 @@
|
||||
border-color: var(--body-color);
|
||||
}
|
||||
|
||||
// Popup
|
||||
.maplibregl-popup-content {
|
||||
padding: 0.75rem 1rem;
|
||||
// Popup: a small bordered panel with no arrow
|
||||
.maplibregl-popup .maplibregl-popup-content {
|
||||
padding: 0.75rem;
|
||||
color: var(--body-color);
|
||||
background: var(--bg-surface);
|
||||
box-shadow:
|
||||
@@ -76,21 +76,25 @@
|
||||
@include border-radius(var(--border-radius));
|
||||
}
|
||||
|
||||
.maplibregl-popup-tip {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// Room for the close button, which MapLibre pins to the right edge in both directions
|
||||
.maplibregl-popup-content:has(.maplibregl-popup-close-button) {
|
||||
.maplibregl-popup .maplibregl-popup-content:has(.maplibregl-popup-close-button) {
|
||||
/* rtl:ignore */
|
||||
padding-right: 2.25rem;
|
||||
padding-right: 2rem;
|
||||
}
|
||||
|
||||
.maplibregl-popup-close-button {
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
padding: 0;
|
||||
margin: 0.375rem;
|
||||
margin: 0.25rem;
|
||||
font-size: 1rem;
|
||||
line-height: 1;
|
||||
color: var(--secondary);
|
||||
@include border-radius(var(--border-radius));
|
||||
@include border-radius(var(--border-radius-sm));
|
||||
|
||||
&:hover {
|
||||
color: var(--body-color);
|
||||
@@ -98,32 +102,17 @@
|
||||
}
|
||||
}
|
||||
|
||||
.maplibregl-popup-anchor-top,
|
||||
.maplibregl-popup-anchor-top-left,
|
||||
.maplibregl-popup-anchor-top-right {
|
||||
.maplibregl-popup-tip {
|
||||
border-bottom-color: var(--bg-surface);
|
||||
}
|
||||
}
|
||||
|
||||
.maplibregl-popup-anchor-bottom,
|
||||
.maplibregl-popup-anchor-bottom-left,
|
||||
.maplibregl-popup-anchor-bottom-right {
|
||||
.maplibregl-popup-tip {
|
||||
border-top-color: var(--bg-surface);
|
||||
}
|
||||
}
|
||||
|
||||
.maplibregl-popup-anchor-left .maplibregl-popup-tip {
|
||||
border-right-color: var(--bg-surface);
|
||||
}
|
||||
|
||||
.maplibregl-popup-anchor-right .maplibregl-popup-tip {
|
||||
border-left-color: var(--bg-surface);
|
||||
// Popup used as a tooltip: a short inverted label, shown on hover
|
||||
.map-tooltip .maplibregl-popup-content {
|
||||
padding: 0.25rem 0.5rem;
|
||||
color: var(--text-inverted);
|
||||
pointer-events: none;
|
||||
background: var(--bg-surface-inverted);
|
||||
box-shadow: #{$box-shadow-md};
|
||||
}
|
||||
|
||||
// Popup that holds a card-like block: an image on top, padded content below
|
||||
.map-popup-card {
|
||||
.map-popup-card.maplibregl-popup {
|
||||
.maplibregl-popup-content {
|
||||
width: #{$map-popup-card-width};
|
||||
padding: 0;
|
||||
@@ -161,7 +150,6 @@
|
||||
line-height: 1;
|
||||
color: var(--map-marker-color);
|
||||
cursor: pointer;
|
||||
filter: #{$map-marker-shadow};
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
@@ -169,6 +157,7 @@
|
||||
z-index: -1;
|
||||
content: '';
|
||||
background: var(--map-marker-bg);
|
||||
filter: #{$map-marker-shadow};
|
||||
border: var(--map-marker-border-width) solid var(--map-marker-border-color);
|
||||
@include border-radius(var(--border-radius-pill));
|
||||
}
|
||||
@@ -187,6 +176,24 @@
|
||||
--map-marker-size: #{$map-marker-dot-size};
|
||||
}
|
||||
|
||||
// Short text under a marker
|
||||
.map-marker-label {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 50%;
|
||||
margin-top: 0.125rem;
|
||||
font-size: $h6-font-size;
|
||||
font-weight: var(--font-weight-medium);
|
||||
line-height: 1.2;
|
||||
color: var(--body-color);
|
||||
text-shadow:
|
||||
0 0 2px var(--bg-surface),
|
||||
0 0 2px var(--bg-surface),
|
||||
0 0 4px var(--bg-surface);
|
||||
white-space: nowrap;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
// Teardrop: a square with one sharp corner, turned to point down. Its tip
|
||||
// reaches 20.7% below the circle, so the root is that much taller.
|
||||
.map-marker-pin {
|
||||
|
||||
@@ -12,6 +12,9 @@ classnames:
|
||||
desc: Wrapper that keeps the map at a fixed aspect ratio
|
||||
- class: map-marker
|
||||
desc: Marker drawn in HTML, a circle that can hold an icon, a count or an avatar
|
||||
part:
|
||||
- class: map-marker-label
|
||||
desc: Short text under a marker
|
||||
modifier:
|
||||
- class: map-marker-dot
|
||||
desc: Small dot with no content
|
||||
@@ -19,6 +22,8 @@ classnames:
|
||||
desc: Teardrop pin that points down, used with the bottom anchor
|
||||
- class: map-popup-card
|
||||
desc: Popup with no padding and a fixed width, for an image above the text
|
||||
- class: map-tooltip
|
||||
desc: Popup shown as a small dark label, for text on hover
|
||||
color:
|
||||
- class: map-marker-{color}
|
||||
desc: Any theme color, for example map-marker-red or map-marker-green
|
||||
@@ -83,7 +88,7 @@ Create the map after the DOM is ready. Pass the id of your container, a style, a
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const map = new maplibregl.Map({
|
||||
container: 'map-simple',
|
||||
style: 'https://tiles.openfreemap.org/styles/liberty',
|
||||
style: 'https://tiles.openfreemap.org/styles/positron',
|
||||
zoom: 13,
|
||||
center: [13.4049, 52.518827],
|
||||
})
|
||||
@@ -102,10 +107,10 @@ Set `style` to the URL of any MapLibre style. OpenFreeMap has five of them:
|
||||
|
||||
| Style | Result |
|
||||
| --- | --- |
|
||||
| `https://tiles.openfreemap.org/styles/liberty` | Default street map. |
|
||||
| `https://tiles.openfreemap.org/styles/positron` | Light gray map, used by the Tabler demos. Markers and data stand out on it. |
|
||||
| `https://tiles.openfreemap.org/styles/dark` | Dark gray map, used by the demos in dark mode. |
|
||||
| `https://tiles.openfreemap.org/styles/liberty` | Street map in color. |
|
||||
| `https://tiles.openfreemap.org/styles/bright` | Street map with stronger colors. |
|
||||
| `https://tiles.openfreemap.org/styles/positron` | Light gray map, good under charts and markers. |
|
||||
| `https://tiles.openfreemap.org/styles/dark` | Dark map. |
|
||||
| `https://tiles.openfreemap.org/styles/fiord` | Dark blue map. |
|
||||
|
||||
<Example>
|
||||
@@ -116,7 +121,7 @@ Styles from other providers work the same way, as long as they follow the MapLib
|
||||
|
||||
### Markers
|
||||
|
||||
Add a marker for each place. `setLngLat` takes the same longitude and latitude order as `center`.
|
||||
Add a marker for each place. `setLngLat` takes the same longitude and latitude order as `center`. The demos use a small dot, drawn with the `.map-marker` and `.map-marker-dot` classes.
|
||||
|
||||
```js
|
||||
const cities = [
|
||||
@@ -126,7 +131,10 @@ const cities = [
|
||||
]
|
||||
|
||||
cities.forEach(function (coords) {
|
||||
new maplibregl.Marker({ color: 'var(--tblr-primary)' }).setLngLat(coords).addTo(map)
|
||||
const element = document.createElement('span')
|
||||
element.className = 'map-marker map-marker-dot'
|
||||
|
||||
new maplibregl.Marker({ element: element }).setLngLat(coords).addTo(map)
|
||||
})
|
||||
```
|
||||
|
||||
@@ -134,7 +142,7 @@ cities.forEach(function (coords) {
|
||||
<Map mapId="markers" />
|
||||
</Example>
|
||||
|
||||
The `color` option is written to the `fill` attribute of the marker icon. A CSS variable works there, so `var(--tblr-primary)` keeps the markers in your theme color, including after a theme change.
|
||||
Without `element`, MapLibre draws its own pin. Its `color` option accepts a CSS variable, for example `var(--tblr-primary)`.
|
||||
|
||||
### Custom markers
|
||||
|
||||
@@ -148,6 +156,8 @@ element.textContent = '12'
|
||||
new maplibregl.Marker({ element: element }).setLngLat([13.35, 52.512]).addTo(map)
|
||||
```
|
||||
|
||||
Put a `.map-marker-label` element inside the marker for a short text under it.
|
||||
|
||||
Add `.map-marker-dot` for a small dot, or `.map-marker-pin` for a pin that points down. A pin needs `anchor: 'bottom'`, so its tip sits on the place. Change the color with `.map-marker-{color}`.
|
||||
|
||||
```js
|
||||
@@ -161,7 +171,7 @@ new maplibregl.Marker({ element: pin, anchor: 'bottom' }).setLngLat([13.47, 52.5
|
||||
<Map mapId="marker-styles" title="Marker styles" center={[52.522, 13.405]} zoom={11}>
|
||||
<div data-lng="13.345" data-lat="52.54"><span class="map-marker map-marker-dot"></span></div>
|
||||
<div data-lng="13.39" data-lat="52.545"><span class="map-marker map-marker-dot map-marker-red"></span></div>
|
||||
<div data-lng="13.445" data-lat="52.538"><span class="map-marker"><Icon name="building-store" /></span></div>
|
||||
<div data-lng="13.445" data-lat="52.538"><span class="map-marker"><Icon name="building-store" /><span class="map-marker-label">Store</span></span></div>
|
||||
<div data-lng="13.35" data-lat="52.512"><span class="map-marker map-marker-green">12</span></div>
|
||||
<div data-lng="13.405" data-lat="52.522" data-anchor="bottom"><span class="map-marker map-marker-pin"><Icon name="home" /></span></div>
|
||||
<div data-lng="13.47" data-lat="52.518" data-anchor="bottom"><span class="map-marker map-marker-pin map-marker-red"><Icon name="star" /></span></div>
|
||||
@@ -201,15 +211,32 @@ marker.on('dragend', function () {
|
||||
|
||||
### Popups
|
||||
|
||||
Attach a popup to a marker with `setPopup`. It opens when the marker is clicked. Click a marker on the map above to see one.
|
||||
Attach a popup to a marker with `setPopup`. It opens when the marker is clicked. Click a marker on the map above to see one. Tabler draws the popup as a small bordered panel with no arrow.
|
||||
|
||||
```js
|
||||
new maplibregl.Marker({ color: 'var(--tblr-primary)' })
|
||||
new maplibregl.Marker({ element: element })
|
||||
.setLngLat([16.366667, 48.2])
|
||||
.setPopup(new maplibregl.Popup({ offset: 32 }).setText('Vienna'))
|
||||
.setPopup(new maplibregl.Popup({ offset: 16, closeButton: false }).setText('Vienna'))
|
||||
.addTo(map)
|
||||
```
|
||||
|
||||
Leave out `closeButton: false` to show a close button in the corner. The popup also closes on a click on the map.
|
||||
|
||||
### Tooltips
|
||||
|
||||
For a short label on hover, add the `map-tooltip` class to a popup. It turns the popup into a small dark label.
|
||||
|
||||
```js
|
||||
const tooltip = new maplibregl.Popup({ closeButton: false, offset: 12, className: 'map-tooltip' })
|
||||
|
||||
map.on('mousemove', 'cities', function (event) {
|
||||
tooltip.setLngLat(event.lngLat).setText(event.features[0].properties.name).addTo(map)
|
||||
})
|
||||
map.on('mouseleave', 'cities', function () {
|
||||
tooltip.remove()
|
||||
})
|
||||
```
|
||||
|
||||
Use `setHTML` instead of `setText` for richer content. Only pass HTML you trust, as it is not escaped.
|
||||
|
||||
### Popup card
|
||||
@@ -267,7 +294,7 @@ A map style does not change with the page theme on its own. Pick the style from
|
||||
```js
|
||||
const themeStyle = function () {
|
||||
const dark = document.documentElement.getAttribute('data-bs-theme') === 'dark'
|
||||
return 'https://tiles.openfreemap.org/styles/' + (dark ? 'dark' : 'liberty')
|
||||
return 'https://tiles.openfreemap.org/styles/' + (dark ? 'dark' : 'positron')
|
||||
}
|
||||
|
||||
const map = new maplibregl.Map({
|
||||
@@ -299,7 +326,7 @@ map.on('style.load', function () {
|
||||
type: 'line',
|
||||
source: 'route',
|
||||
layout: { 'line-cap': 'round', 'line-join': 'round' },
|
||||
paint: { 'line-color': '#066fd1', 'line-width': 4 },
|
||||
paint: { 'line-color': '#066fd1', 'line-width': 3, 'line-opacity': 0.8 },
|
||||
})
|
||||
})
|
||||
```
|
||||
@@ -330,7 +357,7 @@ function mapColor(name) {
|
||||
return 'rgb(' + pixel[0] + ', ' + pixel[1] + ', ' + pixel[2] + ')'
|
||||
}
|
||||
|
||||
map.addLayer({ id: 'route', type: 'line', source: 'route', paint: { 'line-color': mapColor('--tblr-primary'), 'line-width': 4 } })
|
||||
map.addLayer({ id: 'route', type: 'line', source: 'route', paint: { 'line-color': mapColor('--tblr-primary'), 'line-width': 3 } })
|
||||
```
|
||||
|
||||
Call it inside `style.load`, so the colors are read again after a theme change.
|
||||
@@ -378,12 +405,40 @@ map.addLayer({
|
||||
source: 'cities',
|
||||
paint: {
|
||||
'circle-color': '#066fd1',
|
||||
'circle-opacity': 0.4,
|
||||
'circle-radius': ['interpolate', ['linear'], ['get', 'users'], 0, 4, 1600, 28],
|
||||
'circle-opacity': 0.6,
|
||||
'circle-radius': ['interpolate', ['linear'], ['get', 'users'], 0, 4, 1600, 24],
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
### Content over the map
|
||||
|
||||
To put buttons or a small card over the map, place them next to the map container and position them with the utility classes:
|
||||
|
||||
```html
|
||||
<div class="ratio ratio-16x9">
|
||||
<div>
|
||||
<div id="map-cities" class="w-100 h-100 rounded"></div>
|
||||
<div class="btn-list position-absolute top-0 start-0 m-2">
|
||||
<button type="button" class="btn btn-sm btn-pill btn-primary">Berlin</button>
|
||||
<button type="button" class="btn btn-sm btn-pill">Tokyo</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Globe
|
||||
|
||||
MapLibre can draw the map as a 3D globe. Set the projection in the `style.load` event, as it is part of the map style. People can turn the globe by dragging it.
|
||||
|
||||
```js
|
||||
map.on('style.load', function () {
|
||||
map.setProjection({ type: 'globe' })
|
||||
})
|
||||
```
|
||||
|
||||
Use a low zoom, between 1 and 2, to see the whole globe. To connect two places with an arc, draw a line layer through points on the shortest path between them. The [maps preview page](https://preview.tabler.io/maps.html) has a short function that returns these points.
|
||||
|
||||
### Moving the map
|
||||
|
||||
Move to another place with `flyTo`, for example from a row of buttons:
|
||||
|
||||
@@ -12,7 +12,7 @@ import CaptureScript from '@shared/components/CaptureScript.astro'
|
||||
window.tabler_map ??= {}
|
||||
|
||||
function initMap() {
|
||||
const themeStyle = () => `https://tiles.openfreemap.org/styles/${document.documentElement.getAttribute('data-bs-theme') === 'dark' ? 'dark' : 'liberty'}`
|
||||
const themeStyle = () => `https://tiles.openfreemap.org/styles/${document.documentElement.getAttribute('data-bs-theme') === 'dark' ? 'dark' : 'positron'}`
|
||||
|
||||
const map = new maplibregl.Map({
|
||||
container: 'map-fullsize',
|
||||
|
||||
+102
-23
@@ -61,6 +61,14 @@ const storeCities: [number, number][] = [
|
||||
]
|
||||
const stores = storeCities.flatMap(([lng, lat], city) => Array.from({ length: 5 + (city % 4) }, (_, index) => [lng + Math.sin(city * 7 + index * 3) * 0.6, lat + Math.cos(city * 5 + index * 2) * 0.4]))
|
||||
|
||||
const hub = { name: 'London', lng: -0.1276, lat: 51.5072 }
|
||||
const destinations = [
|
||||
{ name: 'Tokyo', lng: 139.6917, lat: 35.6895 },
|
||||
{ name: 'Mumbai', lng: 72.8777, lat: 19.076 },
|
||||
{ name: 'Singapore', lng: 103.8198, lat: 1.3521 },
|
||||
{ name: 'Nairobi', lng: 36.8219, lat: -1.2921 },
|
||||
]
|
||||
|
||||
const places = [
|
||||
{ name: 'Museum Island', text: 'Five museums on the Spree, open daily.', photo: 'a-visit-to-the-bookstore.jpg', lat: 52.5169, lng: 13.4019, open: true },
|
||||
{ name: 'Bellevue Palace', text: 'Residence by the Tiergarten park.', photo: 'book-on-the-grass.jpg', lat: 52.5176, lng: 13.3531, open: false },
|
||||
@@ -114,11 +122,11 @@ const places = [
|
||||
<Card>
|
||||
<CardBody>
|
||||
<CardTitle>Marker styles</CardTitle>
|
||||
<CardSubtitle>Draw markers in HTML with <code>.map-marker</code>: a dot, a circle with an icon or a count, a pin, or an avatar.</CardSubtitle>
|
||||
<CardSubtitle>Draw markers in HTML with <code>.map-marker</code>: a dot, a circle with an icon or a count, a pin, or an avatar. Add <code>.map-marker-label</code> for a short text.</CardSubtitle>
|
||||
<Map mapId="marker-styles" title="Marker styles" center={[52.522, 13.405]} zoom={11} ratio="4x3">
|
||||
<div data-lng="13.345" data-lat="52.54"><span class="map-marker map-marker-dot"></span></div>
|
||||
<div data-lng="13.39" data-lat="52.545"><span class="map-marker map-marker-dot map-marker-red"></span></div>
|
||||
<div data-lng="13.445" data-lat="52.538"><span class="map-marker"><Icon name="building-store" /></span></div>
|
||||
<div data-lng="13.445" data-lat="52.538"><span class="map-marker"><Icon name="building-store" /><span class="map-marker-label">Store</span></span></div>
|
||||
<div data-lng="13.35" data-lat="52.512"><span class="map-marker map-marker-green">12</span></div>
|
||||
<div data-lng="13.405" data-lat="52.52" data-anchor="bottom"><span class="map-marker map-marker-pin"><Icon name="home" /></span></div>
|
||||
<div data-lng="13.47" data-lat="52.518" data-anchor="bottom"><span class="map-marker map-marker-pin map-marker-red"><Icon name="star" /></span></div>
|
||||
@@ -180,7 +188,7 @@ const places = [
|
||||
type: 'line',
|
||||
source: 'route',
|
||||
layout: { 'line-cap': 'round', 'line-join': 'round' },
|
||||
paint: { 'line-color': tablerMapColor('--tblr-primary'), 'line-width': 4 },
|
||||
paint: { 'line-color': tablerMapColor('--tblr-primary'), 'line-width': 3, 'line-opacity': 0.8 },
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -220,7 +228,7 @@ const places = [
|
||||
<CardBody>
|
||||
<CardTitle>Clusters</CardTitle>
|
||||
<CardSubtitle>Group many points into clusters that split as you zoom in. Click a cluster to zoom to it.</CardSubtitle>
|
||||
<Map mapId="clusters" title="Clusters" center={[48.5, 9]} zoom={3} style="positron" controls />
|
||||
<Map mapId="clusters" title="Clusters" center={[48.5, 9]} zoom={3} controls />
|
||||
<CaptureScript>
|
||||
<script is:inline define:vars={{ stores }}>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
@@ -279,17 +287,18 @@ const places = [
|
||||
<Card>
|
||||
<CardBody>
|
||||
<CardTitle>City switcher</CardTitle>
|
||||
<CardSubtitle>Move the map to a place with <code>flyTo</code> when a button is clicked.</CardSubtitle>
|
||||
<div class="btn-group w-100 mb-3" role="group" aria-label="City" id="map-cities-switcher">
|
||||
{
|
||||
cities.slice(0, 4).map((city, index) => (
|
||||
<button type="button" class:list={['btn', index === 0 && 'active']} data-lng={city.lng} data-lat={city.lat}>
|
||||
{city.name}
|
||||
</button>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
<Map mapId="cities" title="City switcher" center={[52.52, 13.405]} zoom={10} ratio="21x9" />
|
||||
<CardSubtitle>Put buttons over the map and move it to a place with <code>flyTo</code>.</CardSubtitle>
|
||||
<Map mapId="cities" title="City switcher" center={[52.52, 13.405]} zoom={10}>
|
||||
<div slot="overlay" class="btn-list position-absolute top-0 start-0 w-auto h-auto m-2" role="group" aria-label="City" id="map-cities-switcher">
|
||||
{
|
||||
cities.slice(0, 4).map((city, index) => (
|
||||
<button type="button" class:list={['btn btn-sm btn-pill', index === 0 && 'btn-primary']} aria-pressed={index === 0 ? 'true' : 'false'} data-lng={city.lng} data-lat={city.lat}>
|
||||
{city.name}
|
||||
</button>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</Map>
|
||||
<CaptureScript>
|
||||
<script is:inline>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
@@ -298,7 +307,10 @@ const places = [
|
||||
|
||||
buttons.forEach((button) => {
|
||||
button.addEventListener('click', () => {
|
||||
buttons.forEach((item) => item.classList.toggle('active', item === button))
|
||||
buttons.forEach((item) => {
|
||||
item.classList.toggle('btn-primary', item === button)
|
||||
item.setAttribute('aria-pressed', item === button)
|
||||
})
|
||||
map.flyTo({ center: [Number(button.dataset.lng), Number(button.dataset.lat)], zoom: 10 })
|
||||
})
|
||||
})
|
||||
@@ -314,12 +326,12 @@ const places = [
|
||||
<CardBody>
|
||||
<CardTitle>Bubble map</CardTitle>
|
||||
<CardSubtitle>Size circles by a value, for example users per city. Hover a circle to see the number.</CardSubtitle>
|
||||
<Map mapId="bubbles" title="Bubble map" center={[20, 20]} zoom={1} style="positron" ratio="21x9" />
|
||||
<Map mapId="bubbles" title="Bubble map" center={[20, 20]} zoom={1} ratio="21x9" />
|
||||
<CaptureScript>
|
||||
<script is:inline define:vars={{ cities }}>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const map = window.tabler_map['map-bubbles']
|
||||
const popup = new maplibregl.Popup({ closeButton: false, offset: 12 })
|
||||
const tooltip = new maplibregl.Popup({ closeButton: false, offset: 12, className: 'map-tooltip' })
|
||||
|
||||
map.on('style.load', () => {
|
||||
const color = tablerMapColor('--tblr-primary')
|
||||
@@ -332,7 +344,7 @@ const places = [
|
||||
id: 'cities',
|
||||
type: 'circle',
|
||||
source: 'cities',
|
||||
paint: { 'circle-color': color, 'circle-opacity': 0.4, 'circle-radius': ['interpolate', ['linear'], ['get', 'users'], 0, 4, 1600, 28], 'circle-stroke-width': 1, 'circle-stroke-color': color },
|
||||
paint: { 'circle-color': color, 'circle-opacity': 0.6, 'circle-radius': ['interpolate', ['linear'], ['get', 'users'], 0, 4, 1600, 24] },
|
||||
})
|
||||
})
|
||||
|
||||
@@ -340,11 +352,11 @@ const places = [
|
||||
const city = event.features[0].properties
|
||||
|
||||
map.getCanvas().style.cursor = 'pointer'
|
||||
popup.setLngLat([city.lng, city.lat]).setText(`${city.name}: ${city.users} users`).addTo(map)
|
||||
tooltip.setLngLat([city.lng, city.lat]).setText(`${city.name}: ${city.users} users`).addTo(map)
|
||||
})
|
||||
map.on('mouseleave', 'cities', () => {
|
||||
map.getCanvas().style.cursor = ''
|
||||
popup.remove()
|
||||
tooltip.remove()
|
||||
})
|
||||
})
|
||||
</script>
|
||||
@@ -353,12 +365,79 @@ const places = [
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-12">
|
||||
<div class="col-lg-6">
|
||||
<Card>
|
||||
<CardBody>
|
||||
<CardTitle>Globe</CardTitle>
|
||||
<CardSubtitle>Show the map as a 3D globe with <code>setProjection</code>, and connect places with arcs.</CardSubtitle>
|
||||
<Map mapId="globe" title="Globe" center={[32, 62]} zoom={1.3} ratio="4x3" globe>
|
||||
<div data-lng={hub.lng} data-lat={hub.lat}>
|
||||
<span class="map-marker map-marker-dot">
|
||||
<span class="map-marker-label">{hub.name}</span>
|
||||
</span>
|
||||
</div>
|
||||
{
|
||||
destinations.map((place) => (
|
||||
<div data-lng={place.lng} data-lat={place.lat}>
|
||||
<span class="map-marker map-marker-dot" style="--tblr-map-marker-size: 0.625rem">
|
||||
<span class="map-marker-label">{place.name}</span>
|
||||
</span>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</Map>
|
||||
<CaptureScript>
|
||||
<script is:inline define:vars={{ hub, destinations }}>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const map = window.tabler_map['map-globe']
|
||||
|
||||
// Points along the shortest path between two places on the globe
|
||||
const arc = (from, to, steps = 64) => {
|
||||
const [lng1, lat1, lng2, lat2] = [from.lng, from.lat, to.lng, to.lat].map((degrees) => (degrees * Math.PI) / 180)
|
||||
const angle = 2 * Math.asin(Math.sqrt(Math.sin((lat2 - lat1) / 2) ** 2 + Math.cos(lat1) * Math.cos(lat2) * Math.sin((lng2 - lng1) / 2) ** 2))
|
||||
|
||||
return Array.from({ length: steps + 1 }, (_, step) => {
|
||||
const start = Math.sin((1 - step / steps) * angle) / Math.sin(angle)
|
||||
const end = Math.sin((step / steps) * angle) / Math.sin(angle)
|
||||
const x = start * Math.cos(lat1) * Math.cos(lng1) + end * Math.cos(lat2) * Math.cos(lng2)
|
||||
const y = start * Math.cos(lat1) * Math.sin(lng1) + end * Math.cos(lat2) * Math.sin(lng2)
|
||||
const z = start * Math.sin(lat1) + end * Math.sin(lat2)
|
||||
|
||||
return [(Math.atan2(y, x) * 180) / Math.PI, (Math.atan2(z, Math.hypot(x, y)) * 180) / Math.PI]
|
||||
})
|
||||
}
|
||||
|
||||
map.on('style.load', () => {
|
||||
// Hide the place names of the map style, so only the marker labels are left
|
||||
map
|
||||
.getStyle()
|
||||
.layers.filter((layer) => layer.type === 'symbol')
|
||||
.forEach((layer) => map.setLayoutProperty(layer.id, 'visibility', 'none'))
|
||||
|
||||
map.addSource('arcs', {
|
||||
type: 'geojson',
|
||||
data: { type: 'FeatureCollection', features: destinations.map((place) => ({ type: 'Feature', geometry: { type: 'LineString', coordinates: arc(hub, place) } })) },
|
||||
})
|
||||
map.addLayer({
|
||||
id: 'arcs',
|
||||
type: 'line',
|
||||
source: 'arcs',
|
||||
paint: { 'line-color': tablerMapColor('--tblr-primary'), 'line-width': 1.5, 'line-dasharray': [2, 2] },
|
||||
})
|
||||
})
|
||||
})
|
||||
</script>
|
||||
</CaptureScript>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-6">
|
||||
<Card>
|
||||
<CardBody>
|
||||
<CardTitle>Draggable marker</CardTitle>
|
||||
<CardSubtitle>Add <code>draggable: true</code> to let people move a marker, for example to pick an address.</CardSubtitle>
|
||||
<Map mapId="draggable" title="Draggable marker" center={[52.52, 13.405]} zoom={12} ratio="21x9">
|
||||
<Map mapId="draggable" title="Draggable marker" center={[52.52, 13.405]} zoom={12} ratio="4x3">
|
||||
<div data-lng="13.405" data-lat="52.52" data-anchor="bottom" data-draggable><span class="map-marker map-marker-pin"><Icon name="map-pin" /></span></div>
|
||||
</Map>
|
||||
</CardBody>
|
||||
|
||||
@@ -17,15 +17,17 @@ interface Props {
|
||||
title?: string;
|
||||
style?: string;
|
||||
controls?: boolean;
|
||||
/** draws the map as a 3D globe instead of a flat map */
|
||||
globe?: boolean;
|
||||
}
|
||||
|
||||
const { mapId, ratio, center: centerProp, zoom: zoomProp, title, style: styleProp, controls: controlsProp } = Astro.props;
|
||||
const { mapId, ratio, center: centerProp, zoom: zoomProp, title, style: styleProp, controls: controlsProp, globe = false } = Astro.props;
|
||||
|
||||
type MapData = {
|
||||
title?: string | undefined;
|
||||
ratio?: string;
|
||||
card?: boolean;
|
||||
/** OpenFreeMap style name: liberty (default), bright, positron, dark or fiord */
|
||||
/** OpenFreeMap style name: positron (default), liberty, bright, dark or fiord */
|
||||
style?: string | undefined;
|
||||
/** style used in dark mode, `dark` by default */
|
||||
darkStyle?: string;
|
||||
@@ -41,7 +43,7 @@ const hasHtmlMarkers = Astro.slots.has('default');
|
||||
|
||||
const mapContainerId = `map-${mapId}`;
|
||||
const styleUrl = (name: string) => `https://tiles.openfreemap.org/styles/${name}`;
|
||||
const style = styleUrl(data?.style ?? 'liberty');
|
||||
const style = styleUrl(data?.style ?? 'positron');
|
||||
const darkStyle = styleUrl(data?.darkStyle ?? 'dark');
|
||||
const controls = data?.controls ?? false;
|
||||
const zoom = data?.zoom ?? 13;
|
||||
@@ -55,7 +57,8 @@ const ratioClass = data?.ratio ?? ratio ?? '16x9';
|
||||
data && (
|
||||
<div class={`ratio ratio-${ratioClass}`}>
|
||||
<div>
|
||||
<div id={mapContainerId} class={`w-100 h-100${data.card ? ' rounded' : ''}`} role="img" aria-label={data.title ?? 'Map'} />
|
||||
<div id={mapContainerId} class="w-100 h-100 rounded" role="img" aria-label={data.title ?? 'Map'} />
|
||||
<slot name="overlay" />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
@@ -71,7 +74,7 @@ const ratioClass = data?.ratio ?? ratio ?? '16x9';
|
||||
data && (
|
||||
<CaptureScript>
|
||||
<!-- BEGIN MAP -->
|
||||
<script is:inline define:vars={{ mapContainerId, style, darkStyle, controls, zoom, center, markers }}>
|
||||
<script is:inline define:vars={{ mapContainerId, style, darkStyle, controls, globe, zoom, center, markers }}>
|
||||
window.tabler_map ??= {};
|
||||
|
||||
function initMap() {
|
||||
@@ -88,11 +91,16 @@ const ratioClass = data?.ratio ?? ratio ?? '16x9';
|
||||
map.addControl(new maplibregl.NavigationControl());
|
||||
}
|
||||
|
||||
if (globe) {
|
||||
// The projection is part of the style, so set it again each time the style loads
|
||||
map.on('style.load', () => map.setProjection({ type: 'globe' }));
|
||||
}
|
||||
|
||||
markers.forEach((marker) => {
|
||||
new maplibregl.Marker({ color: 'var(--tblr-primary)' })
|
||||
.setLngLat(marker.coords)
|
||||
.setPopup(new maplibregl.Popup({ offset: 32 }).setText(marker.name))
|
||||
.addTo(map);
|
||||
const element = document.createElement('span');
|
||||
|
||||
element.className = 'map-marker map-marker-dot';
|
||||
new maplibregl.Marker({ element }).setLngLat(marker.coords).setPopup(new maplibregl.Popup({ offset: 16, closeButton: false }).setText(marker.name)).addTo(map);
|
||||
});
|
||||
|
||||
// Markers written in HTML: the first child is the marker, a <template> is its popup
|
||||
@@ -102,7 +110,7 @@ const ratioClass = data?.ratio ?? ratio ?? '16x9';
|
||||
const popup = item.querySelector('template');
|
||||
|
||||
if (popup) {
|
||||
marker.setPopup(new maplibregl.Popup({ offset: anchor === 'bottom' ? 40 : 20, className: popup.className, maxWidth: 'none', focusAfterOpen: !('open' in item.dataset) }).setDOMContent(popup.content));
|
||||
marker.setPopup(new maplibregl.Popup({ offset: anchor === 'bottom' ? 40 : 16, className: popup.className, maxWidth: 'none', focusAfterOpen: !('open' in item.dataset) }).setDOMContent(popup.content));
|
||||
}
|
||||
|
||||
marker.setLngLat([Number(item.dataset.lng), Number(item.dataset.lat)]).addTo(map);
|
||||
|
||||
@@ -4,13 +4,13 @@
|
||||
"controls": true
|
||||
},
|
||||
"light": {
|
||||
"title": "Light map style",
|
||||
"title": "Street map style",
|
||||
"center": [
|
||||
51.5285582,
|
||||
-0.2416782
|
||||
],
|
||||
"zoom": 11,
|
||||
"style": "positron"
|
||||
"style": "liberty"
|
||||
},
|
||||
"markers": {
|
||||
"title": "Map with markers",
|
||||
|
||||
Reference in new issue
Block a user