Skip to content

Commit

Permalink
add popup offset option closes #1962 (#3058)
Browse files Browse the repository at this point in the history
* add popup offset option closes #1962

* Refactored popup offset calculation, improve docs.
  • Loading branch information
lucaswoj authored Aug 25, 2016
1 parent 705e54e commit 647f3c2
Show file tree
Hide file tree
Showing 4 changed files with 268 additions and 7 deletions.
78 changes: 71 additions & 7 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,18 @@ 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|{[position: string]: PointLike}} [options.offset] -
* A pixel offset applied to the popup's location specified as:
* - a single number specifying a distance from the popup's location
* - a [`PointLike`](#PointLike) specifying a constant offset
* - an object of [`PointLike`](#PointLike)s specifing an offset for each anchor position
* Negative offsets indicate left and up.
* @example
* var popup = new mapboxgl.Popup()
* .setLngLat(e.lngLat)
Expand Down Expand Up @@ -198,16 +207,17 @@ Popup.prototype = util.inherit(Evented, /** @lends Popup.prototype */{
this._container.appendChild(this._content);
}

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

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

if (pos.y < height) {
if (pos.y + offset.bottom[1] - height < 0) {
anchor = ['top'];
} else if (pos.y > this._map.transform.height - height) {
} else if (pos.y + offset.top[1] + height > this._map.transform.height) {
anchor = ['bottom'];
} else {
anchor = [];
Expand All @@ -226,6 +236,8 @@ Popup.prototype = util.inherit(Evented, /** @lends Popup.prototype */{
}
}

var offsetedPos = pos.add(offset[anchor]);

var anchorTranslate = {
'top': 'translate(-50%,0)',
'top-left': 'translate(0,0)',
Expand All @@ -243,10 +255,62 @@ Popup.prototype = util.inherit(Evented, /** @lends Popup.prototype */{
}
classList.add('mapboxgl-popup-anchor-' + anchor);

DOM.setTransform(this._container, anchorTranslate[anchor] + ' translate(' + pos.x + 'px,' + pos.y + 'px)');
DOM.setTransform(this._container, anchorTranslate[anchor] + ' translate(' + offsetedPos.x + 'px,' + offsetedPos.y + 'px)');
},

_onClickClose: function() {
this.remove();
}
});

function normalizeOffset(offset) {

if (!offset) {
return normalizeOffset(new Point(0, 0));

} else if (typeof offset === 'number') {
// input specifies a radius from which to calculate offsets at all positions
var cornerOffset = Math.round(Math.sqrt(0.5 * Math.pow(offset, 2)));
return {
'top': new Point(0, offset),
'top-left': new Point(cornerOffset, cornerOffset),
'top-right': new Point(-cornerOffset, cornerOffset),
'bottom': new Point(0, -offset),
'bottom-left': new Point(cornerOffset, -cornerOffset),
'bottom-right': new Point(-cornerOffset, -cornerOffset),
'left': new Point(offset, 0),
'right': new Point(-offset, 0)
};

} else if (isPointLike(offset)) {
// input specifies a single offset to be applied to all positions
var convertedOffset = Point.convert(offset);
return {
'top': convertedOffset,
'top-left': convertedOffset,
'top-right': convertedOffset,
'bottom': convertedOffset,
'bottom-left': convertedOffset,
'bottom-right': convertedOffset,
'left': convertedOffset,
'right': convertedOffset
};

} else {
// input specifies an offset per position
return {
'top': Point.convert(offset['top']),
'top-left': Point.convert(offset['top-left']),
'top-right': Point.convert(offset['top-right']),
'bottom': Point.convert(offset['bottom']),
'bottom-left': Point.convert(offset['bottom-left']),
'bottom-right': Point.convert(offset['bottom-right']),
'left': Point.convert(offset['left']),
'right': Point.convert(offset['right'])
};
}
}

function isPointLike(input) {
return input instanceof Point || Array.isArray(input);
}
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],
'top-right': [0,0],
'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 647f3c2

Please sign in to comment.