Skip to content

Commit

Permalink
add popup offset option closes #1962
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewharvey committed Aug 1, 2016
1 parent 79a4fee commit 9229a38
Show file tree
Hide file tree
Showing 4 changed files with 248 additions and 5 deletions.
56 changes: 51 additions & 5 deletions js/ui/popup.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ var util = require('../util/util');
var Evented = require('../util/evented');
var DOM = require('../util/dom');
var LngLat = require('../geo/lng_lat');
var Point = require('point-geometry');

/**
* A popup component.
Expand All @@ -16,10 +17,19 @@ var LngLat = require('../geo/lng_lat');
* top right corner of the popup.
* @param {boolean} [options.closeOnClick=true] If `true`, the popup will closed when the
* map is clicked.
* @param {string} options.anchor - A string indicating the popup's location relative to
* @param {string} [options.anchor] - A string indicating the popup's location relative to
* the coordinate set via [Popup#setLngLat](#Popup#setLngLat).
* Options are `'top'`, `'bottom'`, `'left'`, `'right'`, `'top-left'`,
* `'top-right'`, `'bottom-left'`, and `'bottom-right'`.
* `'top-right'`, `'bottom-left'`, and `'bottom-right'`. If unset the anchor will be
* dynamically set to ensure the popup falls within the map container with a preference
* for `'bottom'`.
* @param {number|PointLike|Object} [options.offset] - The offset in pixels to apply
* from the geographical location of the popup. This can be a single number representing
* the radius of circular offsets, [`PointLike`](#PointLike) for a fixed `[x,y]` offset
* regardless of the anchor, or an Object mapping anchor to a [`PointLike`](#PointLike)
* objects to apply a different offsets for each anchor, eg. `{top: [0,-10], bottom: [0,0]}`.
* For the offset values represented by a [`PointLike`](#PointLike), negatives indicate
* left and up.
* @example
* var popup = new mapboxgl.Popup()
* .setLngLat(e.lngLat)
Expand Down Expand Up @@ -187,15 +197,48 @@ Popup.prototype = util.inherit(Evented, /** @lends Popup.prototype */{
}

var pos = this._map.project(this._lngLat).round(),
anchor = this.options.anchor;
anchor = this.options.anchor,
offset = this.options.offset;

// a fixed offset regardless of the anchor
var offsetFixed = [0, 0];
// an object of offsets per each anchor
var offsets;

if (offset) {
if (typeof offset === 'number') {
// when offset is a fixed radius
var cornerOffset = Math.round(Math.sqrt(0.5 * Math.pow(offset, 2)));
offsets = {
'top': [0, offset],
'top-left': [cornerOffset, cornerOffset],
'top-right': [-cornerOffset, cornerOffset],
'bottom': [0, -offset],
'bottom-left': [cornerOffset, -cornerOffset],
'bottom-right': [-cornerOffset, -cornerOffset],
'left': [offset, 0],
'right': [-offset, 0]
};
} else if (offset.length && offset.length === 2) {
// when the offset is a fixed [x,y]
offsetFixed = offset;
} else {
// when the offset is preset for each anchor
offsets = offset;
}
}

// maximum offsets for each anchor configuration: top, bottom
var offsetTop = (offsets && offsets.top || offsetFixed)[1];
var offsetBottom = (offsets && offsets.bottom || offsetFixed)[1];

if (!anchor) {
var width = this._container.offsetWidth,
height = this._container.offsetHeight;

if (pos.y < height) {
if (pos.y + offsetBottom < height) {
anchor = ['top'];
} else if (pos.y > this._map.transform.height - height) {
} else if (pos.y + offsetTop > this._map.transform.height - height) {
anchor = ['bottom'];
} else {
anchor = [];
Expand All @@ -214,6 +257,9 @@ Popup.prototype = util.inherit(Evented, /** @lends Popup.prototype */{
}
}

// apply the offset to the Popup pos
pos = pos._add(Point.convert(offsets && offsets[anchor] || offsetFixed));

var anchorTranslate = {
'top': 'translate(-50%,0)',
'top-left': 'translate(0,0)',
Expand Down
Binary file added test/manual/marker.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
100 changes: 100 additions & 0 deletions test/manual/popup-offset-multiple-fixed.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
<!DOCTYPE html>
<html>
<head>
<title>Mapbox GL JS debug page</title>
<meta charset='utf-8'>
<meta name='viewport' content='width=device-width, initial-scale=1.0'>

<link rel='stylesheet' href='../../dist/mapbox-gl.css' />
<style>
body { margin: 0; padding: 0; }
html, body, #map { height: 100%; }
</style>
</head>

<body>
<div id='map'></div>
<script src='../../dist/mapbox-gl-dev.js'></script>
<script>
mapboxgl.accessToken = 'pk.eyJ1IjoiamZpcmUiLCJhIjoiZTFlNmQ3N2MzYmM2YzVjMzhkOTM2NTRhYzNiNGZiNGYifQ.1W47kmoEUpTJa3YIFefxUQ';

/* map with grey background to increase legibility of white popups */
var map = new mapboxgl.Map({
container: 'map',
style: {
'version': 8,
'sources': {},
'layers': [
{
'id': 'background',
'type': 'background',
'paint': {
'background-color': '#999'
}
}
]
}
});

var width = map.getContainer().offsetWidth,
height = map.getContainer().offsetHeight;

/* create a set of LngLat locations for all parts of the map which relate to a popup anchor option */
var lngLatLocations = {
'top-left': map.unproject([10, 10]),
'top': map.unproject([width / 2, 10]),
'top-right': map.unproject([width - 10, 10]),
'right': map.unproject([width - 10, height / 2]),
'bottom-right': map.unproject([width - 10, height - 10]),
'bottom': map.unproject([width / 2, height - 10]),
'bottom-left': map.unproject([10, height - 10]),
'left': map.unproject([10, height / 2]),
'center': map.unproject([width / 2, height / 2])
};

/* we have a complex marker which we'll need to define popup offsets for each of the popup anchors */
var markerWidth = 76;
var markerHeight = 101;
var markerRadius = markerWidth / 2;
var linearOffset = Math.round(Math.sqrt(0.5 * Math.pow(markerRadius, 2)));

var popupOffsets = {
'top': [0, 0],
'top-left': [0,0],//[linearOffset, (markerHeight - markerRadius - linearOffset) * -1],
'top-right': [0,0],//[-linearOffset, (markerHeight - markerRadius - linearOffset) * -1],
'bottom': [0, -markerHeight],
'bottom-left': [linearOffset, (markerHeight - markerRadius + linearOffset) * -1],
'bottom-right': [-linearOffset, (markerHeight - markerRadius + linearOffset) * -1],
'left': [markerRadius, (markerHeight - markerRadius) * -1],
'right': [-markerRadius, (markerHeight - markerRadius) * -1]
};

map.on('load', function () {
/* create the markers */
Object.keys(lngLatLocations).map(function(loc) {
var img = document.createElement('img');
img.src = 'marker.png';
img.style.width = markerWidth + 'px';
img.style.height = markerHeight + 'px';
var marker = new mapboxgl.Marker(img, {
offset: {
x: Math.round(-markerWidth / 2),
y: -markerHeight
}
})
.setLngLat(lngLatLocations[loc])
.addTo(map);
});
});

/* create the popups */
/* this is a complex marker, so the popup offset is an object of positions for each anchor */
Object.keys(lngLatLocations).map(function(loc) {
(new mapboxgl.Popup({ offset: popupOffsets }))
.setLngLat(lngLatLocations[loc])
.setText(loc)
.addTo(map);
});
</script>
</body>
</html>
97 changes: 97 additions & 0 deletions test/manual/popup-offset-radius.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
<!DOCTYPE html>
<html>
<head>
<title>Mapbox GL JS debug page</title>
<meta charset='utf-8'>
<meta name='viewport' content='width=device-width, initial-scale=1.0'>

<link rel='stylesheet' href='../../dist/mapbox-gl.css' />
<style>
body { margin: 0; padding: 0; }
html, body, #map { height: 100%; }
</style>
</head>

<body>
<div id='map'></div>
<script src='../../dist/mapbox-gl-dev.js'></script>
<script>
mapboxgl.accessToken = 'pk.eyJ1IjoiamZpcmUiLCJhIjoiZTFlNmQ3N2MzYmM2YzVjMzhkOTM2NTRhYzNiNGZiNGYifQ.1W47kmoEUpTJa3YIFefxUQ';

/* map with black background to increase legibility of white popups */
var map = new mapboxgl.Map({
container: 'map',
style: {
'version': 8,
'sources': {},
'layers': [
{
'id': 'background',
'type': 'background',
'paint': {
'background-color': '#000'
}
}
]
}
});

var width = map.getContainer().offsetWidth,
height = map.getContainer().offsetHeight;

/* create a set of LngLat locations for all parts of the map which relate to a popup anchor option */
var lngLatLocations = {
'top-left': map.unproject([10, 10]),
'top': map.unproject([width / 2, 10]),
'top-right': map.unproject([width - 10, 10]),
'right': map.unproject([width - 10, height / 2]),
'bottom-right': map.unproject([width - 10, height - 10]),
'bottom': map.unproject([width / 2, height - 10]),
'bottom-left': map.unproject([10, height - 10]),
'left': map.unproject([10, height / 2]),
'center': map.unproject([width / 2, height / 2])
};

/* size of marker to use */
var markerRadius = 50;

map.on('load', function () {
/* create the markers */
map.addSource('markers', {
'type': 'geojson',
'data': {
'type': 'FeatureCollection',
'features': Object.keys(lngLatLocations).map(function(loc) {
return {
'type': 'Feature',
'geometry': {
'type': 'Point',
'coordinates': lngLatLocations[loc].toArray()
}
};
})
}
});

map.addLayer({
'id': 'marker-fill',
'type': 'circle',
'source': 'markers',
'paint': {
'circle-color': '#aaf',
'circle-radius': markerRadius
}
});
});

/* create the popups */
/* this is a circular marker, so the popup offset is a radius */
Object.keys(lngLatLocations).map(function(loc) {
(new mapboxgl.Popup({ offset: markerRadius }))
.setLngLat(lngLatLocations[loc])
.setText(loc)
.addTo(map);
});
</script>
</body>
</html>

0 comments on commit 9229a38

Please sign in to comment.