Skip to content

Commit

Permalink
Add anchor option to Marker
Browse files Browse the repository at this point in the history
  • Loading branch information
jfirebaugh committed Mar 15, 2018
1 parent 191a0cc commit e49c7bd
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 59 deletions.
10 changes: 0 additions & 10 deletions debug/markers.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,6 @@
<style>
body { margin: 0; padding: 0; }
html, body, #map { height: 100%; }

.mapboxgl-marker {
width: 10px;
height: 10px;
background: red;
margin-top: -5px;
margin-left: -5px;
border-radius: 5px;
cursor: pointer;
}
</style>
</head>

Expand Down
32 changes: 32 additions & 0 deletions src/ui/anchor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// @flow

export type Anchor =
| 'middle'
| 'top'
| 'bottom'
| 'left'
| 'right'
| 'top-left'
| 'top-right'
| 'bottom-left'
| 'bottom-right';

export const anchorTranslate: {[Anchor]: string} = {
'middle': 'translate(-50%,-50%)',
'top': 'translate(-50%,0)',
'top-left': 'translate(0,0)',
'top-right': 'translate(-100%,0)',
'bottom': 'translate(-50%,-100%)',
'bottom-left': 'translate(0,-100%)',
'bottom-right': 'translate(-100%,-100%)',
'left': 'translate(0,-50%)',
'right': 'translate(-100%,-50%)'
};

export function applyAnchorClass(element: HTMLElement, anchor: Anchor, prefix: string) {
const classList = element.classList;
for (const key in anchorTranslate) {
classList.remove(`mapboxgl-${prefix}-anchor-${key}`);
}
classList.add(`mapboxgl-${prefix}-anchor-${anchor}`);
}
38 changes: 20 additions & 18 deletions src/ui/marker.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,34 +6,46 @@ import LngLat from '../geo/lng_lat';
import Point from '@mapbox/point-geometry';
import smartWrap from '../util/smart_wrap';
import { bindAll } from '../util/util';
import { type Anchor, anchorTranslate, applyAnchorClass } from './anchor';

import type Map from './map';
import type Popup from './popup';
import type {LngLatLike} from "../geo/lng_lat";
import type {MapMouseEvent} from './events';

type Options = {
offset?: PointLike,
anchor?: Anchor
};

/**
* Creates a marker component
* @param element DOM element to use as a marker. If left unspecified a default SVG will be created as the DOM element to use.
* @param options
* @param options.anchor A string indicating the Marker's location relative to the coordinate set via {@link Marker#setLngLat}.
* Options are `'middle'`, `'top'`, `'bottom'`, `'left'`, `'right'`, `'top-left'`, `'top-right'`, `'bottom-left'`, and `'bottom-right'`.
* The default is `'middle'`.
* @param options.offset The offset in pixels as a {@link PointLike} object to apply relative to the element's center. Negatives indicate left and up.
* @example
* var marker = new mapboxgl.Marker()
* .setLngLat([30.5, 50.5])
* .addTo(map);
* @see [Add custom icons with Markers](https://www.mapbox.com/mapbox-gl-js/example/custom-marker-icons/)
*/
class Marker {
export default class Marker {
_map: Map;
_anchor: Anchor;
_offset: Point;
_element: HTMLElement;
_popup: ?Popup;
_lngLat: LngLat;
_pos: ?Point;

constructor(element: ?HTMLElement, options?: {offset: PointLike}) {
constructor(element: ?HTMLElement, options?: Options) {
bindAll(['_update', '_onMapClick'], this);

this._anchor = options && options.anchor || 'middle';

if (!element) {
element = DOM.create('div');

Expand Down Expand Up @@ -134,23 +146,14 @@ class Marker {
// the y value of the center of the shadow ellipse relative to the svg top left is "shadow transform translate-y (29.0) + ellipse cy (5.80029008)"
// offset to the svg center "height (41 / 2)" gives (29.0 + 5.80029008) - (41 / 2) and rounded for an integer pixel offset gives 14
// negative is used to move the marker up from the center so the tip is at the Marker lngLat
const defaultMarkerOffset = [0, -14];
if (!(options && options.offset)) {
if (!options) {
options = {
offset: defaultMarkerOffset
};
} else {
options.offset = defaultMarkerOffset;
}
}
this._offset = Point.convert(options && options.offset || [0, -14]);
} else {
this._offset = Point.convert(options && options.offset || [0, 0]);
}

this._offset = Point.convert(options && options.offset || [0, 0]);

element.classList.add('mapboxgl-marker');
this._element = element;

this._element = element;
this._popup = null;
}

Expand Down Expand Up @@ -296,7 +299,8 @@ class Marker {
this._pos = this._pos.round();
}

DOM.setTransform(this._element, `translate(-50%, -50%) translate(${this._pos.x}px, ${this._pos.y}px)`);
DOM.setTransform(this._element, `${anchorTranslate[this._anchor]} translate(${this._pos.x}px, ${this._pos.y}px)`);
applyAnchorClass(this._element, this._anchor, 'marker');
}

/**
Expand All @@ -318,5 +322,3 @@ class Marker {
return this;
}
}

export default Marker;
48 changes: 17 additions & 31 deletions src/ui/popup.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import LngLat from '../geo/lng_lat';
import Point from '@mapbox/point-geometry';
import window from '../util/window';
import smartWrap from '../util/smart_wrap';
import { type Anchor, anchorTranslate, applyAnchorClass } from './anchor';

import type Map from './map';
import type {LngLatLike} from '../geo/lng_lat';
Expand All @@ -16,7 +17,6 @@ const defaultOptions = {
closeOnClick: true
};

export type Anchor = 'top' | 'bottom' | 'left' | 'right' | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
export type Offset = number | PointLike | {[Anchor]: PointLike};

export type PopupOptions = {
Expand Down Expand Up @@ -66,7 +66,7 @@ export type PopupOptions = {
* @see [Display a popup on hover](https://www.mapbox.com/mapbox-gl-js/example/popup-on-hover/)
* @see [Display a popup on click](https://www.mapbox.com/mapbox-gl-js/example/popup-on-click/)
*/
class Popup extends Evented {
export default class Popup extends Evented {
_map: Map;
options: PopupOptions;
_content: HTMLElement;
Expand Down Expand Up @@ -276,54 +276,39 @@ class Popup extends Evented {

const pos = this._pos = this._map.project(this._lngLat);

let anchor = this.options.anchor;
let anchor: Anchor = this.options.anchor;
const offset = normalizeOffset(this.options.offset);

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

if (pos.y + offset.bottom.y < height) {
anchor = ['top'];
anchorComponents = ['top'];
} else if (pos.y > this._map.transform.height - height) {
anchor = ['bottom'];
anchorComponents = ['bottom'];
} else {
anchor = [];
anchorComponents = [];
}

if (pos.x < width / 2) {
anchor.push('left');
anchorComponents.push('left');
} else if (pos.x > this._map.transform.width - width / 2) {
anchor.push('right');
anchorComponents.push('right');
}

if (anchor.length === 0) {
if (anchorComponents.length === 0) {
anchor = 'bottom';
} else {
anchor = anchor.join('-');
anchor = (anchorComponents.join('-'): any);
}
}

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

const anchorTranslate = {
'top': 'translate(-50%,0)',
'top-left': 'translate(0,0)',
'top-right': 'translate(-100%,0)',
'bottom': 'translate(-50%,-100%)',
'bottom-left': 'translate(0,-100%)',
'bottom-right': 'translate(-100%,-100%)',
'left': 'translate(0,-50%)',
'right': 'translate(-100%,-50%)'
};

const classList = this._container.classList;
for (const key in anchorTranslate) {
classList.remove(`mapboxgl-popup-anchor-${key}`);
}
classList.add(`mapboxgl-popup-anchor-${anchor}`);

DOM.setTransform(this._container, `${anchorTranslate[anchor]} translate(${offsetedPos.x}px,${offsetedPos.y}px)`);
applyAnchorClass(this._container, anchor, 'popup');
}

_onClickClose() {
Expand All @@ -339,6 +324,7 @@ function normalizeOffset(offset: ?Offset) {
// input specifies a radius from which to calculate offsets at all positions
const cornerOffset = Math.round(Math.sqrt(0.5 * Math.pow(offset, 2)));
return {
'middle': new Point(0, 0),
'top': new Point(0, offset),
'top-left': new Point(cornerOffset, cornerOffset),
'top-right': new Point(-cornerOffset, cornerOffset),
Expand All @@ -353,6 +339,7 @@ function normalizeOffset(offset: ?Offset) {
// input specifies a single offset to be applied to all positions
const convertedOffset = Point.convert(offset);
return {
'middle': convertedOffset,
'top': convertedOffset,
'top-left': convertedOffset,
'top-right': convertedOffset,
Expand All @@ -366,6 +353,7 @@ function normalizeOffset(offset: ?Offset) {
} else {
// input specifies an offset per position
return {
'middle': Point.convert(offset['middle'] || [0, 0]),
'top': Point.convert(offset['top'] || [0, 0]),
'top-left': Point.convert(offset['top-left'] || [0, 0]),
'top-right': Point.convert(offset['top-right'] || [0, 0]),
Expand All @@ -377,5 +365,3 @@ function normalizeOffset(offset: ?Offset) {
};
}
}

export default Popup;
26 changes: 26 additions & 0 deletions test/unit/ui/marker.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,29 @@ test('Marker#togglePopup closes a popup that was open', (t) => {
map.remove();
t.end();
});

test('Marker anchor defaults to middle', (t) => {
const map = createMap();
const marker = new Marker()
.setLngLat([0, 0])
.addTo(map);

t.ok(marker.getElement().classList.contains('mapboxgl-marker-anchor-middle'));
t.match(marker.getElement().style.transform, /translate\(-50%,-50%\)/);

map.remove();
t.end();
});

test('Marker anchors as specified by the anchor option', (t) => {
const map = createMap();
const marker = new Marker(null, {anchor: 'top'})
.setLngLat([0, 0])
.addTo(map);

t.ok(marker.getElement().classList.contains('mapboxgl-marker-anchor-top'));
t.match(marker.getElement().style.transform, /translate\(-50%,0\)/);

map.remove();
t.end();
});

0 comments on commit e49c7bd

Please sign in to comment.