Google Style Clustered Tooltips

Topics: Feature Requests
Dec 29, 2011 at 11:32 AM
Edited Dec 29, 2011 at 11:46 AM

I have an issue where I am populating my map with records from a database. And in many cases I have multiple records at a single latitude/longitude pair. So I may have upwards of 10 markers right on top of each other. Using the built-in GMap marker tooltips, when the user mouses over this cluster of markers, they only see the tooltip for the front-most marker.

I'd like to instead show one tooltip with a button for the user to cycle through the various tooltips.  I have alot of what I need done already completed and working:

- First I disable the built-in GMap tooltips.
- On the mouse-enter event of each marker I add the marker to a list of currently “mouse-overed” markers.
- On the mouse-leave event of each marker I remove the marker from that list.
- Anytime I add or remove a marker from the list I do a check against the list.  The check sees if there is at least one marker in the list.

Here is where I have stopped.  If the list is not empty, I need to display a custom tooltip at the location of the first marker in the list.  It will display the tooltip information associated with the first marker and offer a button to scroll through the tooltips for each other marker in the list.

The look I’m going for is similar to what Google does when they have clustered markers. 

Has anyone here done anything along the lines of this?  If not, I’m comfortable attempting it on my own.  I’m unsure about how to make the custom tooltip.  Any suggestions on what classes I can inherit and how to draw the tooltip to the map?

Radioman, is there any chance of getting something like this built in?  Thanks again.

Dec 29, 2011 at 2:50 PM

For anyone interested, this is how I solved the problem:

private List<GMapMarker> mouseOveredMarkers = new List<GMapMarker>();

private void MainMap_OnMarkerEnter(GMapMarker item)
{
    mouseOveredMarkers.Add(item);
}

private void MainMap_OnMarkerLeave(GMapMarker item)
{
    mouseOveredMarkers.Remove(item);
}

void MainMap_OnMarkerClick(GMapMarker item, MouseEventArgs e)
{
    if (mouseOveredMarkers.Contains(item))
    {
        if (mouseOveredMarkers.Count >= 1)
        {
            ToolTipContents ttc = new ToolTipContents(mouseOveredMarkers);
            PoperContainer ttcContainer = new PoperContainer(ttc);

            GMap.NET.GPoint p = MainMap.FromLatLngToLocal(item.Position);
            p.Offset(0, (-1 * ttc.Height) - 34);
            ttcContainer.Show(this, new System.Drawing.Point(p.X, p.Y));
        }
    }
}

So I have my tooltips setup to show on the OnMarkerClick event.  I utilized the SuperContextMenu control from CodeProject to serve as the tooltip rather than creating my own inherited tooltip.  I used SuperContextMenu because it allows you to setup a your own custom control which will act as a context menu.  For my purposes, a context menu worked great because when the use clicks on the marker, the context menu appears and will not dissapear until the user clicks elsewhere.  So my ToolTipContents control takes the list of markers that have been clicked on and then displays the information as I want it (like a tooltip).  I also have controls in it to move to the next or previous marker in the list.

The SuperContextMenu can be found here:  http://www.codeproject.com/KB/menus/SuperContextMenu.aspx 

Coordinator
Jan 9, 2012 at 12:24 PM

thats one way to do it, i didn't manage to do it with internally rendered tooltips

Jan 9, 2012 at 6:07 PM

I like the idea of using a context menu in place of the internal tooltips for one reason in particular.  The context menu floats above the map.  So there is never a situation with a marker close to the edge of the map where it's tooltip gets cut off. 

However, I'm sure for some applications that would not be a desired behavior.

Oct 18, 2012 at 1:58 PM

Hi Mike are you still There?

would you post Tooltipcontents Control Also?

Tks

HN

Oct 18, 2012 at 3:01 PM
Edited Oct 18, 2012 at 3:02 PM
using System;
using System.Collections.Generic;
using System.Windows.Forms;
using GMap.NET.WindowsForms;

namespace FAA.Spectrum.DirectionFinder.Markers
{
    internal partial class ToolTipContent : SuperContextMenu.PopedContainer
    {
        private List<GMapMarker> markers = new List<GMapMarker>();
        private int _shownMarker = 0;

        private int ShownMarker
        {
            get { return _shownMarker; }
            set
            {
                if (value < 0)
                    value = markers.Count - 1;
                else if (value >= markers.Count)
                    value = 0;

                _shownMarker = value;

                labelTitle.Text = "Put your title here";
                labelText.Text = markers[_shownMarker].ToolTipText;
                
                labelCounter.Text = (_shownMarker + 1).ToString() + " of " + markers.Count;
            }
        }

        public ToolTipContent(List<GMapMarker> Markers)
        {
            InitializeComponent();

            if (Markers.Count == 0)
                return;

            // Assign the list of markers
            this.markers = Markers;

            // Sort the list of markers if you want to
            // Exclude this if you dont care about order
            markers.Sort(
                delegate(GMapMarker m1, GMapMarker m2)
                {
                    // Do your sorting method for ordering the markers
                    return 0;
                }
            );

            // Show the first marker
            buttonBack.Enabled = buttonNext.Enabled = (markers.Count != 1);
            ShownMarker = 0;
        }

        private void buttonBack_Click(object sender, EventArgs e)
        {
            ShownMarker--;
        }

        private void buttonNext_Click(object sender, EventArgs e)
        {
            ShownMarker++;
        }
    }
}

There is a stripped won version of mm TollTipContent control. As you can see (and as I said in my post), my tooltip inherits from a SuperContextMenu.

In addition to that code, my ToolTipContent contol has a three labels on it. On for the title, one for the tolltip text, and one that is a counter in the style of "1 of 3". I also for a next and back button. I have a picture of it but unfortunately CodePlex doesn't have a built-in method for uploading pictures.

Coordinator
Oct 18, 2012 at 3:42 PM

p.s. click html, and add <img src="... tag

Oct 18, 2012 at 5:50 PM

Thanks radioman. I was aware of that - I simply don't have a repository online where I can upload a publicly accessable image.

Coordinator
Oct 18, 2012 at 5:52 PM

http://imageshack.us works just fine ;}

Oct 18, 2012 at 6:12 PM

Here is a simple example of the end result. Here I am showing a tooltip for a custom Gmap marker class that I created which had additional information fields in it beyond just the ToolTipText property. My point is that you can make this tooltip as pretty or ugly as you like. Just use the framework I provided.

Coordinator
Oct 18, 2012 at 6:23 PM

nice! rf triangulation? ;}

Oct 18, 2012 at 6:26 PM

Yes.  For the Federal Aviation Administration.  You're mapping control is helping keep the National Airspace System safe.

Oct 18, 2012 at 7:17 PM
Edited Oct 18, 2012 at 7:18 PM

(http://s7.postimage.org/fvqg5ad63/SC1.png)

Here's an example of the program in use. User sees a map of where they are via GPS (custom green circle GMapMarker). GPS history track shown in red (GMapRoute). Computer is talking to some radio equipment over USB/ethernet to get an instantaneous bearing towards a transmitter and see what the power spectrum looks like for that frequency (Bearing is shown in blue using GMapRoute which constantly updates). I wrote an algorithm to geo-locate the transmitter based on signal strength and bearing measurements and display the probability cloud over the map. That is the red and yellow cloud you are seeing in the center. Tells the user exactly where to drive to find the transmitter. I'm using a GMapImage for that. I'm made a bunch of different custom markers (in red) for displaying all licensed transmitters in the area. The user can also add marker their own markers (in green) which allows the user to add custom commentary or notes (and assign a custom letter to the marker).  Records all the data and can be used for playing back the information as well.

FAA uses it to find interference on their frequencies.

I had a previous version written in C where I created the map myself using a graphics canvas and mapping tiles from OSM and Google. It was very time consuming figuring out how to draw the tiles and logic for navigating the map. It was very primitive although it allowed panning, zooming, and switching from satellite to street view. But using your control in C# has been a huge improvement. As you already know, it's packed with features and allows me to focus on the purpose of the software rather than focusing on getting the map to work. It's been a blessing. So thank you again.

Coordinator
Oct 18, 2012 at 7:37 PM

Cool man! Great work, thanks for sharing

Feb 19, 2013 at 1:34 PM
Hello mike_mankus,

I would like to know how did u created those red and yellow clouds using GmapImage.
is it possible to change their image during runtime?
Feb 19, 2013 at 2:01 PM
Edited Feb 19, 2013 at 2:03 PM
softprogen wrote:
Hello mike_mankus,

I would like to know how did u created those red and yellow clouds using GmapImage.
is it possible to change their image during runtime?
It's quite easy to update the GMapImage during runtime. In the example below, gmImage is my GMapImage and MainMap is my GMapControl.
// Assign the bitmap
gmImage.Image = (your bitmap here); 

// Passing PointLatLng of top left (TL) corner of where to plot image
var tl = MainMap.FromLatLngToLocal(gmImageTL);
 
// Passing PointLatLng of bottom right (BR) corner of where to plot image
var br = MainMap.FromLatLngToLocal(gmImageBR); 

// Setting the GMapImage position based on the top left corner
gmImage.Position = gmImageTL; 

// Setting the GMapImage size based on the top left and bottom right corner
gmImage.Size = new System.Drawing.Size(br.X - tl.X, br.Y - tl.Y); 

// Telling the map to redraw
MainMap.Invalidate(); 
I'm assigning the Position and Size of the GMapImage each time because in my application it can be moved at anytime. If your position is fixed, you can skip those steps and simply resign the Image property of the GMapImage and then invalidate the GMapControl.

As for the red and yellow heatmap, I wrote a complex algorithm that generates that image every second based on the data I'm receiving from my instruments. It's a 1000x1000 pixel bitmap. I set the alpha characteristics to acheive the transparency. I'm not going to publish that algorithm. But as you can see from my code sample, it doesn't take much to have the image be updated in real-time once you have your image.
Feb 19, 2013 at 2:40 PM
Edited Feb 19, 2013 at 2:41 PM
I'm doing something quite a bit different, but here is a good link on creating heatmap type images:

http://dylanvester.com/post/Creating-Heat-Maps-with-NET-20-(C-Sharp).aspx

Obviously it's usefulness to you is dependant on what you're trying to do. But it should get you started.
Feb 19, 2013 at 3:53 PM
Thank you for the reply,

I was searching heatmap kind of image for my application, and your link and ideas seems quite useful for me.
Feb 20, 2013 at 6:21 PM
Hello Mike,

I tried to create an image, but i am not able to get that transparency level as in your application.
It completely hides the part of my map where the image is positioned.
Can u tell me how your did that, or if possible can u share or image.
Feb 23, 2013 at 3:21 PM
mike_mankus wrote:
I'm doing something quite a bit different, but here is a good link on creating heatmap type images:

http://dylanvester.com/post/Creating-Heat-Maps-with-NET-20-(C-Sharp).aspx

Obviously it's usefulness to you is dependant on what you're trying to do. But it should get you started.
Hello Mike,

I placed a picturebox over my GMap control to test if i get correct results, and i am getting it correct.
and now i am trying to get the images over my map
but i have a problem with GMapImage, it gives me an error as
'The type or namespace GMapImage could not be found are you missing a using directive or assembly reference '.
how should i correct this error? am i missing something?
Feb 26, 2013 at 5:34 PM
Sorry about that. I created GMapImage for myself by inheriting from GMapMarker. See below:
using System.Drawing;
using GMap.NET.WindowsForms;

namespace XXXXXX
{
    public class GMapImage : GMapMarker
    {
        private Image image;
        public Image Image
        {
            get
            {
                return image;
            }
            set
            {
                image = value;
                if (image != null)
                {
                    this.Size = new Size(image.Width, image.Height);
                }
            }
        }

        public GMapImage(GMap.NET.PointLatLng p)
            : base(p)
        {
            DisableRegionCheck = true;
            IsHitTestVisible = false;
        }

        public override void OnRender(Graphics g)
        {
            if (image == null)
                return;
            
            Pen pen = new Pen(Brushes.Red, 2);
            Rectangle rect = new Rectangle(LocalPosition.X, LocalPosition.Y, Size.Width, Size.Height);
            g.DrawImage(image, rect);
            g.DrawRectangle(pen, rect);    
        }
    }
}
I opted to put a red box as a border around my image. You could easily remove that.
Feb 26, 2013 at 5:42 PM
As for transparency... You need to make sure that the image colors have an alpha characteristic set. You have a few options.

If you have a color you want to be completely transparent, this is easy. For example, say your image has a white background and in all the white areas, you want the image to be totally transparent.
myBitmap.MakeTransparent(Color.White);
You can also add an alpha characteristic to areas of the image so that they are partially transparent. I do not know who you are creating your image, but here is an example. Say I want to color a pixel in my image to be Red, but I also want it to be semi-transparent.
int alpha = 200; // Any number between 0 and 255 to set the alpha byte
myBitmap.SetPixel(xCoord, yCoord, Color.FromArgb(alpha, 255, 0, 0));
The above code sets that pixel to red with an alpha characteristic of 200 out of 255, meaning about 21% transparent. you could do this for each pixel in the image.
Coordinator
Feb 26, 2013 at 6:42 PM
i recommend to make Pen static or using(Pen pen = new Pen(Brushes.Red, 2)) { ..
..because rendering is done many times per second, if you move the map, you'll get huge memory leak..
Feb 27, 2013 at 11:57 AM
Excellent catch. I have a bad habit of this with my pens and brushes.
Nov 20, 2013 at 1:11 AM
hello mike_mankus. hope you are still there

i am working on a program in which i need to map markers and put an onClick event that will show camera live feeds.
as of now, i know how to put a click event on a custom image marker.
my problem is how to put something inside the "tooltip"

something like the one picture you posted, with textbox and buttons. i use C# and i have an activex control for that camera. i just need it to put on something which is i think it is possible inside the tooltip, and after i saw your post, it gave me hope. i am not native to C#, i just use it because that is gmaps' language, so i need every bit of help in understanding your code. i am hoping for your reply, i think i really need a hands-on tutorial on this :)
is this ok? add me on skype : jeanpaderes
thanks in advance!
Feb 29, 2016 at 3:42 PM
mike_mankus wrote:
For anyone interested, this is how I solved the problem: private List<GMapMarker> mouseOveredMarkers = new List<GMapMarker>(); private void MainMap_OnMarkerEnter(GMapMarker item) { mouseOveredMarkers.Add(item); } private void MainMap_OnMarkerLeave(GMapMarker item) { mouseOveredMarkers.Remove(item); } void MainMap_OnMarkerClick(GMapMarker item, MouseEventArgs e) { if (mouseOveredMarkers.Contains(item)) { if (mouseOveredMarkers.Count >= 1) { ToolTipContents ttc = new ToolTipContents(mouseOveredMarkers); PoperContainer ttcContainer = new PoperContainer(ttc); GMap.NET.GPoint p = MainMap.FromLatLngToLocal(item.Position); p.Offset(0, (-1 * ttc.Height) - 34); ttcContainer.Show(this, new System.Drawing.Point(p.X, p.Y)); } } } So I have my tooltips setup to show on the OnMarkerClick event.  I utilized the SuperContextMenu control from CodeProject to serve as the tooltip rather than creating my own inherited tooltip.  I used SuperContextMenu because it allows you to setup a your own custom control which will act as a context menu.  For my purposes, a context menu worked great because when the use clicks on the marker, the context menu appears and will not dissapear until the user clicks elsewhere.  So my ToolTipContents control takes the list of markers that have been clicked on and then displays the information as I want it (like a tooltip).  I also have controls in it to move to the next or previous marker in the list. The SuperContextMenu can be found here:  http://www.codeproject.com/KB/menus/SuperContextMenu.aspx 
Thank you mike_mankus.

I simplified the "on marker click" part with a simple loop and a messagebox which fills my needs.

Notice I started the loop at 1 to skip the top most tooltip.
if (mouseOveredMarkers.Count > 1)
{
     for (int i = 1; i < mouseOveredMarkers.Count; ++i)
     {
          GMapMarker tt = mouseOveredMarkers[i];
          MessageBox.Show(tt.ToolTipText , " Hidden Text");
     }
}