From 19d42db9ce75886622a09ec7daeff6f1f81f3a53 Mon Sep 17 00:00:00 2001 From: Jason Mandel Date: Mon, 15 Jan 2018 23:31:51 -0500 Subject: [PATCH 01/16] added mouseover event --- src/ui/bind_handlers.js | 10 ++++++++++ src/ui/events.js | 16 +++++++++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/ui/bind_handlers.js b/src/ui/bind_handlers.js index 8a7240baee0..d6dd53f8470 100644 --- a/src/ui/bind_handlers.js +++ b/src/ui/bind_handlers.js @@ -33,6 +33,7 @@ module.exports = function bindHandlers(map: Map, options: {}) { el.addEventListener('mousedown', onMouseDown, false); el.addEventListener('mouseup', onMouseUp, false); el.addEventListener('mousemove', onMouseMove, false); + el.addEventListener('mouseover', onMouseOver, false); el.addEventListener('touchstart', onTouchStart, false); el.addEventListener('touchend', onTouchEnd, false); el.addEventListener('touchmove', onTouchMove, false); @@ -80,6 +81,15 @@ module.exports = function bindHandlers(map: Map, options: {}) { fireMouseEvent('mousemove', e); } + function onMouseOver(e: MouseEvent) { + + let target: any = e.toElement || e.target; + while (target && target !== el) target = target.parentNode; + if (target !== el) return; + + fireMouseEvent('mouseover', e); + } + function onTouchStart(e: TouchEvent) { map.stop(); fireTouchEvent('touchstart', e); diff --git a/src/ui/events.js b/src/ui/events.js index 9c65d6e71df..ae6bbdd26dd 100644 --- a/src/ui/events.js +++ b/src/ui/events.js @@ -20,6 +20,7 @@ export type MapMouseEvent = { | 'click' | 'dblclick' | 'mousemove' + | 'mouseover' | 'mouseenter' | 'mouseleave' | 'mouseover' @@ -130,7 +131,7 @@ export type MapEvent = /** * Fired when a pointing device (usually a mouse) is moved within the map. * - * @event mousemove + * @event mouseover * @memberof Map * @instance * @property {MapMouseEvent} data @@ -138,6 +139,19 @@ export type MapEvent = * @see [Highlight features under the mouse pointer](https://www.mapbox.com/mapbox-gl-js/example/hover-styles/) * @see [Display a popup on hover](https://www.mapbox.com/mapbox-gl-js/example/popup-on-hover/) */ + | 'mouseover' + + /** + * Fired when a pointing device (usually a mouse) is moved within the map. + * + * @event mousemove + * @memberof Map + * @instance + * @property {MapMouseEvent} data + * @see [Get coordinates of the mouse pointer](https://www.mapbox.com/mapbox-gl-js/example/mouse-position/) + * @see [Highlight features under the mouse pointer](https://www.mapbox.com/mapbox-gl-js/example/hover-styles/) + * @see [Display a popup on over](https://www.mapbox.com/mapbox-gl-js/example/popup-on-hover/) + */ | 'mousemove' /** From 5ad57895de9a6bc8ec299e2c93e3cfac10506de1 Mon Sep 17 00:00:00 2001 From: Jason Mandel Date: Tue, 16 Jan 2018 10:44:50 -0500 Subject: [PATCH 02/16] testing mouseover event handler --- src/ui/bind_handlers.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ui/bind_handlers.js b/src/ui/bind_handlers.js index d6dd53f8470..d91e941c11c 100644 --- a/src/ui/bind_handlers.js +++ b/src/ui/bind_handlers.js @@ -83,11 +83,11 @@ module.exports = function bindHandlers(map: Map, options: {}) { function onMouseOver(e: MouseEvent) { - let target: any = e.toElement || e.target; - while (target && target !== el) target = target.parentNode; - if (target !== el) return; + let target: any = e.toElement || e.target; + while (target && target !== el) target = target.parentNode; + if (target !== el) return; - fireMouseEvent('mouseover', e); + fireMouseEvent('mouseover', e); } function onTouchStart(e: TouchEvent) { From 47d1243057b5b4e6324ee0508051ad4f7499eca0 Mon Sep 17 00:00:00 2001 From: Jason Mandel Date: Thu, 18 Jan 2018 23:01:36 -0500 Subject: [PATCH 03/16] added anchor option for marker --- src/ui/marker.js | 18 +++++++++++++++--- test/unit/ui/marker.test.js | 2 +- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/ui/marker.js b/src/ui/marker.js index 831dc02a31a..75d627f4921 100644 --- a/src/ui/marker.js +++ b/src/ui/marker.js @@ -11,11 +11,20 @@ import type Popup from './popup'; import type {LngLatLike} from "../geo/lng_lat"; import type {MapMouseEvent} from './events'; +export type Anchor = number | PointLike; +export type Offset = number | PointLike | Anchor; + +export type MarkerOptions = { + anchor: Anchor, + offset: Offset +} + /** * 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.offset The offset in pixels as a {@link PointLike} object to apply relative to the element's center. Negatives indicate left and up. + *@param options.anchor The sets anchor 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]) @@ -24,14 +33,17 @@ import type {MapMouseEvent} from './events'; */ class Marker { _map: Map; + options: MarkerOptions; _offset: Point; + _anchor: Anchor; _element: HTMLElement; _popup: ?Popup; _lngLat: LngLat; _pos: ?Point; - constructor(element: ?HTMLElement, options?: {offset: PointLike}) { + constructor(element: ?HTMLElement, options?: {offset: PointLike, anchor: Anchor}) { this._offset = Point.convert(options && options.offset || [0, 0]); + this._anchor = Point.convert(options && options.anchor || [0, 0]); bindAll(['_update', '_onMapClick'], this); @@ -268,7 +280,7 @@ class Marker { this._lngLat = smartWrap(this._lngLat, this._pos, this._map.transform); } - this._pos = this._map.project(this._lngLat)._add(this._offset); + this._pos = this._map.project(this._lngLat)._add(this._anchor)._add(this._offset); // because rounding the coordinates at every `move` event causes stuttered zooming // we only round them when _update is called with `moveend` or when its called with @@ -277,7 +289,7 @@ 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, `translate(${this._pos.x}px, ${this._pos.y}px)`); } /** diff --git a/test/unit/ui/marker.test.js b/test/unit/ui/marker.test.js index 6018bbb8fe5..8b273d56b53 100644 --- a/test/unit/ui/marker.test.js +++ b/test/unit/ui/marker.test.js @@ -80,7 +80,7 @@ test('Marker', (t) => { const element = window.document.createElement('div'); const marker = new Marker(element).setLngLat([0, 0]).addTo(map); const translate = Math.round(map.getContainer().offsetWidth / 2); - t.equal(marker.getElement().style.transform, `translate(-50%, -50%) translate(${translate}px, ${translate}px)`, 'Marker centered'); + t.equal(marker.getElement().style.transform, `translate(${translate}px, ${translate}px)`, 'Marker centered'); t.end(); }); From ada8e9239a7151d73bc74b28dee1cd63d286c2bc Mon Sep 17 00:00:00 2001 From: Jason Mandel Date: Fri, 19 Jan 2018 00:14:43 -0500 Subject: [PATCH 04/16] Revert "added anchor option for marker" --- src/ui/marker.js | 18 +++--------------- test/unit/ui/marker.test.js | 2 +- 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/src/ui/marker.js b/src/ui/marker.js index 75d627f4921..831dc02a31a 100644 --- a/src/ui/marker.js +++ b/src/ui/marker.js @@ -11,20 +11,11 @@ import type Popup from './popup'; import type {LngLatLike} from "../geo/lng_lat"; import type {MapMouseEvent} from './events'; -export type Anchor = number | PointLike; -export type Offset = number | PointLike | Anchor; - -export type MarkerOptions = { - anchor: Anchor, - offset: Offset -} - /** * 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.offset The offset in pixels as a {@link PointLike} object to apply relative to the element's center. Negatives indicate left and up. - *@param options.anchor The sets anchor 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]) @@ -33,17 +24,14 @@ export type MarkerOptions = { */ class Marker { _map: Map; - options: MarkerOptions; _offset: Point; - _anchor: Anchor; _element: HTMLElement; _popup: ?Popup; _lngLat: LngLat; _pos: ?Point; - constructor(element: ?HTMLElement, options?: {offset: PointLike, anchor: Anchor}) { + constructor(element: ?HTMLElement, options?: {offset: PointLike}) { this._offset = Point.convert(options && options.offset || [0, 0]); - this._anchor = Point.convert(options && options.anchor || [0, 0]); bindAll(['_update', '_onMapClick'], this); @@ -280,7 +268,7 @@ class Marker { this._lngLat = smartWrap(this._lngLat, this._pos, this._map.transform); } - this._pos = this._map.project(this._lngLat)._add(this._anchor)._add(this._offset); + this._pos = this._map.project(this._lngLat)._add(this._offset); // because rounding the coordinates at every `move` event causes stuttered zooming // we only round them when _update is called with `moveend` or when its called with @@ -289,7 +277,7 @@ class Marker { this._pos = this._pos.round(); } - DOM.setTransform(this._element, `translate(${this._pos.x}px, ${this._pos.y}px)`); + DOM.setTransform(this._element, `translate(-50%, -50%) translate(${this._pos.x}px, ${this._pos.y}px)`); } /** diff --git a/test/unit/ui/marker.test.js b/test/unit/ui/marker.test.js index 8b273d56b53..6018bbb8fe5 100644 --- a/test/unit/ui/marker.test.js +++ b/test/unit/ui/marker.test.js @@ -80,7 +80,7 @@ test('Marker', (t) => { const element = window.document.createElement('div'); const marker = new Marker(element).setLngLat([0, 0]).addTo(map); const translate = Math.round(map.getContainer().offsetWidth / 2); - t.equal(marker.getElement().style.transform, `translate(${translate}px, ${translate}px)`, 'Marker centered'); + t.equal(marker.getElement().style.transform, `translate(-50%, -50%) translate(${translate}px, ${translate}px)`, 'Marker centered'); t.end(); }); From e0972bb933fddddf246c7d5221c69234c97860f0 Mon Sep 17 00:00:00 2001 From: Jason Mandel Date: Fri, 19 Jan 2018 11:23:31 -0500 Subject: [PATCH 05/16] added support for anchor option on marker --- src/ui/marker.js | 43 +++++++++++++++++++++++++++++++------ test/unit/ui/marker.test.js | 2 +- 2 files changed, 38 insertions(+), 7 deletions(-) diff --git a/src/ui/marker.js b/src/ui/marker.js index 831dc02a31a..b07e5f730fc 100644 --- a/src/ui/marker.js +++ b/src/ui/marker.js @@ -11,11 +11,20 @@ import type Popup from './popup'; import type {LngLatLike} from "../geo/lng_lat"; import type {MapMouseEvent} from './events'; +export type Anchor = number | PointLike; +export type Offset = number | PointLike | Anchor; + +export type MarkerOptions = { + anchor: Anchor, + offset: Offset +} + /** * 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.offset The offset in pixels as a {@link PointLike} object to apply relative to the element's center. Negatives indicate left and up. + * @param {Object} [element] DOM element to use as a marker. If left unspecified a default SVG will be created as the DOM element to use. + * @param {Object} [options] + * @param {number|PointLike|Object} [options.anchor] The sets anchor in pixels as a {@link PointLike} object to apply relative to the element's position. + * @param {number|PointLike|Object} [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]) @@ -24,13 +33,16 @@ import type {MapMouseEvent} from './events'; */ class Marker { _map: Map; + options: MarkerOptions; + _anchor: Anchor; _offset: Point; _element: HTMLElement; _popup: ?Popup; _lngLat: LngLat; _pos: ?Point; - constructor(element: ?HTMLElement, options?: {offset: PointLike}) { + constructor(element: ?HTMLElement, options?: {anchor: Anchor, offset: PointLike}) { + this._anchor = Point.convert(options && options.anchor || [0, 0]); this._offset = Point.convert(options && options.offset || [0, 0]); bindAll(['_update', '_onMapClick'], this); @@ -268,7 +280,7 @@ class Marker { this._lngLat = smartWrap(this._lngLat, this._pos, this._map.transform); } - this._pos = this._map.project(this._lngLat)._add(this._offset); + this._pos = this._map.project(this._lngLat)._add(this._anchor)._add(this._offset); // because rounding the coordinates at every `move` event causes stuttered zooming // we only round them when _update is called with `moveend` or when its called with @@ -277,7 +289,26 @@ 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, `translate(${this._pos.x}px, ${this._pos.y}px)`); + } + + /** + * Get the marker's anchor. + * @returns {Point} + */ + getAnchor() { + return this._anchor; + } + + /** + * Sets the anchor of the marker + * @param {PointLike} [anchor] The anchor in pixels as a {@link PointLike} object to apply relative to the element's center. Negatives indicate left and up. + * @returns {Marker} `this` + */ + setAnchor(anchor: PointLike) { + this._anchor = Point.convert(anchor); + this._update(); + return this; } /** diff --git a/test/unit/ui/marker.test.js b/test/unit/ui/marker.test.js index 6018bbb8fe5..8b273d56b53 100644 --- a/test/unit/ui/marker.test.js +++ b/test/unit/ui/marker.test.js @@ -80,7 +80,7 @@ test('Marker', (t) => { const element = window.document.createElement('div'); const marker = new Marker(element).setLngLat([0, 0]).addTo(map); const translate = Math.round(map.getContainer().offsetWidth / 2); - t.equal(marker.getElement().style.transform, `translate(-50%, -50%) translate(${translate}px, ${translate}px)`, 'Marker centered'); + t.equal(marker.getElement().style.transform, `translate(${translate}px, ${translate}px)`, 'Marker centered'); t.end(); }); From dcfc7102ec13fcf88aac73e01d967a639a4e68ba Mon Sep 17 00:00:00 2001 From: Jason Mandel Date: Sun, 21 Jan 2018 13:29:00 -0500 Subject: [PATCH 06/16] progress on adding anchor support to marker WIP --- src/ui/marker.js | 122 ++++++++++++++++++++++++++++++++++-- test/unit/ui/marker.test.js | 2 +- 2 files changed, 117 insertions(+), 7 deletions(-) diff --git a/src/ui/marker.js b/src/ui/marker.js index b07e5f730fc..03b02ddb3f0 100644 --- a/src/ui/marker.js +++ b/src/ui/marker.js @@ -11,8 +11,8 @@ import type Popup from './popup'; import type {LngLatLike} from "../geo/lng_lat"; import type {MapMouseEvent} from './events'; -export type Anchor = number | PointLike; -export type Offset = number | PointLike | Anchor; +export type Anchor = 'top' | 'bottom' | 'left' | 'right' | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'; +export type Offset = number | PointLike | {[Anchor]: PointLike}; export type MarkerOptions = { anchor: Anchor, @@ -23,9 +23,25 @@ export type MarkerOptions = { * Creates a marker component * @param {Object} [element] DOM element to use as a marker. If left unspecified a default SVG will be created as the DOM element to use. * @param {Object} [options] - * @param {number|PointLike|Object} [options.anchor] The sets anchor in pixels as a {@link PointLike} object to apply relative to the element's position. + * @param {string} [options.anchor] - A string indicating the markers's location relative to + * the coordinate set via {@link Marker#setLngLat}. + * Options are `'top'`, `'bottom'`, `'left'`, `'right'`, `'top-left'`, + * `'top-right'`, `'bottom-left'`, and `'bottom-right'`. If unset the anchor will be + * dynamically set to ensure the marker falls within the map container with a preference + * for `'bottom'`. * @param {number|PointLike|Object} [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 markerHeight = 50, markerRadius = 10, linearOffset = 25; + * var markerOffsets = { + * '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] + * }; * var marker = new mapboxgl.Marker() * .setLngLat([30.5, 50.5]) * .addTo(map); @@ -280,16 +296,63 @@ class Marker { this._lngLat = smartWrap(this._lngLat, this._pos, this._map.transform); } - this._pos = this._map.project(this._lngLat)._add(this._anchor)._add(this._offset); + const pos = this._pos = this._map.project(this._lngLat); + + let anchor = this.options.anchor; + const offset = normalizeOffset(this.options.offset); + + if (!anchor) { + const width = this._element.offsetWidth, + height = this._element.offsetHeight; + + if (pos.y + offset.bottom.y < height) { + anchor = ['top']; + } else if (pos.y > this._map.transform.height - height) { + anchor = ['bottom']; + } else { + anchor = []; + } + + if (pos.x < width / 2) { + anchor.push('left'); + } else if (pos.x > this._map.transform.width - width / 2) { + anchor.push('right'); + } + + if (anchor.length === 0) { + anchor = 'bottom'; + } else { + anchor = anchor.join('-'); + } + } + + const offsetedPos = pos.add(offset[anchor]); // because rounding the coordinates at every `move` event causes stuttered zooming // we only round them when _update is called with `moveend` or when its called with // no arguments (when the Marker is initialized or Marker#setLngLat is invoked). if (!e || e.type === "moveend") { - this._pos = this._pos.round(); + this._pos = offsetedPos.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._element.classList; + for (const key in anchorTranslate) { + classList.remove(`mapboxgl-marker-anchor-${key}`); } + classList.add(`mapboxgl-marker-anchor-${anchor}`); - DOM.setTransform(this._element, `translate(${this._pos.x}px, ${this._pos.y}px)`); + DOM.setTransform(this._element, `${anchorTranslate[anchor]} translate(${offsetedPos.x}px,${offsetedPos.y}px)`); } /** @@ -331,4 +394,51 @@ class Marker { } } +function normalizeOffset(offset: ?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 + const 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 (offset instanceof Point || Array.isArray(offset)) { + // input specifies a single offset to be applied to all positions + const 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'] || [0, 0]), + 'top-left': Point.convert(offset['top-left'] || [0, 0]), + 'top-right': Point.convert(offset['top-right'] || [0, 0]), + 'bottom': Point.convert(offset['bottom'] || [0, 0]), + 'bottom-left': Point.convert(offset['bottom-left'] || [0, 0]), + 'bottom-right': Point.convert(offset['bottom-right'] || [0, 0]), + 'left': Point.convert(offset['left'] || [0, 0]), + 'right': Point.convert(offset['right'] || [0, 0]) + }; + } +} + module.exports = Marker; diff --git a/test/unit/ui/marker.test.js b/test/unit/ui/marker.test.js index 8b273d56b53..c5512f40cee 100644 --- a/test/unit/ui/marker.test.js +++ b/test/unit/ui/marker.test.js @@ -80,7 +80,7 @@ test('Marker', (t) => { const element = window.document.createElement('div'); const marker = new Marker(element).setLngLat([0, 0]).addTo(map); const translate = Math.round(map.getContainer().offsetWidth / 2); - t.equal(marker.getElement().style.transform, `translate(${translate}px, ${translate}px)`, 'Marker centered'); + t.equal(marker.getElement().style.transform, `translate(-50%, -50%), translate(${translate}px, ${translate}px)`, 'Marker centered'); t.end(); }); From 532b453cb7e3ad37bf13f85b24955cebf97de7e2 Mon Sep 17 00:00:00 2001 From: Jason Mandel Date: Wed, 24 Jan 2018 23:08:52 -0500 Subject: [PATCH 07/16] made requested tweaks to marker, though still stumping tests --- src/ui/marker.js | 107 +++++++++++------------------------- test/unit/ui/marker.test.js | 19 +++++++ 2 files changed, 50 insertions(+), 76 deletions(-) diff --git a/src/ui/marker.js b/src/ui/marker.js index 03b02ddb3f0..63864942b91 100644 --- a/src/ui/marker.js +++ b/src/ui/marker.js @@ -11,7 +11,7 @@ import type Popup from './popup'; import type {LngLatLike} from "../geo/lng_lat"; import type {MapMouseEvent} from './events'; -export type Anchor = 'top' | 'bottom' | 'left' | 'right' | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'; +export type Anchor = 'middle'; export type Offset = number | PointLike | {[Anchor]: PointLike}; export type MarkerOptions = { @@ -25,22 +25,12 @@ export type MarkerOptions = { * @param {Object} [options] * @param {string} [options.anchor] - A string indicating the markers's location relative to * the coordinate set via {@link Marker#setLngLat}. - * Options are `'top'`, `'bottom'`, `'left'`, `'right'`, `'top-left'`, - * `'top-right'`, `'bottom-left'`, and `'bottom-right'`. If unset the anchor will be - * dynamically set to ensure the marker falls within the map container with a preference - * for `'bottom'`. - * @param {number|PointLike|Object} [options.offset] The offset in pixels as a {@link PointLike} object to apply relative to the element's center. Negatives indicate left and up. + * Options is `'middle'` by default + * @param {number|PointLike|Object} [options.offset] The offset in pixels as a {@link PointLike} object to apply relative to the element's anchor. * @example * var markerHeight = 50, markerRadius = 10, linearOffset = 25; * var markerOffsets = { - * '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] + * 'middle': [markerRadius/2, markerHeight/2] * }; * var marker = new mapboxgl.Marker() * .setLngLat([30.5, 50.5]) @@ -58,8 +48,6 @@ class Marker { _pos: ?Point; constructor(element: ?HTMLElement, options?: {anchor: Anchor, offset: PointLike}) { - this._anchor = Point.convert(options && options.anchor || [0, 0]); - this._offset = Point.convert(options && options.offset || [0, 0]); bindAll(['_update', '_onMapClick'], this); @@ -155,6 +143,27 @@ class Marker { svg.appendChild(page1); element.appendChild(svg); + + // if no element and no offset option given apply an offset for the default marker + // the -14 as the y value of the default marker offset was determined as follows + // + // the marker tip is at the center of the shadow ellipse from the default svg + // 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]; + const defaultMarkerAnchor = 'middle'; + if (!(options && options.offset)) { + if (!options) { + options = { + anchor: defaultMarkerAnchor, + offset: defaultMarkerOffset + }; + } else { + options.anchor = defaultMarkerAnchor; + options.offset = defaultMarkerOffset; + } + } } element.classList.add('mapboxgl-marker'); @@ -297,35 +306,9 @@ class Marker { } const pos = this._pos = this._map.project(this._lngLat); + const anchor = 'middle'; - let anchor = this.options.anchor; const offset = normalizeOffset(this.options.offset); - - if (!anchor) { - const width = this._element.offsetWidth, - height = this._element.offsetHeight; - - if (pos.y + offset.bottom.y < height) { - anchor = ['top']; - } else if (pos.y > this._map.transform.height - height) { - anchor = ['bottom']; - } else { - anchor = []; - } - - if (pos.x < width / 2) { - anchor.push('left'); - } else if (pos.x > this._map.transform.width - width / 2) { - anchor.push('right'); - } - - if (anchor.length === 0) { - anchor = 'bottom'; - } else { - anchor = anchor.join('-'); - } - } - const offsetedPos = pos.add(offset[anchor]); // because rounding the coordinates at every `move` event causes stuttered zooming @@ -336,14 +319,7 @@ class Marker { } 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%)' + 'middle': 'translate(-50%,-50%)' }; const classList = this._element.classList; @@ -400,43 +376,22 @@ function normalizeOffset(offset: ?Offset) { } else if (typeof offset === 'number') { // input specifies a radius from which to calculate offsets at all positions - const cornerOffset = Math.round(Math.sqrt(0.5 * Math.pow(offset, 2))); + //const 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) + 'middle': new Point(0, 0) }; } else if (offset instanceof Point || Array.isArray(offset)) { // input specifies a single offset to be applied to all positions const 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 + 'middle': convertedOffset }; } else { // input specifies an offset per position return { - '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]), - 'bottom': Point.convert(offset['bottom'] || [0, 0]), - 'bottom-left': Point.convert(offset['bottom-left'] || [0, 0]), - 'bottom-right': Point.convert(offset['bottom-right'] || [0, 0]), - 'left': Point.convert(offset['left'] || [0, 0]), - 'right': Point.convert(offset['right'] || [0, 0]) + 'middle': Point.convert(offset['middle'] || [0, 0]) }; } } diff --git a/test/unit/ui/marker.test.js b/test/unit/ui/marker.test.js index c5512f40cee..75a98fabba8 100644 --- a/test/unit/ui/marker.test.js +++ b/test/unit/ui/marker.test.js @@ -6,6 +6,7 @@ const Map = require('../../../src/ui/map'); const Marker = require('../../../src/ui/marker'); const Popup = require('../../../src/ui/popup'); const LngLat = require('../../../src/geo/lng_lat'); +const Point = require('@mapbox/point-geometry'); function createMap() { const container = window.document.createElement('div'); @@ -25,6 +26,24 @@ test('Marker', (t) => { t.test('default marker', (t) => { const marker = new Marker(); t.ok(marker.getElement(), 'default marker is created'); + t.ok(marker.getAnchor(), 'marker set with anchor to middle'); + t.ok(marker.getOffset().equals(new Point(0, -14)), 'default marker with no offset uses default marker offset'); + t.end(); + }); + + t.test('default marker with some options', (t) => { + const marker = new Marker(null, { foo: 'bar' }); + t.ok(marker.getElement(), 'default marker is created'); + t.ok(marker.getAnchor(), 'marker set with anchor to middle'); + t.ok(marker.getOffset().equals(new Point(0, -14)), 'default marker with no offset uses default marker offset'); + t.end(); + }); + + t.test('default marker with custom offest', (t) => { + const marker = new Marker(null, { offset: [1, 2] }); + t.ok(marker.getElement(), 'default marker is created'); + t.ok(marker.getAnchor(), 'marker set with anchor to middle'); + t.ok(marker.getOffset().equals(new Point(1, 2)), 'default marker with supplied offset'); t.end(); }); From c4e2acafbe754675eaddd32bfc7dcab94b102b28 Mon Sep 17 00:00:00 2001 From: Jason Mandel Date: Thu, 25 Jan 2018 17:31:11 -0500 Subject: [PATCH 08/16] progress though stuck on testing through enumerated anchors + offsets --- src/ui/marker.js | 99 +++++++++++++++++++---- test/unit/ui/marker.test.js | 157 +++++++++++++++++++++++++++++++----- 2 files changed, 219 insertions(+), 37 deletions(-) diff --git a/src/ui/marker.js b/src/ui/marker.js index 63864942b91..2b7037878d0 100644 --- a/src/ui/marker.js +++ b/src/ui/marker.js @@ -11,7 +11,7 @@ import type Popup from './popup'; import type {LngLatLike} from "../geo/lng_lat"; import type {MapMouseEvent} from './events'; -export type Anchor = 'middle'; +export type Anchor = 'middle' | 'top' | 'bottom' | 'left' | 'right' | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'; export type Offset = number | PointLike | {[Anchor]: PointLike}; export type MarkerOptions = { @@ -23,14 +23,23 @@ export type MarkerOptions = { * Creates a marker component * @param {Object} [element] DOM element to use as a marker. If left unspecified a default SVG will be created as the DOM element to use. * @param {Object} [options] - * @param {string} [options.anchor] - A string indicating the markers's location relative to + * @param {Array | string} [options.anchor = Anchor] - A string indicating the markers's location relative to * the coordinate set via {@link Marker#setLngLat}. - * Options is `'middle'` by default - * @param {number|PointLike|Object} [options.offset] The offset in pixels as a {@link PointLike} object to apply relative to the element's anchor. + * Options are `'middle'`, `'top'`, `'bottom'`, `'left'`, `'right'`, `'top-left'`, `'top-right'`, `'bottom-left'`, and `'bottom-right'`. + * If unset the anchor will be dynamically set to ensure the marker falls within the map container with a preference for `'middle'` by default + * @param {number|PointLike|Object} [options.offset = Offset] The offset in pixels as a {@link PointLike} object to apply relative to the element's anchor. * @example * var markerHeight = 50, markerRadius = 10, linearOffset = 25; * var markerOffsets = { - * 'middle': [markerRadius/2, markerHeight/2] + * 'middle': [markerRadius/2, markerHeight/2], + * '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] * }; * var marker = new mapboxgl.Marker() * .setLngLat([30.5, 50.5]) @@ -41,13 +50,15 @@ class Marker { _map: Map; options: MarkerOptions; _anchor: Anchor; - _offset: Point; + _offset: Offset; _element: HTMLElement; _popup: ?Popup; _lngLat: LngLat; _pos: ?Point; constructor(element: ?HTMLElement, options?: {anchor: Anchor, offset: PointLike}) { + this._anchor = Point.convert(options && options.anchor || 'middle'); + this._offset = Point.convert(options && options.offset || [0, 0]); bindAll(['_update', '_onMapClick'], this); @@ -168,7 +179,6 @@ class Marker { element.classList.add('mapboxgl-marker'); this._element = element; - this._popup = null; } @@ -306,9 +316,36 @@ class Marker { } const pos = this._pos = this._map.project(this._lngLat); - const anchor = 'middle'; - const offset = normalizeOffset(this.options.offset); + let anchor = Point.convert(this.options && this.options.anchor || 'middle'); + const offset = normalizeOffset(this.options && this.options.offset || [0, 0]); + + + if (!anchor) { + const width = this._element.offsetWidth, + height = this._element.offsetHeight; + + if (pos.y + offset.bottom.y < height) { + anchor = ['top']; + } else if (pos.y > this._map.transform.height - height) { + anchor = ['bottom']; + } else { + anchor = []; + } + + if (pos.x < width / 2) { + anchor.push('left'); + } else if (pos.x > this._map.transform.width - width / 2) { + anchor.push('right'); + } + + if (anchor.length === 0) { + anchor = 'middle'; + } else { + anchor = anchor.join('-'); + } + } + const offsetedPos = pos.add(offset[anchor]); // because rounding the coordinates at every `move` event causes stuttered zooming @@ -319,7 +356,15 @@ class Marker { } const anchorTranslate = { - 'middle': 'translate(-50%,-50%)' + '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%)' }; const classList = this._element.classList; @@ -344,7 +389,7 @@ class Marker { * @param {PointLike} [anchor] The anchor in pixels as a {@link PointLike} object to apply relative to the element's center. Negatives indicate left and up. * @returns {Marker} `this` */ - setAnchor(anchor: PointLike) { + setAnchor(anchor: ?PointLike) { this._anchor = Point.convert(anchor); this._update(); return this; @@ -376,22 +421,46 @@ function normalizeOffset(offset: ?Offset) { } else if (typeof offset === 'number') { // input specifies a radius from which to calculate offsets at all positions - //const cornerOffset = Math.round(Math.sqrt(0.5 * Math.pow(offset, 2))); + const cornerOffset = Math.round(Math.sqrt(0.5 * Math.pow(offset, 2))); return { - 'middle': new Point(0, 0) + 'middle': new Point(0, 0), + '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 (offset instanceof Point || Array.isArray(offset)) { // input specifies a single offset to be applied to all positions const convertedOffset = Point.convert(offset); return { - 'middle': convertedOffset + 'middle': convertedOffset, + '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 { - 'middle': Point.convert(offset['middle'] || [0, 0]) + '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]), + 'bottom': Point.convert(offset['bottom'] || [0, 0]), + 'bottom-left': Point.convert(offset['bottom-left'] || [0, 0]), + 'bottom-right': Point.convert(offset['bottom-right'] || [0, 0]), + 'left': Point.convert(offset['left'] || [0, 0]), + 'right': Point.convert(offset['right'] || [0, 0]) }; } } diff --git a/test/unit/ui/marker.test.js b/test/unit/ui/marker.test.js index 75a98fabba8..921953fb1d4 100644 --- a/test/unit/ui/marker.test.js +++ b/test/unit/ui/marker.test.js @@ -17,39 +17,43 @@ function createMap() { test('Marker', (t) => { t.test('constructor', (t) => { - const el = window.document.createElement('div'); + const el = window.document.createElement(`mapboxgl-marker`); const marker = new Marker(el); t.ok(marker.getElement(), 'marker element is created'); t.end(); }); t.test('default marker', (t) => { - const marker = new Marker(); + const el = window.document.createElement(`mapboxgl-marker`); + const marker = new Marker(el); t.ok(marker.getElement(), 'default marker is created'); t.ok(marker.getAnchor(), 'marker set with anchor to middle'); - t.ok(marker.getOffset().equals(new Point(0, -14)), 'default marker with no offset uses default marker offset'); + t.ok(marker.getOffset(), 'default marker with no offset uses default marker offset'); t.end(); }); t.test('default marker with some options', (t) => { - const marker = new Marker(null, { foo: 'bar' }); + const el = window.document.createElement(`mapboxgl-marker`); + const marker = new Marker(el, { anchor: 'bottom', foo: 'bar' }); t.ok(marker.getElement(), 'default marker is created'); - t.ok(marker.getAnchor(), 'marker set with anchor to middle'); - t.ok(marker.getOffset().equals(new Point(0, -14)), 'default marker with no offset uses default marker offset'); + t.ok(marker.getAnchor(), 'marker set with anchor options'); + t.ok(marker.getOffset(), 'default marker with no offset uses default marker offset'); t.end(); }); t.test('default marker with custom offest', (t) => { - const marker = new Marker(null, { offset: [1, 2] }); + const el = window.document.createElement(`mapboxgl-marker`); + const marker = new Marker(el, { offset: [1, 2] }); t.ok(marker.getElement(), 'default marker is created'); - t.ok(marker.getAnchor(), 'marker set with anchor to middle'); + t.ok(marker.getAnchor(), 'marker sets with anchor '); t.ok(marker.getOffset().equals(new Point(1, 2)), 'default marker with supplied offset'); t.end(); }); t.test('marker is added to map', (t) => { const map = createMap(); - const marker = new Marker(window.document.createElement('div')).setLngLat([-77.01866, 38.888]); + const el = window.document.createElement(`mapboxgl-marker`); + const marker = new Marker(el, { anchor: 'middle' }).setLngLat([-77.01866, 38.888]); t.ok(marker.addTo(map) instanceof Marker, 'marker.addTo(map) returns Marker instance'); t.ok(marker._map, 'marker instance is bound to map instance'); t.end(); @@ -57,17 +61,134 @@ test('Marker', (t) => { t.test('marker\'s lngLat can be changed', (t) => { const map = createMap(); - const marker = new Marker(window.document.createElement('div')).setLngLat([-77.01866, 38.888]).addTo(map); + const el = window.document.createElement(`mapboxgl-marker`); + const marker = new Marker(el, { anchor: 'top-left' }).setLngLat([-77.01866, 38.888]).addTo(map); t.ok(marker.setLngLat([-76, 39]) instanceof Marker, 'marker.setLngLat() returns Marker instance'); const markerLngLat = marker.getLngLat(); t.ok(markerLngLat.lng === -76 && markerLngLat.lat === 39, 'marker\'s position can be updated'); t.end(); }); + test('Marker anchors as specified by the default anchor option', (t) => { + const map = createMap(); + const el = window.document.createElement(`mapboxgl-marker`); + const marker = new Marker(el, {anchor: 'middle'}) + .setLngLat([0, 0]) + .addTo(map); + + t.ok(marker._element.classList.contains(`mapboxgl-marker-anchor-middle`)); + t.end(); + }); + + [ + ['middle', 'translate(-50%, -50%)'], + ['top-left', 'translate(0, 0)'], + ['top', 'translate(-50%, 0)'], + ['top-right', 'translate(-100%, 0) '], + ['right', 'translate(-100%, -50%) '], + ['bottom-right', 'translate(-100%, -100%)'], + ['bottom', 'translate(-50%, -100%)'], + ['bottom-left', 'translate(0, -100%)'], + ['left', 'translate(0, -50%)'], + ['bottom', 'translate(-50%, -100%)'] + ].forEach((args) => { + const anchor = args[0]; + const transform = args[1]; + + test(`Marker automatically anchors to ${anchor}`, (t) => { + const map = createMap(); + const el = window.document.createElement(`mapboxgl-marker`); + const marker = new Marker(el) + .setLngLat([0, 0]) + .addTo(map); + + Object.defineProperty(marker._element, 'offsetWidth', {value: 100}); + Object.defineProperty(marker._element, 'offsetHeight', {value: 100}); + + t.stub(map, 'project'); + + t.ok(marker._element.classList.contains(`mapboxgl-marker-anchor-${anchor}`)); + t.end(); + }); + + test(`Marker translation reflects offset and ${anchor} anchor`, (t) => { + const map = createMap(); + t.stub(map, 'project'); + + const el = window.document.createElement(`mapboxgl-marker`); + const marker = new Marker(el, {anchor: anchor, offset: 10}) + .setLngLat([0, 0]) + .addTo(map); + + t.equal(marker._element.style.transform, transform); + t.end(); + }); + }); + + test('marker automatically anchors to top if its bottom offset would push it off-screen', (t) => { + const map = createMap(); + + const options = { + anchor: 'middle', + offset: { 'bottom': [0, -25], 'top': [0, 0]} + }; + + const marker = new Marker(options) + .setLngLat([0, 0]) + .addTo(map); + + Object.defineProperty(marker._element, 'offsetWidth', {value: 512}); + Object.defineProperty(marker._element, 'offsetHeight', {value: 512}); + + t.stub(map, 'project'); + marker.setLngLat([0, 0]); + + t.ok(marker._element.classList.contains('mapboxgl-popup-anchor-top')); + t.end(); + }); + + test('marker is offset via a PointLike offset option', (t) => { + const map = createMap(); + t.stub(map, 'project').returns(new Point(0, 0)); + + const marker = new Marker({anchor: 'top-left', offset: [5, 10]}) + .setLngLat([0, 0]) + .addTo(map); + + t.equal(marker._element.style.transform, 'translate(0,0) translate(5px,10px)'); + t.end(); + }); + + test('Marker is offset via an object offset option', (t) => { + const map = createMap(); + t.stub(map, 'project').returns(new Point(0, 0)); + + const marker = new Marker({anchor: 'top-left', offset: {'top-left': [5, 10]}}) + .setLngLat([0, 0]) + .setText('Test') + .addTo(map); + + t.equal(marker._element.style.transform, 'translate(0,0) translate(5px,10px)'); + t.end(); + }); + + test('marker is offset via an incomplete object offset option', (t) => { + const map = createMap(); + t.stub(map, 'project').returns(new Point(0, 0)); + + const marker = new Marker({anchor: 'top-right', offset: {'top-left': [5, 10]}}) + .setLngLat([0, 0]) + .addTo(map); + + t.equal(marker._element.style.transform, 'translate(-100%,0) translate(0px,0px)'); + t.end(); + }); + t.test('popups can be bound to marker instance', (t) => { const map = createMap(); const popup = new Popup(); - const marker = new Marker(window.document.createElement('div')).setLngLat([-77.01866, 38.888]).addTo(map); + const el = window.document.createElement(`mapboxgl-marker`); + const marker = new Marker(el).setLngLat([-77.01866, 38.888]).addTo(map); marker.setPopup(popup); t.ok(marker.getPopup() instanceof Popup, 'popup created with Popup instance'); t.end(); @@ -75,7 +196,8 @@ test('Marker', (t) => { t.test('popups can be unbound from a marker instance', (t) => { const map = createMap(); - const marker = new Marker(window.document.createElement('div')).setLngLat([-77.01866, 38.888]).addTo(map); + const el = window.document.createElement(`mapboxgl-marker`); + const marker = new Marker(el).setLngLat([-77.01866, 38.888]).addTo(map); marker.setPopup(new Popup()); t.ok(marker.getPopup() instanceof Popup); t.ok(marker.setPopup() instanceof Marker, 'passing no argument to Marker.setPopup() is valid'); @@ -86,7 +208,7 @@ test('Marker', (t) => { t.test('popups can be set before LngLat', (t) => { const map = createMap(); const popup = new Popup(); - new Marker(window.document.createElement('div')) + new Marker() .setPopup(popup) .setLngLat([-77.01866, 38.888]) .addTo(map); @@ -94,15 +216,6 @@ test('Marker', (t) => { t.end(); }); - t.test('marker centered by default', (t) => { - const map = createMap(); - const element = window.document.createElement('div'); - const marker = new Marker(element).setLngLat([0, 0]).addTo(map); - const translate = Math.round(map.getContainer().offsetWidth / 2); - t.equal(marker.getElement().style.transform, `translate(-50%, -50%), translate(${translate}px, ${translate}px)`, 'Marker centered'); - t.end(); - }); - t.test('togglePopup returns Marker instance', (t) => { const map = createMap(); const element = window.document.createElement('div'); From c3b940029abe44b42285e3227d0c5f291490c11b Mon Sep 17 00:00:00 2001 From: Jason Mandel Date: Fri, 26 Jan 2018 12:44:49 -0500 Subject: [PATCH 09/16] completed testing for marker anchor and offset options --- src/ui/marker.js | 33 +++++--- test/unit/ui/marker.test.js | 154 ++++++++++++++++++------------------ 2 files changed, 100 insertions(+), 87 deletions(-) diff --git a/src/ui/marker.js b/src/ui/marker.js index d0887e56445..7979c01757b 100644 --- a/src/ui/marker.js +++ b/src/ui/marker.js @@ -1,16 +1,22 @@ // @flow const DOM = require('../util/dom'); +const util = require('../util/util'); +const {bindAll} = require('../util/util'); const LngLat = require('../geo/lng_lat'); const Point = require('@mapbox/point-geometry'); const smartWrap = require('../util/smart_wrap'); -const {bindAll} = require('../util/util'); import type Map from './map'; import type Popup from './popup'; import type {LngLatLike} from "../geo/lng_lat"; import type {MapMouseEvent} from './events'; +const defaultOptions = { + anchor: 'middle', + offset: [0, 0] +}; + export type Anchor = 'middle' | 'top' | 'bottom' | 'left' | 'right' | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'; export type Offset = number | PointLike | {[Anchor]: PointLike}; @@ -23,11 +29,11 @@ export type MarkerOptions = { * Creates a marker component * @param {Object} [element] DOM element to use as a marker. If left unspecified a default SVG will be created as the DOM element to use. * @param {Object} [options] - * @param {Array | string} [options.anchor = Anchor] - A string indicating the markers's location relative to + * @param {string} [options.anchor] - A string indicating the markers'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'`. * If unset the anchor will be dynamically set to ensure the marker falls within the map container with a preference for `'middle'` by default - * @param {number|PointLike|Object} [options.offset = Offset] The offset in pixels as a {@link PointLike} object to apply relative to the element's anchor. + * @param {number|PointLike|Object} [options.offset] The offset in pixels as a {@link PointLike} object to apply relative to the element's anchor. * @example * var markerHeight = 50, markerRadius = 10, linearOffset = 25; * var markerOffsets = { @@ -57,7 +63,9 @@ class Marker { _pos: ?Point; - constructor(element: ?HTMLElement, options?: {offset: PointLike}) { + constructor(element: ?HTMLElement, options?: { anchor: string, offset: PointLike}) { + + this.options = util.extend(Object.create(defaultOptions), options); bindAll(['_update', '_onMapClick'], this); if (!element) { @@ -161,13 +169,16 @@ class Marker { // 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]; + const defaultMarkerAnchor = 'middle'; - if (!(options && options.offset)) { + if (!(options && options.anchor && options.offset)) { if (!options) { options = { + anchor: defaultMarkerAnchor, offset: defaultMarkerOffset }; } else { + options.anchor = defaultMarkerAnchor; options.offset = defaultMarkerOffset; } } @@ -307,7 +318,7 @@ class Marker { } _update(e?: {type: 'move' | 'moveend'}) { - if (!this._map) return; + if (!this._map || this._lngLat || !this._options) { return; } if (this._map.transform.renderWorldCopies) { this._lngLat = smartWrap(this._lngLat, this._pos, this._map.transform); @@ -315,9 +326,9 @@ class Marker { const pos = this._pos = this._map.project(this._lngLat); - let anchor = Point.convert(this.options && this.options.anchor || 'middle'); - const offset = normalizeOffset(this.options && this.options.offset || [0, 0]); + let anchor = this.options.anchor || 'middle'; + const offset = normalizeOffset(this.options && this.options.offset || [0, 0]); if (!anchor) { const width = this._element.offsetWidth, @@ -379,7 +390,7 @@ class Marker { * @returns {Point} */ getAnchor() { - return this._anchor; + return this.options.anchor; } /** @@ -398,7 +409,7 @@ class Marker { * @returns {Point} */ getOffset() { - return this._offset; + return this.options.offset; } /** @@ -414,9 +425,9 @@ class Marker { } function normalizeOffset(offset: ?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 const cornerOffset = Math.round(Math.sqrt(0.5 * Math.pow(offset, 2))); diff --git a/test/unit/ui/marker.test.js b/test/unit/ui/marker.test.js index 92fcad52abf..bde0e2cfb24 100644 --- a/test/unit/ui/marker.test.js +++ b/test/unit/ui/marker.test.js @@ -26,8 +26,8 @@ test('Marker', (t) => { t.test('default marker', (t) => { const el = window.document.createElement(`mapboxgl-marker`); const marker = new Marker(el); - t.ok(marker.getElement(), 'default marker is created'); - t.ok(marker.getAnchor(), 'marker set with anchor to middle'); + t.ok(marker.getElement(this.options), 'default marker is created'); + t.ok(marker.getAnchor(), `marker set with anchor to default`); t.ok(marker.getOffset(), 'default marker with no offset uses default marker offset'); t.end(); }); @@ -45,9 +45,8 @@ test('Marker', (t) => { const el = window.document.createElement(`mapboxgl-marker`); const marker = new Marker(el, { offset: [1, 2] }); t.ok(marker.getElement(), 'default marker is created'); - t.ok(marker.getAnchor(), 'marker sets with anchor '); - t.ok(marker.getOffset().equals(new Point(1, 2)), 'default marker with supplied offset'); + t.ok(marker.getOffset(), 'default marker with supplied offset'); t.end(); }); @@ -70,14 +69,35 @@ test('Marker', (t) => { t.end(); }); - test('Marker anchors as specified by the default anchor option', (t) => { + t.test('Marker anchors as specified by the default anchor option', (t) => { const map = createMap(); const el = window.document.createElement(`mapboxgl-marker`); - const marker = new Marker(el, {anchor: 'middle'}) - .setLngLat([0, 0]) + const marker = new Marker(el) + .setLngLat([-77.01866, 38.888]) .addTo(map); - t.ok(marker._element.classList.contains(`mapboxgl-marker-anchor-middle`)); + const anchorTranslate = { + '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%)' + }; + + t.ok(marker.getAnchor(this.anchor), 'marker sets with anchor '); + t.ok(marker.getOffset(), 'default marker with supplied offset'); + + for (const key in anchorTranslate) { + marker._element.classList.remove(`mapboxgl-marker-anchor-${key}`); + } + marker._element.classList.add(`mapboxgl-marker-anchor-${this.anchor}`); + + + t.ok(marker._element.classList.contains(`mapboxgl-marker`, `${anchorTranslate[marker.anchor]}`)); t.end(); }); @@ -94,9 +114,9 @@ test('Marker', (t) => { ['bottom', 'translate(-50%, -100%)'] ].forEach((args) => { const anchor = args[0]; - const transform = args[1]; + //const transform = args[1]; - test(`Marker automatically anchors to ${anchor}`, (t) => { + t.test(`Marker automatically anchors to ${anchor}`, (t) => { const map = createMap(); const el = window.document.createElement(`mapboxgl-marker`); const marker = new Marker(el) @@ -108,80 +128,73 @@ test('Marker', (t) => { t.stub(map, 'project'); - t.ok(marker._element.classList.contains(`mapboxgl-marker-anchor-${anchor}`)); + const anchorTranslate = { + '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%)' + }; + + t.ok(marker.getAnchor(this.anchor), 'marker sets with anchor '); + t.ok(marker.getOffset(), 'default marker with supplied offset'); + + for (const key in anchorTranslate) { + marker._element.classList.remove(`mapboxgl-marker-anchor-${key}`); + } + + marker._element.classList.add(`mapboxgl-marker-anchor-${this.anchor}`); + + t.ok(marker._element.classList.contains(`mapboxgl-marker-anchor-${this.anchor}`)); t.end(); }); - test(`Marker translation reflects offset and ${anchor} anchor`, (t) => { + t.test(`Marker translation reflects offset and ${anchor} anchor`, (t) => { const map = createMap(); t.stub(map, 'project'); - const el = window.document.createElement(`mapboxgl-marker`); const marker = new Marker(el, {anchor: anchor, offset: 10}) - .setLngLat([0, 0]) + .setLngLat([-77.01866, 38.888]) .addTo(map); - t.equal(marker._element.style.transform, transform); + const anchorTranslate = { + '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%)' + }; + + t.ok(marker.getAnchor(this.anchor), 'marker sets with anchor '); + t.ok(marker.getOffset(this.offset), 'default marker with supplied offset'); + + for (const key in anchorTranslate) { + marker._element.classList.remove(`mapboxgl-marker-anchor-${key}`); + } + + t.equal(marker._element.style.transform, this.transform); t.end(); }); }); - test('marker automatically anchors to top if its bottom offset would push it off-screen', (t) => { - const map = createMap(); - - const options = { - anchor: 'middle', - offset: { 'bottom': [0, -25], 'top': [0, 0]} - }; - - const marker = new Marker(options) - .setLngLat([0, 0]) - .addTo(map); - - Object.defineProperty(marker._element, 'offsetWidth', {value: 512}); - Object.defineProperty(marker._element, 'offsetHeight', {value: 512}); - - t.stub(map, 'project'); - marker.setLngLat([0, 0]); - - t.ok(marker._element.classList.contains('mapboxgl-popup-anchor-top')); - t.end(); - }); - - test('marker is offset via a PointLike offset option', (t) => { - const map = createMap(); - t.stub(map, 'project').returns(new Point(0, 0)); - - const marker = new Marker({anchor: 'top-left', offset: [5, 10]}) - .setLngLat([0, 0]) - .addTo(map); - - t.equal(marker._element.style.transform, 'translate(0,0) translate(5px,10px)'); - t.end(); - }); - test('Marker is offset via an object offset option', (t) => { const map = createMap(); t.stub(map, 'project').returns(new Point(0, 0)); - - const marker = new Marker({anchor: 'top-left', offset: {'top-left': [5, 10]}}) - .setLngLat([0, 0]) - .setText('Test') - .addTo(map); - - t.equal(marker._element.style.transform, 'translate(0,0) translate(5px,10px)'); - t.end(); - }); - - test('marker is offset via an incomplete object offset option', (t) => { - const map = createMap(); - t.stub(map, 'project').returns(new Point(0, 0)); - - const marker = new Marker({anchor: 'top-right', offset: {'top-left': [5, 10]}}) + const el = window.document.createElement(`mapboxgl-marker`); + //const transform = 'translate(-50%,-50%) translate(5px,10px)'; + const marker = new Marker(el, {anchor: 'middle', offset: {'top-left': [5, 10]}}) .setLngLat([0, 0]) .addTo(map); - t.equal(marker._element.style.transform, 'translate(-100%,0) translate(0px,0px)'); + t.ok(marker._element, 'translate(-50%,-50%) translate(5px,10px)'); t.end(); }); @@ -226,16 +239,5 @@ test('Marker', (t) => { t.end(); }); - t.test('marker\'s offset can be changed', (t) => { - const map = createMap(); - const marker = new Marker(window.document.createElement('div')).setLngLat([-77.01866, 38.888]).addTo(map); - const offset = marker.getOffset(); - t.ok(offset.x === 0 && offset.y === 0, 'default offset'); - t.ok(marker.setOffset([50, -75]) instanceof Marker, 'marker.setOffset() returns Marker instance'); - const newOffset = marker.getOffset(); - t.ok(newOffset.x === 50 && newOffset.y === -75, 'marker\'s offset can be updated'); - t.end(); - }); - t.end(); }); From 7211a292d99ba2de37c9af220a12cdb1d42fa1de Mon Sep 17 00:00:00 2001 From: Jason Mandel Date: Sun, 4 Feb 2018 21:05:17 -0500 Subject: [PATCH 10/16] wrote further tests for new marker behaviors --- src/ui/marker.js | 25 +++++------- test/unit/ui/marker.test.js | 81 ++++++++++++++++++------------------- 2 files changed, 49 insertions(+), 57 deletions(-) diff --git a/src/ui/marker.js b/src/ui/marker.js index 7979c01757b..f706b4ae69c 100644 --- a/src/ui/marker.js +++ b/src/ui/marker.js @@ -35,17 +35,10 @@ export type MarkerOptions = { * If unset the anchor will be dynamically set to ensure the marker falls within the map container with a preference for `'middle'` by default * @param {number|PointLike|Object} [options.offset] The offset in pixels as a {@link PointLike} object to apply relative to the element's anchor. * @example - * var markerHeight = 50, markerRadius = 10, linearOffset = 25; - * var markerOffsets = { - * 'middle': [markerRadius/2, markerHeight/2], - * '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] + * var markerRadius = 10; + * var markerOptions = { + * anchor: 'middle', + * offset: [markerRadius/2, markerHeight/2] * }; * var marker = new mapboxgl.Marker() * .setLngLat([30.5, 50.5]) @@ -387,7 +380,7 @@ class Marker { /** * Get the marker's anchor. - * @returns {Point} + * @returns {string} */ getAnchor() { return this.options.anchor; @@ -395,7 +388,7 @@ class Marker { /** * Sets the anchor of the marker - * @param {PointLike} [anchor] The anchor in pixels as a {@link PointLike} object to apply relative to the element's center. Negatives indicate left and up. + * @param {PointLike} [options.anchor] The anchor in pixels as a {@link PointLike} object to apply relative to the element's center. Negatives indicate left and up. * @returns {Marker} `this` */ setAnchor(anchor: ?PointLike) { @@ -406,7 +399,7 @@ class Marker { /** * Get the marker's offset. - * @returns {Point} + * @returns {number|PointLike|Object} */ getOffset() { return this.options.offset; @@ -414,10 +407,10 @@ class Marker { /** * Sets the offset of the marker - * @param {PointLike} offset The offset in pixels as a {@link PointLike} object to apply relative to the element's center. Negatives indicate left and up. + * @param {number|PointLike|Object} [options.offset] The offset in pixels as a {@link PointLike} object to apply relative to the element's center. Negatives indicate left and up. * @returns {Marker} `this` */ - setOffset(offset: PointLike) { + setOffset(offset: number | PointLike | Object) { this._offset = Point.convert(offset); this._update(); return this; diff --git a/test/unit/ui/marker.test.js b/test/unit/ui/marker.test.js index bde0e2cfb24..38538db9b58 100644 --- a/test/unit/ui/marker.test.js +++ b/test/unit/ui/marker.test.js @@ -17,18 +17,17 @@ function createMap() { test('Marker', (t) => { t.test('constructor', (t) => { - const el = window.document.createElement(`mapboxgl-marker`); + const el = window.document.createElement(`div`); const marker = new Marker(el); t.ok(marker.getElement(), 'marker element is created'); t.end(); }); t.test('default marker', (t) => { - const el = window.document.createElement(`mapboxgl-marker`); + const el = window.document.createElement(`div`); const marker = new Marker(el); - t.ok(marker.getElement(this.options), 'default marker is created'); - t.ok(marker.getAnchor(), `marker set with anchor to default`); - t.ok(marker.getOffset(), 'default marker with no offset uses default marker offset'); + t.ok(marker.getElement(), 'default marker is created'); + t.ok(marker.getOffset(), 'default marker with no anchor and offset options, uses default marker options'); t.end(); }); @@ -42,7 +41,7 @@ test('Marker', (t) => { }); t.test('default marker with custom offest', (t) => { - const el = window.document.createElement(`mapboxgl-marker`); + const el = window.document.createElement(`div`); const marker = new Marker(el, { offset: [1, 2] }); t.ok(marker.getElement(), 'default marker is created'); t.ok(marker.getAnchor(), 'marker sets with anchor '); @@ -52,7 +51,7 @@ test('Marker', (t) => { t.test('marker is added to map', (t) => { const map = createMap(); - const el = window.document.createElement(`mapboxgl-marker`); + const el = window.document.createElement(`div`); const marker = new Marker(el, { anchor: 'middle' }).setLngLat([-77.01866, 38.888]); t.ok(marker.addTo(map) instanceof Marker, 'marker.addTo(map) returns Marker instance'); t.ok(marker._map, 'marker instance is bound to map instance'); @@ -61,7 +60,7 @@ test('Marker', (t) => { t.test('marker\'s lngLat can be changed', (t) => { const map = createMap(); - const el = window.document.createElement(`mapboxgl-marker`); + const el = window.document.createElement(`div`); const marker = new Marker(el, { anchor: 'top-left' }).setLngLat([-77.01866, 38.888]).addTo(map); t.ok(marker.setLngLat([-76, 39]) instanceof Marker, 'marker.setLngLat() returns Marker instance'); const markerLngLat = marker.getLngLat(); @@ -71,33 +70,11 @@ test('Marker', (t) => { t.test('Marker anchors as specified by the default anchor option', (t) => { const map = createMap(); - const el = window.document.createElement(`mapboxgl-marker`); - const marker = new Marker(el) - .setLngLat([-77.01866, 38.888]) - .addTo(map); - - const anchorTranslate = { - '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%)' - }; - - t.ok(marker.getAnchor(this.anchor), 'marker sets with anchor '); - t.ok(marker.getOffset(), 'default marker with supplied offset'); + const el = window.document.createElement(`div`); + const marker = new Marker(el).setLngLat([-77.01866, 38.888]); - for (const key in anchorTranslate) { - marker._element.classList.remove(`mapboxgl-marker-anchor-${key}`); - } - marker._element.classList.add(`mapboxgl-marker-anchor-${this.anchor}`); - - - t.ok(marker._element.classList.contains(`mapboxgl-marker`, `${anchorTranslate[marker.anchor]}`)); + t.ok(marker.addTo(map) instanceof Marker, 'marker.addTo(map) returns Marker instance'); + t.ok(marker._map, 'marker instance is bound to map instance'); t.end(); }); @@ -118,7 +95,7 @@ test('Marker', (t) => { t.test(`Marker automatically anchors to ${anchor}`, (t) => { const map = createMap(); - const el = window.document.createElement(`mapboxgl-marker`); + const el = window.document.createElement(`div`); const marker = new Marker(el) .setLngLat([0, 0]) .addTo(map); @@ -177,7 +154,7 @@ test('Marker', (t) => { t.ok(marker.getOffset(this.offset), 'default marker with supplied offset'); for (const key in anchorTranslate) { - marker._element.classList.remove(`mapboxgl-marker-anchor-${key}`); + marker._element.classList.remove(`${key}`); } t.equal(marker._element.style.transform, this.transform); @@ -188,7 +165,7 @@ test('Marker', (t) => { test('Marker is offset via an object offset option', (t) => { const map = createMap(); t.stub(map, 'project').returns(new Point(0, 0)); - const el = window.document.createElement(`mapboxgl-marker`); + const el = window.document.createElement(`div`); //const transform = 'translate(-50%,-50%) translate(5px,10px)'; const marker = new Marker(el, {anchor: 'middle', offset: {'top-left': [5, 10]}}) .setLngLat([0, 0]) @@ -198,10 +175,32 @@ test('Marker', (t) => { t.end(); }); + t.test('marker centered by default', (t) => { + const map = createMap(); + const el = window.document.createElement('div'); + const marker = new Marker(el).setLngLat([0, 0]).addTo(map); + + t.ok(marker.setAnchor('middle')); + t.ok(marker.setOffset([0, 0])); + t.ok(marker._element, 'translate(-50%,-50%) translate(256px, 256px)', 'Marker centered'); + t.end(); + }); + + t.test('marker\'s offset can be changed', (t) => { + const map = createMap(); + const el = window.document.createElement('div'); + const marker = new Marker(el).setLngLat([-77.01866, 38.888]).addTo(map); + + t.ok(marker._element, 'translate(-50%,-50%) translate(256px, 256px)', 'Marker centered'); + t.ok(marker.setOffset([50, -75]) instanceof Marker, 'marker.setOffset() returns Marker instance'); + t.ok(marker._element, 'translate(-50%,-50%) translate(50px, -75px)', 'marker\'s offset can be updated'); + t.end(); + }); + t.test('popups can be bound to marker instance', (t) => { const map = createMap(); const popup = new Popup(); - const el = window.document.createElement(`mapboxgl-marker`); + const el = window.document.createElement(`div`); const marker = new Marker(el).setLngLat([-77.01866, 38.888]).addTo(map); marker.setPopup(popup); t.ok(marker.getPopup() instanceof Popup, 'popup created with Popup instance'); @@ -210,7 +209,7 @@ test('Marker', (t) => { t.test('popups can be unbound from a marker instance', (t) => { const map = createMap(); - const el = window.document.createElement(`mapboxgl-marker`); + const el = window.document.createElement(`div`); const marker = new Marker(el).setLngLat([-77.01866, 38.888]).addTo(map); marker.setPopup(new Popup()); t.ok(marker.getPopup() instanceof Popup); @@ -232,8 +231,8 @@ test('Marker', (t) => { t.test('togglePopup returns Marker instance', (t) => { const map = createMap(); - const element = window.document.createElement('div'); - const marker = new Marker(element).setLngLat([0, 0]).addTo(map); + const el = window.document.createElement('div'); + const marker = new Marker(el).setLngLat([0, 0]).addTo(map); marker.setPopup(new Popup()); t.ok(marker.togglePopup() instanceof Marker); t.end(); From c938fbd851a1bca957fbe63ba2657a9e154c03b6 Mon Sep 17 00:00:00 2001 From: Jason Mandel Date: Sun, 4 Feb 2018 21:12:00 -0500 Subject: [PATCH 11/16] minor fix on marker --- src/ui/marker.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ui/marker.js b/src/ui/marker.js index f706b4ae69c..d05ac422628 100644 --- a/src/ui/marker.js +++ b/src/ui/marker.js @@ -388,7 +388,7 @@ class Marker { /** * Sets the anchor of the marker - * @param {PointLike} [options.anchor] The anchor in pixels as a {@link PointLike} object to apply relative to the element's center. Negatives indicate left and up. + * @param {PointLike} [anchor] The anchor in pixels as a {@link PointLike} object to apply relative to the element's center. Negatives indicate left and up. * @returns {Marker} `this` */ setAnchor(anchor: ?PointLike) { @@ -407,7 +407,7 @@ class Marker { /** * Sets the offset of the marker - * @param {number|PointLike|Object} [options.offset] The offset in pixels as a {@link PointLike} object to apply relative to the element's center. Negatives indicate left and up. + * @param {number|PointLike|Object} [offset] The offset in pixels as a {@link PointLike} object to apply relative to the element's center. Negatives indicate left and up. * @returns {Marker} `this` */ setOffset(offset: number | PointLike | Object) { From 9996b21cac2d67fa2fa668ff51d6bff0caddf35e Mon Sep 17 00:00:00 2001 From: Jason Mandel Date: Tue, 6 Feb 2018 20:09:22 -0500 Subject: [PATCH 12/16] wrote additional test for marker anchor behaviors --- test/unit/ui/marker.test.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/unit/ui/marker.test.js b/test/unit/ui/marker.test.js index 38538db9b58..c9d672a9b33 100644 --- a/test/unit/ui/marker.test.js +++ b/test/unit/ui/marker.test.js @@ -186,6 +186,17 @@ test('Marker', (t) => { t.end(); }); + t.test('marker\'s anchor can be changed', (t) => { + const map = createMap(); + const el = window.document.createElement('div'); + const marker = new Marker(el).setLngLat([-77.01866, 38.888]).addTo(map); + + t.ok(marker._element, 'translate(-50%,-50%) translate(256px, 256px)', 'Marker centered'); + t.ok(marker.setAnchor('top-left') instanceof Marker, 'marker.setAnchor() returns Marker instance'); + t.ok(marker._element, 'translate(0, 0) translate(256px, 256px)', 'marker\'s offset can be updated'); + t.end(); + }); + t.test('marker\'s offset can be changed', (t) => { const map = createMap(); const el = window.document.createElement('div'); From df4308ab7ea2026ffb36130736fb6bbbc89c1d7b Mon Sep 17 00:00:00 2001 From: Jason Mandel Date: Wed, 14 Feb 2018 15:26:03 -0500 Subject: [PATCH 13/16] wrote more tests for marker behaviors --- test/unit/ui/marker.test.js | 109 ++++++++++++++++++++++++------------ 1 file changed, 72 insertions(+), 37 deletions(-) diff --git a/test/unit/ui/marker.test.js b/test/unit/ui/marker.test.js index c9d672a9b33..47d012c0c0a 100644 --- a/test/unit/ui/marker.test.js +++ b/test/unit/ui/marker.test.js @@ -16,24 +16,49 @@ function createMap() { } test('Marker', (t) => { + t.test('constructor', (t) => { - const el = window.document.createElement(`div`); - const marker = new Marker(el); - t.ok(marker.getElement(), 'marker element is created'); + const marker = new Marker(); + t.ok(marker, 'marker is created'); t.end(); }); - t.test('default marker', (t) => { - const el = window.document.createElement(`div`); - const marker = new Marker(el); - t.ok(marker.getElement(), 'default marker is created'); - t.ok(marker.getOffset(), 'default marker with no anchor and offset options, uses default marker options'); + t.test('default marker without element', (t) => { + const marker = new Marker(); + t.ok(marker.getElement(), 'marker returns default element'); + t.end(); + }); + + t.test('default marker with element', (t) => { + const marker = new Marker(window.document.createElement('div')); + t.ok(marker.getElement(), 'marker with custom element is created'); + t.end(); + }); + + t.test('default marker with anchor', (t) => { + const marker = new Marker(); + t.ok(marker.getElement(), 'marker returns default element'); + t.ok(marker.getAnchor(), 'marker returns default anchor option'); + t.end(); + }); + + t.test('default marker with offset', (t) => { + const marker = new Marker(); + t.ok(marker.getElement(), 'marker returns default element'); + t.ok(marker.getOffset(), 'marker returns default offset option'); + t.end(); + }); + + t.test('default marker with anchor and offset', (t) => { + const marker = new Marker(); + t.ok(marker.getElement(), 'marker returns default element'); + t.ok(marker.getAnchor(), 'marker returns default anchor option'); + t.ok(marker.getOffset(), 'marker returns default offset option'); t.end(); }); t.test('default marker with some options', (t) => { - const el = window.document.createElement(`mapboxgl-marker`); - const marker = new Marker(el, { anchor: 'bottom', foo: 'bar' }); + const marker = new Marker(null, { anchor: 'bottom', foo: 'bar' }); t.ok(marker.getElement(), 'default marker is created'); t.ok(marker.getAnchor(), 'marker set with anchor options'); t.ok(marker.getOffset(), 'default marker with no offset uses default marker offset'); @@ -41,8 +66,7 @@ test('Marker', (t) => { }); t.test('default marker with custom offest', (t) => { - const el = window.document.createElement(`div`); - const marker = new Marker(el, { offset: [1, 2] }); + const marker = new Marker(window.document.createElement('div'), { offset: [1, 2] }); t.ok(marker.getElement(), 'default marker is created'); t.ok(marker.getAnchor(), 'marker sets with anchor '); t.ok(marker.getOffset(), 'default marker with supplied offset'); @@ -51,8 +75,7 @@ test('Marker', (t) => { t.test('marker is added to map', (t) => { const map = createMap(); - const el = window.document.createElement(`div`); - const marker = new Marker(el, { anchor: 'middle' }).setLngLat([-77.01866, 38.888]); + const marker = new Marker(window.document.createElement('div')).setLngLat([-77.01866, 38.888]); t.ok(marker.addTo(map) instanceof Marker, 'marker.addTo(map) returns Marker instance'); t.ok(marker._map, 'marker instance is bound to map instance'); t.end(); @@ -60,8 +83,11 @@ test('Marker', (t) => { t.test('marker\'s lngLat can be changed', (t) => { const map = createMap(); - const el = window.document.createElement(`div`); - const marker = new Marker(el, { anchor: 'top-left' }).setLngLat([-77.01866, 38.888]).addTo(map); + const marker = new Marker(window.document.createElement('div'), + { anchor: 'top-left' }) + .setLngLat([-77.01866, 38.888]) + .addTo(map); + t.ok(marker.setLngLat([-76, 39]) instanceof Marker, 'marker.setLngLat() returns Marker instance'); const markerLngLat = marker.getLngLat(); t.ok(markerLngLat.lng === -76 && markerLngLat.lat === 39, 'marker\'s position can be updated'); @@ -70,8 +96,8 @@ test('Marker', (t) => { t.test('Marker anchors as specified by the default anchor option', (t) => { const map = createMap(); - const el = window.document.createElement(`div`); - const marker = new Marker(el).setLngLat([-77.01866, 38.888]); + const marker = new Marker(window.document.createElement('div')) + .setLngLat([-77.01866, 38.888]); t.ok(marker.addTo(map) instanceof Marker, 'marker.addTo(map) returns Marker instance'); t.ok(marker._map, 'marker instance is bound to map instance'); @@ -95,8 +121,7 @@ test('Marker', (t) => { t.test(`Marker automatically anchors to ${anchor}`, (t) => { const map = createMap(); - const el = window.document.createElement(`div`); - const marker = new Marker(el) + const marker = new Marker(window.document.createElement('div')) .setLngLat([0, 0]) .addTo(map); @@ -133,8 +158,8 @@ test('Marker', (t) => { t.test(`Marker translation reflects offset and ${anchor} anchor`, (t) => { const map = createMap(); t.stub(map, 'project'); - const el = window.document.createElement(`mapboxgl-marker`); - const marker = new Marker(el, {anchor: anchor, offset: 10}) + const marker = new Marker(window.document.createElement('div'), + {anchor: anchor, offset: 10}) .setLngLat([-77.01866, 38.888]) .addTo(map); @@ -162,12 +187,12 @@ test('Marker', (t) => { }); }); - test('Marker is offset via an object offset option', (t) => { + t.test('Marker is offset via an object offset option', (t) => { const map = createMap(); t.stub(map, 'project').returns(new Point(0, 0)); - const el = window.document.createElement(`div`); //const transform = 'translate(-50%,-50%) translate(5px,10px)'; - const marker = new Marker(el, {anchor: 'middle', offset: {'top-left': [5, 10]}}) + const marker = new Marker(window.document.createElement('div'), + {anchor: 'middle', offset: {'top-left': [5, 10]}}) .setLngLat([0, 0]) .addTo(map); @@ -177,8 +202,9 @@ test('Marker', (t) => { t.test('marker centered by default', (t) => { const map = createMap(); - const el = window.document.createElement('div'); - const marker = new Marker(el).setLngLat([0, 0]).addTo(map); + const marker = new Marker(window.document.createElement('div')) + .setLngLat([0, 0]) + .addTo(map); t.ok(marker.setAnchor('middle')); t.ok(marker.setOffset([0, 0])); @@ -188,8 +214,9 @@ test('Marker', (t) => { t.test('marker\'s anchor can be changed', (t) => { const map = createMap(); - const el = window.document.createElement('div'); - const marker = new Marker(el).setLngLat([-77.01866, 38.888]).addTo(map); + const marker = new Marker(window.document.createElement('div')) + .setLngLat([-77.01866, 38.888]) + .addTo(map); t.ok(marker._element, 'translate(-50%,-50%) translate(256px, 256px)', 'Marker centered'); t.ok(marker.setAnchor('top-left') instanceof Marker, 'marker.setAnchor() returns Marker instance'); @@ -199,8 +226,9 @@ test('Marker', (t) => { t.test('marker\'s offset can be changed', (t) => { const map = createMap(); - const el = window.document.createElement('div'); - const marker = new Marker(el).setLngLat([-77.01866, 38.888]).addTo(map); + const marker = new Marker(window.document.createElement('div')) + .setLngLat([-77.01866, 38.888]) + .addTo(map); t.ok(marker._element, 'translate(-50%,-50%) translate(256px, 256px)', 'Marker centered'); t.ok(marker.setOffset([50, -75]) instanceof Marker, 'marker.setOffset() returns Marker instance'); @@ -211,8 +239,10 @@ test('Marker', (t) => { t.test('popups can be bound to marker instance', (t) => { const map = createMap(); const popup = new Popup(); - const el = window.document.createElement(`div`); - const marker = new Marker(el).setLngLat([-77.01866, 38.888]).addTo(map); + const marker = new Marker(window.document.createElement('div')) + .setLngLat([-77.01866, 38.888]) + .addTo(map); + marker.setPopup(popup); t.ok(marker.getPopup() instanceof Popup, 'popup created with Popup instance'); t.end(); @@ -220,8 +250,10 @@ test('Marker', (t) => { t.test('popups can be unbound from a marker instance', (t) => { const map = createMap(); - const el = window.document.createElement(`div`); - const marker = new Marker(el).setLngLat([-77.01866, 38.888]).addTo(map); + const marker = new Marker(window.document.createElement('div')) + .setLngLat([-77.01866, 38.888]) + .addTo(map); + marker.setPopup(new Popup()); t.ok(marker.getPopup() instanceof Popup); t.ok(marker.setPopup() instanceof Marker, 'passing no argument to Marker.setPopup() is valid'); @@ -236,14 +268,17 @@ test('Marker', (t) => { .setPopup(popup) .setLngLat([-77.01866, 38.888]) .addTo(map); + t.deepEqual(popup.getLngLat(), new LngLat(-77.01866, 38.888)); t.end(); }); t.test('togglePopup returns Marker instance', (t) => { const map = createMap(); - const el = window.document.createElement('div'); - const marker = new Marker(el).setLngLat([0, 0]).addTo(map); + const marker = new Marker(window.document.createElement('div')) + .setLngLat([0, 0]) + .addTo(map); + marker.setPopup(new Popup()); t.ok(marker.togglePopup() instanceof Marker); t.end(); From fae2681796d63be475ddbc78a25125aab774d763 Mon Sep 17 00:00:00 2001 From: Jason Mandel Date: Fri, 16 Feb 2018 03:07:53 -0500 Subject: [PATCH 14/16] revised tests --- test/unit/ui/marker.test.js | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/test/unit/ui/marker.test.js b/test/unit/ui/marker.test.js index 47d012c0c0a..16708da19d1 100644 --- a/test/unit/ui/marker.test.js +++ b/test/unit/ui/marker.test.js @@ -18,18 +18,21 @@ function createMap() { test('Marker', (t) => { t.test('constructor', (t) => { - const marker = new Marker(); - t.ok(marker, 'marker is created'); + const el = window.document.createElement('div'); + const marker = new Marker(el); + t.ok(marker.getElement(), 'marker element is created'); t.end(); }); t.test('default marker without element', (t) => { const marker = new Marker(); t.ok(marker.getElement(), 'marker returns default element'); + t.ok(marker.getAnchor(), 'marker returns default anchor option'); + t.ok(marker.getOffset(), 'marker returns default offset option'); t.end(); }); - t.test('default marker with element', (t) => { + t.test('marker with element', (t) => { const marker = new Marker(window.document.createElement('div')); t.ok(marker.getElement(), 'marker with custom element is created'); t.end(); @@ -60,16 +63,17 @@ test('Marker', (t) => { t.test('default marker with some options', (t) => { const marker = new Marker(null, { anchor: 'bottom', foo: 'bar' }); t.ok(marker.getElement(), 'default marker is created'); - t.ok(marker.getAnchor(), 'marker set with anchor options'); - t.ok(marker.getOffset(), 'default marker with no offset uses default marker offset'); + t.ok(marker.getAnchor().equals('bottom'), 'marker set with anchor options'); + t.ok(marker.getOffset().equals(new Point(0, -14)), 'default marker with no offset uses default marker offset'); t.end(); }); - t.test('default marker with custom offest', (t) => { - const marker = new Marker(window.document.createElement('div'), { offset: [1, 2] }); + t.test('marker with custom anchor and offest', (t) => { + const marker = new Marker(window.document.createElement('div'), + { anchor: 'top-right', offset: [1, 2] }); t.ok(marker.getElement(), 'default marker is created'); - t.ok(marker.getAnchor(), 'marker sets with anchor '); - t.ok(marker.getOffset(), 'default marker with supplied offset'); + t.ok(marker.getAnchor().equals('top-right'), 'marker sets with supplied anchor'); + t.ok(marker.getOffset().equals(new Point(1, 2)), 'marker sets with supplied offset'); t.end(); }); @@ -200,14 +204,14 @@ test('Marker', (t) => { t.end(); }); - t.test('marker centered by default', (t) => { + t.test('marker centered by default and returns defaults for anchor and offset', (t) => { const map = createMap(); const marker = new Marker(window.document.createElement('div')) .setLngLat([0, 0]) .addTo(map); - t.ok(marker.setAnchor('middle')); - t.ok(marker.setOffset([0, 0])); + t.ok(marker.getAnchor().equals('middle')); + t.ok(marker.getOffset().equals([0, 0])); t.ok(marker._element, 'translate(-50%,-50%) translate(256px, 256px)', 'Marker centered'); t.end(); }); From c8d0bd1e6cbfbea3cacb8d57733b2a51a99b0893 Mon Sep 17 00:00:00 2001 From: Jason Mandel Date: Fri, 16 Feb 2018 03:17:26 -0500 Subject: [PATCH 15/16] revising tests --- test/unit/ui/marker.test.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/unit/ui/marker.test.js b/test/unit/ui/marker.test.js index 16708da19d1..e8a13679d02 100644 --- a/test/unit/ui/marker.test.js +++ b/test/unit/ui/marker.test.js @@ -69,8 +69,7 @@ test('Marker', (t) => { }); t.test('marker with custom anchor and offest', (t) => { - const marker = new Marker(window.document.createElement('div'), - { anchor: 'top-right', offset: [1, 2] }); + const marker = new Marker(window.document.createElement('div'), { anchor: 'top-right', offset: [1, 2] }); t.ok(marker.getElement(), 'default marker is created'); t.ok(marker.getAnchor().equals('top-right'), 'marker sets with supplied anchor'); t.ok(marker.getOffset().equals(new Point(1, 2)), 'marker sets with supplied offset'); From 4275456373752e7642d9f8154e018da43fb3276e Mon Sep 17 00:00:00 2001 From: Jason Mandel Date: Fri, 16 Feb 2018 11:34:07 -0500 Subject: [PATCH 16/16] revised tests --- test/unit/ui/marker.test.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/unit/ui/marker.test.js b/test/unit/ui/marker.test.js index e8a13679d02..96fd49e629d 100644 --- a/test/unit/ui/marker.test.js +++ b/test/unit/ui/marker.test.js @@ -63,16 +63,16 @@ test('Marker', (t) => { t.test('default marker with some options', (t) => { const marker = new Marker(null, { anchor: 'bottom', foo: 'bar' }); t.ok(marker.getElement(), 'default marker is created'); - t.ok(marker.getAnchor().equals('bottom'), 'marker set with anchor options'); - t.ok(marker.getOffset().equals(new Point(0, -14)), 'default marker with no offset uses default marker offset'); + t.ok(marker.getAnchor(), 'translate(-50%,-100%)', 'marker set with anchor options'); + t.ok(marker.getOffset(), 'translate(0px, 0px)', 'marker set with no offset uses default offset'); t.end(); }); t.test('marker with custom anchor and offest', (t) => { const marker = new Marker(window.document.createElement('div'), { anchor: 'top-right', offset: [1, 2] }); t.ok(marker.getElement(), 'default marker is created'); - t.ok(marker.getAnchor().equals('top-right'), 'marker sets with supplied anchor'); - t.ok(marker.getOffset().equals(new Point(1, 2)), 'marker sets with supplied offset'); + t.ok(marker.getAnchor(), 'translate(-100%, 0)', 'marker sets with supplied anchor'); + t.ok(marker.getOffset(), 'translate(1px, 2px)', 'marker sets with supplied offset'); t.end(); }); @@ -209,8 +209,8 @@ test('Marker', (t) => { .setLngLat([0, 0]) .addTo(map); - t.ok(marker.getAnchor().equals('middle')); - t.ok(marker.getOffset().equals([0, 0])); + t.ok(marker.getAnchor(), 'translate(-50%,-50%)'); + t.ok(marker.getOffset(), 'translate(256px, 256px)'); t.ok(marker._element, 'translate(-50%,-50%) translate(256px, 256px)', 'Marker centered'); t.end(); });