14

I am trying to implement a custom infoBubble that has the box opening to the side of a marker rather than the default position of on top. This has turned out to be harder than expected.

Using the normal infoWindow you can use pixelOffset. See here for the documentation

Using infoBubble this does not seem to be the case. Is there anyway of using pixelOffset in an infoBubble, or something that will do the same thing?

I have found this very difficult to search for, as using a google search such as this returns no relevant results Google Search

Below is all my resources I have been using.

  • Example of infoBubble here.

  • My JavaScript to setup the map and infoBubble here.

And now my javascript here just in-case the jsfiddle link is broken.

<script type="text/javascript">

    $(document).ready(function () {
        init();
    });

    function init() {

        //Setup the map
        var googleMapOptions = {
            center: new google.maps.LatLng(53.5167, -1.1333),
            zoom: 13,
            mapTypeId: google.maps.MapTypeId.ROADMAP
        };

        //Start the map
        var map = new google.maps.Map(document.getElementById("map_canvas"),
        googleMapOptions);

        var marker = new google.maps.Marker({
            position: new google.maps.LatLng(53.5267, -1.1333),
            title: "Just a test"
        });

        marker.setMap(map);

        infoBubble = new InfoBubble({
            map: map,
            content: '<div class="phoneytext">Some label</div>',
            //position: new google.maps.LatLng(-35, 151),
            shadowStyle: 1,
            padding: '10px',
            //backgroundColor: 'rgb(57,57,57)',
            borderRadius: 5,
            minWidth: 200,
            arrowSize: 10,
            borderWidth: 1,
            borderColor: '#2c2c2c',
            disableAutoPan: true,
            hideCloseButton: false,
            arrowPosition: 7,
            backgroundClassName: 'phoney',
            pixelOffset: new google.maps.Size(130, 120),
            arrowStyle: 2
        });
        infoBubble.open(map, marker);

    }
</script>

Update

To help with answering this question i have put together a test case here. The important lines are lines 38 & 39, which should specify where to position the label.

Update 2

For the bounty to be awarded i need to see an example of the infoBubble positioned away from its default position above the marker. Preferably to the right hand side of the marker.

Update 3

I have removed the testcase from update 1 because it is hosted on my old company's servers.

Undefined
  • 11,234
  • 5
  • 37
  • 62

6 Answers6

10

This is my solution.

In InfoBubble library

replace

entire InfoBubble.prototype.draw method

with

/*
 * Sets InfoBubble Position
 * */
InfoBubble.prototype.setBubbleOffset = function(xOffset, yOffset) {
  this.bubbleOffsetX = parseInt(xOffset);
  this.bubbleOffsetY = parseInt(yOffset);
}


/*
 * Gets InfoBubble Position
 * */
InfoBubble.prototype.getBubbleOffset = function() {
  return {
    x: this.bubbleOffsetX || 0,
    y: this.bubbleOffsetY || 0
  }
}

/**
 * Draw the InfoBubble
 * Implementing the OverlayView interface
 */
InfoBubble.prototype.draw = function() {
  var projection = this.getProjection();

  if (!projection) {
    // The map projection is not ready yet so do nothing
    return;
  }

  var latLng = /** @type {google.maps.LatLng} */ (this.get('position'));

  if (!latLng) {
    this.close();
    return;
  }

  var tabHeight = 0;

  if (this.activeTab_) {
    tabHeight = this.activeTab_.offsetHeight;
  }

  var anchorHeight = this.getAnchorHeight_();
  var arrowSize = this.getArrowSize_();
  var arrowPosition = this.getArrowPosition_();

  arrowPosition = arrowPosition / 100;

  var pos = projection.fromLatLngToDivPixel(latLng);
  var width = this.contentContainer_.offsetWidth;
  var height = this.bubble_.offsetHeight;

  if (!width) {
    return;
  }

  // Adjust for the height of the info bubble
  var top = pos.y - (height + arrowSize) + this.getBubbleOffset().y;

  if (anchorHeight) {
    // If there is an anchor then include the height
    top -= anchorHeight;
  }

  var left = pos.x - (width * arrowPosition) + this.getBubbleOffset().x;

  this.bubble_.style['top'] = this.px(top);
  this.bubble_.style['left'] = this.px(left);

  var shadowStyle = parseInt(this.get('shadowStyle'), 10);

  switch (shadowStyle) {
    case 1:
      // Shadow is behind
      this.bubbleShadow_.style['top'] = this.px(top + tabHeight - 1);
      this.bubbleShadow_.style['left'] = this.px(left);
      this.bubbleShadow_.style['width'] = this.px(width);
      this.bubbleShadow_.style['height'] =
        this.px(this.contentContainer_.offsetHeight - arrowSize);
      break;
    case 2:
      // Shadow is below
      width = width * 0.8;
      if (anchorHeight) {
        this.bubbleShadow_.style['top'] = this.px(pos.y);
      } else {
        this.bubbleShadow_.style['top'] = this.px(pos.y + arrowSize);
      }
      this.bubbleShadow_.style['left'] = this.px(pos.x - width * arrowPosition);

      this.bubbleShadow_.style['width'] = this.px(width);
      this.bubbleShadow_.style['height'] = this.px(2);
      break;
  }
};

and then you can use this by

var infoBubble = new InfoBubble({
  map: map,
  content: "My Content",
  position: new google.maps.LatLng(1, 1),
  shadowStyle: 1,
  padding: 0,
  backgroundColor: 'transparent',
  borderRadius: 7,
  arrowSize: 10,
  borderWidth: 1,
  borderColor: '#2c2c2c',
  disableAutoPan: true,
  hideCloseButton: true,
  arrowPosition: 50,
  backgroundClassName: 'infoBubbleBackground',
  arrowStyle: 2

});

Then finally we have to use this method setBubbleOffset(x,y); to set InfoBubble position

infoBubble.setBubbleOffset(0,-32);
Valentin Coudert
  • 1,759
  • 3
  • 19
  • 44
Anubhav Gupta
  • 1,176
  • 11
  • 12
9

It seems as though the infoBubble library itself defaults to positioning the bubble above the marker it is bound to. Take a look at the sample file they included in the library: http://code.google.com/p/google-maps-utility-library-v3/source/browse/trunk/infobubble/examples/example.html . Specifically notice from line 99 to line 122 and the use of the two infobubbles. The first one is bound to the marker, however the second one is a stand-alone and thus if you see line 106, you can define a position to it. Now, based on this understanding I've created an example for you in this fiddle http://jsfiddle.net/pDFc3/. The infoBubble is positioned to the right of the marker.

It's strange, because the infoBubble js library has a function for setPosition ( http://code.google.com/p/google-maps-utility-library-v3/source/browse/trunk/infobubble/src/infobubble.js?r=206 ) see Line 1027. But for some reason after I wait for the DOM to load and try to change the position by going infoBubble.setPosition(newLatLng); I doesn't work. On the contrary, declaring infoBubble.getPosition(); after the DOM loads gives me the current position of the marker the infoBubble is bound to. So setPosition() may have a bug in the js library, because I believe it is still being worked on (I could be wrong maybe it's just buggy).


I've fixed my jsFiddle to solve your issue for when zooming in and out, and positioning the infoBubble accordingly ( http://jsfiddle.net/pDFc3/ ). Let me explain the logic first. Firstly, the maximum zoom level on Google Maps for road map type is 21 - this value is inconsistent for satellite imagery but the maximum zoom the user can go to is 21. From 21, each time you zoom out the differences between two points can be kept consistent "on screen" based on the following logic:

consitent_screen_dist = initial_dist * (2 ^ (21 - zoomlevel))

In our case, the reasonable value for initial distance was 0.00003 degrees (between marker and infoBubble). So, based on this logic I added the following piece to find the initial longitudinal distance between marker and infoBubble:

var newlong = marker.getPosition().lng() + (0.00003 * Math.pow(2, (21 - map.getZoom())));

Likewise, to ensure the distance stays consistent on each zoom level change we simply declare a new longitude as we listen for a change in the zoom level:

    google.maps.event.addListener(map, "zoom_changed", function() {
        newlong = marker.getPosition().lng() + (0.00003 * Math.pow(2, (21 - map.getZoom())));
        infoBubble.setPosition(new google.maps.LatLng(marker.getPosition().lat(), newlong));                
    });

Keep in mind you can make this code much more efficient by declaring variables for marker.getPosition and other values that are called through methods. So that the method calls aren't repeated and slow your code down.

Suvi Vignarajah
  • 5,758
  • 27
  • 38
  • That is what i am looking for, one issue is that when zooming in and out the marker moves in relation to the marker, sometimes hiding it entirely. Do you think this is something that can be resolved? – Undefined Jun 05 '12 at 21:56
  • That is true, it's because we're setting the position of the infoBubble with relation to the marker at a specific zoom level. But once you change the zoom level, the distance between the marker and the infoBubble (difference between the lat/long coordinates of both) does not change. So when you zoom out, that difference is so small that you view the infoBubble on top of the marker. – Suvi Vignarajah Jun 06 '12 at 01:52
  • There is a fix to this issue though, I've found that if you don't bind the infoBubble to maker then you can actually use the infoBubble.setPosition(latlng) method. This means that we can listen for the zoom changes using google maps event listener `zoom_changed` and then use the `getZoom()` function to determine classes for how to position the infoBubble with relation to the marker based on zoom levels. I will update my jsfiddle with this possible fix, but feel free to experiment based on this principle. – Suvi Vignarajah Jun 06 '12 at 01:57
  • I've updated the answer and my jsFiddle, please take a look I think this solves your problem. – Suvi Vignarajah Jun 06 '12 at 03:06
1

Unfortunately there is no such option as pixelOffset in InfoBubble. But if you just want to move up Bubble above the marker in your example you should not set map parameter at bubble initialization. Consider the following fiddle (i fixed it for you):

http://jsfiddle.net/ssrP9/5/


P.S. Your fiddle didn't work because you hadnt added resources properly http://doc.jsfiddle.net/basic/introduction.html#add-resources

Vlad Minaev
  • 585
  • 4
  • 10
  • Forgot to mention. My answer is based on sources analysis. So if you really need to set custom offset relative to a marker there are two ways: 1. Get sources of this library and add neccessary functionality 2. [quite weird] Put invisible marker with necessary offset from original and open bubble for it. – Vlad Minaev Jun 04 '12 at 16:17
  • Thanks for that jsfiddle i will include that in my question. Unfortunately i am looking for placing the infoBubble in a different position from above the marker – Undefined Jun 04 '12 at 21:38
1

I've just come across the exact same issue but couldn't find an answer anywhere. Through a little trial and error I worked it out.

You'll be using the Google JS file for "infoBubble". Go into this file and search for...

InfoBubble.prototype.buildDom_ = function() {

For me, this is on line 203 (but that could be the result of previous shuffling and edits).

Within that function you'll see the position "absolute" declaration. On a new line, you can add marginTop, marginRight, marginBottom and marginLeft. This will nudge the bubble from its default position (which is also dependent on the arrow position declaration in your config)...

This is my code tweak in the bubble JS file which positions the bubble over the top of the marker (due to a design feature)...

var bubble = this.bubble_ = document.createElement('DIV');
bubble.style['position'] = 'absolute';
bubble.style['marginTop'] = this.px(21);
bubble.style['marginLeft'] = this.px(1);
bubble.style['zIndex'] = this.baseZIndex_;

Hope that helps.

Tim Dodd
  • 241
  • 4
  • 13
1

In the InfoBubble buildDom function, add:

bubble.className = 'bubble-container';

Now you have a CSS class for each InfoBubble, you can shift it using CSS margin.

bcm
  • 5,470
  • 10
  • 59
  • 92
  • if you use compressed js find _function m(a){var c=a.c=document.createElement("DIV");_ then add _c.className='bubble-container';_ – Nikolay Aug 08 '18 at 08:46
1

You can also use a defined anchor height;

var anchorHeight = YOURNUMBER;

line 874 infobubble.js

Dtnand
  • 449
  • 3
  • 14