Non-Overlapping Tooltips

Topics: Feature Requests
Sep 9, 2010 at 10:52 PM

Hi radioman,

I have been using the older version of Gmaps for sometime and am now updating to your latest versions. The version i have

been using used the LAYERS for drawing the markers tool tips.

In my custom layer i have code that during the display of each of the markers tool tips in the layer, it loops through looking

for space to display the tooltips in such a way that they do not overlap any existing marker.

The code starts at a radius offset and at 30 degrees, then if no space is found by checking for intersections on other markers

rectangles, it increases the angle by 30 degrees, once is gets back to 30 it increases the radius and loops through again, until

an empty space is found.

My question is,  using the latest versions, i see that the drawing of the markers toop tips are no long handled by the layers

but by the GMapToolTip object itself.   From there how can i get back to the Marker, then the layer, and us similar logic

to the above.

 

I can provide the code i use currenlty , as i think it may be a nice feature to have in the system anyway.

Regards,

Paul.

 

Sep 10, 2010 at 5:59 AM
Edited Sep 10, 2010 at 8:13 AM

marker has reference to tooltip, tooltip has reference to marker, so there is no much of the change, anyway tooltip rendering is in overlay right after markers itself so.. you can make a fork for latest changeset or just share your current code, i'll try to apply it

Sep 10, 2010 at 7:53 AM

I dont know what is meant by creating a fork, so i include the source code here, it is very simple.

This is my current code (VB).  Each marker also stores the current tooltips rectangle, used in the free space detection.  The marker has

an option to handle the drawing of tooltips itself, and if so, then sets the HANDLED = TRUE so the code in the layer to auto position the tooltips is not executed.

I suggest that a property within the layer to say AUTO POSITION TOOLTIPS  = TRUE/FALSE.

 

Thanks again.

 

 

 

 

Protected Overrides Sub DrawToolTip( _
         ByVal g As System.Drawing.Graphics, _
         ByVal m As GMap.NET.WindowsForms.GMapMarker, _
         ByVal x As Integer, _
         ByVal y As Integer)

         Dim s As GraphicsState = g.Save()
         g.SmoothingMode = SmoothingMode.AntiAlias

         Dim Handled As Boolean
         ' Allow the MARKER to draw the tool tip
         CType(m, Core.Marker_Pin).DrawToolTip(g, _
               m, x, y, Handled, _
               TooltipBackground, _
               TooltipPen, _
               TooltipFont, _
               TooltipFormat)

         Dim dOffset As Decimal
         Dim dInitialOffset As Decimal
         Dim dOffsetIncrease As Decimal

         dInitialOffset = 10
         dOffsetIncrease = 10

         If Not Handled Then

            Dim st As System.Drawing.Size = g.MeasureString(m.ToolTipText, TooltipFont).ToSize()
            Dim rect As New System.Drawing.Rectangle(x, y, st.Width + 2, st.Height + 4)

            ' Position this tooptip so it dosn't overlap any existing ones

            Dim mMarker As Core.Marker_Pin
            Dim bSpaceFound As Boolean = False

            Dim dAngle As Decimal

            Dim dStartAngle As Decimal
            dStartAngle = 270 + 30

            dOffset = dInitialOffset

            Dim dRadian As Decimal
            Dim dOffsetX As Decimal
            Dim dOffsetY As Decimal

            dAngle = dStartAngle


            ' ******
            ' the problem is, that when the map is moved, the CO-ORDS of the tool tip text is not updated
            ' so when a new tool tip is drawn, it checks for interection of the OLD co-ords of the tool tips
            ' prior to the map moving
            ' so need to only store the OFFSET of the tool tip, and check for interections of that offset from the updated
            ' local position of the markers
            ' ******

            Do While Not bSpaceFound

               ' Calculate the offset based on the current ANGLE and
               ' the OFFSET LENGTH

               dRadian = dAngle * System.Math.PI / 180

               dOffsetX = x + (dOffset * Math.Cos(dRadian))
               dOffsetY = y + (dOffset * Math.Sin(dRadian))

               dOffsetX = dOffsetX - (rect.Width / 2)
               dOffsetY = dOffsetY - (rect.Height / 2)

               rect.X = dOffsetX
               rect.Y = dOffsetY

               bSpaceFound = True

               Dim oLayer As GMap.NET.WindowsForms.GMapOverlay

               For Each oLayer In control.Overlays

                  For Each mMarker In oLayer.Markers

                     If mMarker.Visible = True And Not m Is mMarker Then

                        If mMarker.TooltipMode = GMap.NET.WindowsForms.MarkerTooltipMode.Always Then

                           Dim rect2 As New System.Drawing.Rectangle(mMarker.LocalPosition.X + mMarker.TooltipRectangle.X, mMarker.LocalPosition.Y + mMarker.TooltipRectangle.Y, mMarker.TooltipRectangle.Width, mMarker.TooltipRectangle.Height)
                  
                           If rect.IntersectsWith(rect2) Then
                              bSpaceFound = False
                              Exit For
                           End If
                        End If

                     End If

                     If mMarker.Visible = True Then
                        ' Does it interect with another MARKER
                        If rect.IntersectsWith(mMarker.MyRectangle) Then
                           bSpaceFound = False
                           Exit For
                        End If
                     End If

                  Next

               Next

               If bSpaceFound = True Then
                  Exit Do
               End If

               ' Increase the ROTATION
               dAngle = dAngle + 30

               If dAngle > 360 Then
                  dAngle = 0
               End If

               If dAngle = dStartAngle Then
                  dOffset = dOffset + dOffsetIncrease
               End If

            Loop

            g.DrawLine(TooltipPen, CSng(x), CSng(y), CSng(rect.X + rect.Width / 2), CSng(rect.Y + rect.Height / 2))
            g.FillRectangle(Me.TooltipBackground, rect)

            g.DrawRectangle(TooltipPen, rect)
            g.DrawString(m.ToolTipText, TooltipFont, Brushes.Black, rect, TooltipFormat)

            rect.X = rect.X - x
            rect.Y = rect.Y - y

            ' Save this TOOLTIPS rectangle for the marker
            CType(m, Core.Marker_Pin).TooltipRectangle = rect
            CType(m, Core.Marker_Pin).TooltipShown = True

         End If

         g.Restore(s)

      End Sub

Sep 22, 2010 at 7:54 AM

Dear paulmoliver

I am using c# and winform and I have also the same problem of overlaping tooltips-ypur solution looks fine for me but I dont know how to implement it (I have also older version where layers are used). I tried to convert your code to C# but I have problems with lines like: CType(m, Core.Marker_Pin).DrawToolTip(g, _
               m, x, y, Handled, _
               TooltipBackground, _
               TooltipPen, _
               TooltipFont, _
               TooltipFormat)

and

where I can't find where you have definition for Core.Marker_Pin and also DrawTooltip has more arguments added like(Handled, _
               TooltipBackground, _
               TooltipPen, _
               TooltipFont, _
               TooltipFormat)

 

I would appreciate if you can help me or if you dont have time can you please post all neded code in VB and I will try to convert to C# and put in right place. You can also send this to me on my email: draganm2004@yahoo.co.uk Just please notify me before so that I can go and look (I don't want that your feedback goes to spam or something)

Looking forward for your feedback.

Best regards

 

Oct 8, 2010 at 4:45 PM

dragonfly1974,   i am just back from a trip to Africa, thats the reason for the delay.

I now have the NON-Overlapping tooltips working in the latest version.   The only problem being is that a Tooltip makes reference to its Marker, and that Marker to other Markers in the Overlay/Layer,   but we can't get to the other layers, so the

non-overlapping logic only works for a single layer.

 

Anyway , here is my custom  Map_Pin (Marker)   in VB.NET

 

Namespace Core

   Public Class Marker_Pin

      Inherits GMap.NET.WindowsForms.GMapMarker

      Protected dWidth As Integer = 15
      Protected dHeight As Integer = 30

      Protected oPen As Pen
      Protected oBrush As SolidBrush
      Protected oBlackPen As New Pen(Color.Black, 2)

      Protected f As New Core_BaseForm
      Protected sTitleText As String

      Protected mColour As Color
      Public Property Colour() As Color
         Get
            Return mColour
         End Get
         Set(ByVal value As Color)
            mColour = value
            oBrush = New SolidBrush(mColour)
            oPen = New Pen(mColour)
         End Set
      End Property

      Protected mType As String
      Public Property Type() As String
         Get
            Return mType
         End Get
         Set(ByVal value As String)
            mType = value
         End Set
      End Property

      Protected mSize As String
      Public Property PinSize() As String
         Get
            Return mSize
         End Get
         Set(ByVal value As String)
            mSize = value
            Select Case mSize
               Case "SMALL"
                  dWidth = 15
                  dHeight = 30
               Case "MEDIUM"
                  dWidth = 30
                  dHeight = 60
               Case "LARGE"
                  dWidth = 45
                  dHeight = 90
            End Select
            Me.Size = New Size(dWidth, dWidth)
         End Set
      End Property

      Protected mValue As Decimal
      Public Property Value() As Decimal
         Get
            Return mValue
         End Get
         Set(ByVal value As Decimal)
            mValue = value
         End Set
      End Property

      Protected mShowLabel As String
      Public Property ShowLabel() As String
         Get
            Return mShowLabel
         End Get
         Set(ByVal value As String)
            mShowLabel = value
         End Set
      End Property

      Protected mTooltipRectangle As Rectangle
      Public Property TooltipRectangle() As Rectangle
         Get
            Return mTooltipRectangle
         End Get
         Set(ByVal value As Rectangle)
            mTooltipRectangle = value
         End Set
      End Property

      Protected mTooltipShown As Boolean
      Public Property TooltipShown() As Boolean
         Get
            Return mTooltipShown
         End Get
         Set(ByVal value As Boolean)
            mTooltipShown = value
         End Set
      End Property

      Protected mMyRectangle As Rectangle
      Public Property MyRectangle() As Rectangle
         Get
            Return mMyRectangle
         End Get
         Set(ByVal value As Rectangle)
            mMyRectangle = value
         End Set
      End Property

      Protected mShowImageOnMouseOver As Boolean
      Public Property ShowImageOnMouseOver() As Boolean
         Get
            Return mShowImageOnMouseOver
         End Get
         Set(ByVal value As Boolean)
            mShowImageOnMouseOver = value
         End Set
      End Property

      Public Sub New(ByVal p As GMap.NET.PointLatLng)
         MyBase.New(p)

         ' Set the SIZE for the MOUSE OVER TOOLTIP detection
         Me.Size = New Size(30, 30)
         Me.mImageSize = New GMap.NET.Size(100, 100)

         sTitleText = "TEST"
         mType = "SQUARE"
         mColour = Color.Red
         mSize = "SMALL"
         mShowLabel = "YES"
         mShowImageOnMouseOver = True

         oBrush = New SolidBrush(mColour)
         oPen = New Pen(mColour)
         oPen.Width = 2

         Me.ToolTip = New GMap.NET.WindowsForms.ToolTips.GMapBaloonToolTip(Me)

      End Sub

      Protected mName As String
      Public Property Name() As String
         Get
            Return mName
         End Get
         Set(ByVal value As String)
            mName = value
         End Set
      End Property

      Protected mImage As System.Drawing.Image
      Public Property Image() As System.Drawing.Image
         Get
            Return mImage
         End Get
         Set(ByVal value As System.Drawing.Image)
            mimage = value
         End Set
      End Property

      Protected mImageSize As GMap.NET.Size
      Public Property ImageSize() As GMap.NET.Size
         Get
            Return mImageSize
         End Get
         Set(ByVal value As GMap.NET.Size)
            mImageSize = value
         End Set
      End Property

      Public Overrides Sub OnRender(ByVal g As System.Drawing.Graphics)

         Select Case mType
            Case "PIN"

               g.SmoothingMode = Drawing2D.SmoothingMode.AntiAlias

               g.FillEllipse( _
                     oBrush, _
                     New System.Drawing.Rectangle( _
                     LocalPosition.X - dWidth - (dWidth / 4), _
                     LocalPosition.Y - dHeight, _
                     dWidth, _
                     dWidth))

               g.DrawEllipse( _
                     oBlackPen, _
                     New System.Drawing.Rectangle( _
                     LocalPosition.X - dWidth - (dWidth / 4), _
                     LocalPosition.Y - dHeight, _
                     dWidth, _
                     dWidth))

               g.DrawLine(oPen, CSng(LocalPosition.X - dWidth), CSng(LocalPosition.Y - dHeight), LocalPosition.X, LocalPosition.Y)

               mMyRectangle = New Rectangle(LocalPosition.X - dWidth - (dWidth / 4), LocalPosition.Y - dHeight, dWidth + (dWidth / 4), dHeight)

            Case "CIRCLE"

               g.SmoothingMode = Drawing2D.SmoothingMode.AntiAlias

               g.FillEllipse( _
                     oBrush, _
                     New System.Drawing.Rectangle( _
                     LocalPosition.X - (dWidth / 2), _
                     LocalPosition.Y - (dWidth / 2), _
                     dWidth, _
                     dWidth))

               g.DrawEllipse( _
                     oBlackPen, _
                     New System.Drawing.Rectangle( _
                     LocalPosition.X - (dWidth / 2), _
                     LocalPosition.Y - (dWidth / 2), _
                     dWidth, _
                     dWidth))

               mMyRectangle = New Rectangle(LocalPosition.X - (dWidth / 2), LocalPosition.Y - (dWidth / 2), dWidth, dWidth)

            Case "SQUARE"

               g.FillRectangle( _
                     oBrush, _
                     New System.Drawing.Rectangle( _
                     LocalPosition.X - (dWidth / 2), _
                     LocalPosition.Y - (dWidth / 2), _
                     dWidth, _
                     dWidth))

               g.DrawRectangle( _
                     oBlackPen, _
                     New System.Drawing.Rectangle( _
                     LocalPosition.X - (dWidth / 2), _
                     LocalPosition.Y - (dWidth / 2), _
                     dWidth, _
                     dWidth))

               mMyRectangle = New Rectangle(LocalPosition.X - (dWidth / 2), LocalPosition.Y - (dWidth / 2), dWidth, dWidth)

            Case Else

               g.FillPolygon( _
                     oBrush, _
                        New PointF() { _
                              New System.Drawing.PointF(LocalPosition.X, LocalPosition.Y - (dWidth / 2)), _
                              New System.Drawing.PointF(LocalPosition.X + (dWidth / 2), LocalPosition.Y + (dWidth / 3)), _
                              New System.Drawing.PointF(LocalPosition.X - (dWidth / 2), LocalPosition.Y + (dWidth / 3)) _
                        })

               g.DrawPolygon( _
                     oBlackPen, _
                        New PointF() { _
                              New System.Drawing.PointF(LocalPosition.X, LocalPosition.Y - (dWidth / 2)), _
                              New System.Drawing.PointF(LocalPosition.X + (dWidth / 2), LocalPosition.Y + (dWidth / 3)), _
                              New System.Drawing.PointF(LocalPosition.X - (dWidth / 2), LocalPosition.Y + (dWidth / 3)) _
                        })

               mMyRectangle = New Rectangle(LocalPosition.X - (dWidth / 2), LocalPosition.Y - (dWidth / 2), dWidth, dWidth)

         End Select

         ' Draw the IMAGE
         If Not mImage Is Nothing Then

            If (Me.IsMouseOver And Me.ToolTipMode = GMap.NET.WindowsForms.MarkerTooltipMode.OnMouseOver) Or _
               Me.ToolTipMode = GMap.NET.WindowsForms.MarkerTooltipMode.Always Then

               ' Draw a BORDER
               Dim dBorderThinkness As Integer = 1
               g.DrawRectangle( _
                     oBlackPen, _
                     New System.Drawing.Rectangle( _
                     LocalPosition.X - dBorderThinkness, _
                     LocalPosition.Y - dBorderThinkness, _
                     mImageSize.Width + dBorderThinkness + dBorderThinkness, _
                     mImageSize.Height + dBorderThinkness + dBorderThinkness))

               g.DrawImage(mImage, LocalPosition.X, LocalPosition.Y, mImageSize.Width, mImageSize.Height)

            End If
         End If

      End Sub

   End Class

End Namespace

 

 

 

 

And this is the custom Tooltip   that provides for non-overlap

 

 

Imports System.Drawing
Imports System.Drawing.Drawing2D

Namespace Core

   Public Class Marker_Tooltip
      Inherits GMap.NET.WindowsForms.GMapToolTip

      Public Radius As Single = 10.0F
      Public mMarker As GMap.NET.WindowsForms.GMapMarker

      Public Sub New(ByVal marker As GMap.NET.WindowsForms.GMapMarker)
         MyBase.New(marker)

         ' Set mMARKER as the internal marker in the GMAP dll is not accessable
         mMarker = marker

         Stroke = New Pen(Color.FromArgb(140, Color.Navy))
         Stroke.Width = 3
         Me.Stroke.LineJoin = LineJoin.Round
         Me.Stroke.StartCap = LineCap.RoundAnchor
         Fill = Brushes.Yellow
      End Sub

      Public Overrides Sub Draw(ByVal g As System.Drawing.Graphics)

         ' Don't need to call the base DRAW
         '   MyBase.Draw(g)

         Dim st As System.Drawing.Size = g.MeasureString(mMarker.ToolTipText, Font).ToSize()
         Dim rect As New System.Drawing.Rectangle(mMarker.ToolTipPosition.X, mMarker.ToolTipPosition.Y - st.Height, st.Width + TextPadding.Width, st.Height + TextPadding.Height)

         Dim dOffset As Decimal
         Dim dInitialOffset As Decimal
         Dim dOffsetIncrease As Decimal

         Dim iAttempt As Integer
         iAttempt = 1

         ' dInitialOffset = 10
         dOffsetIncrease = 100
         dInitialOffset = dOffsetIncrease

         ' Find a place for this tooltip
         Dim bSpaceFound As Boolean = False


         Dim dAngle As Decimal

         Dim dStartAngle As Decimal
         dStartAngle = 360 - 45

         dOffset = dInitialOffset

         Dim dRadian As Decimal
         Dim dOffsetX As Decimal
         Dim dOffsetY As Decimal

         Dim dStartX As Decimal
         Dim dStartY As Decimal

         ' Start at the Markers local position

         dStartX = mMarker.LocalPosition.X
         dStartY = mMarker.LocalPosition.Y

         rect = New System.Drawing.Rectangle(dStartX, dStartY, st.Width + 2, st.Height + 4)

         dAngle = dStartAngle

         ' Auto place the TOOLTIPS without overlapping other markers in the same OVERLAY
         ' Check for other TOOLTIP positions on other MARKERS on this layer

         Dim oOtherMarkers As Core.Marker_Pin

         bSpaceFound = False

         Do While Not bSpaceFound

            ' Calculate the offset based on the current ANGLE and
            ' the OFFSET LENGTH

            dRadian = dAngle * System.Math.PI / 180

            dOffsetX = dStartX + (dOffset * Math.Cos(dRadian))
            dOffsetY = dStartY + (dOffset * Math.Sin(dRadian))

            dOffsetX -= (rect.Width / 2)
            dOffsetY -= (rect.Height / 2)

            rect.X = dOffsetX
            rect.Y = dOffsetY

            bSpaceFound = True

            ' This is the current RECTANGLE that is being checked for available space

            '  g.FillRectangle(Brushes.Blue, rect)
            '  g.DrawString(iAttempt.ToString, Font, Brushes.Black, rect, Format)
            iAttempt += 1

            For Each oOtherMarkers In Me.mMarker.Overlay.Markers

               If oOtherMarkers.IsVisible = True And Not mMarker Is oOtherMarkers Then

                  If mMarker Is oOtherMarkers Then

                     ' This marker is the current marker
                     If rect.IntersectsWith(oOtherMarkers.TooltipRectangle) Then
                        bSpaceFound = False
                        Exit For
                     End If

                  Else

                     If mMarker.ToolTipMode = GMap.NET.WindowsForms.MarkerTooltipMode.Always Then
                        If rect.IntersectsWith(oOtherMarkers.TooltipRectangle) Then
                           bSpaceFound = False
                           Exit For
                        End If
                     End If

                  End If

               End If

            Next

            If bSpaceFound = True Then
               Exit Do
            End If

            ' Increase the ROTATION
            dAngle = dAngle + 15

            If dAngle > 360 Then
               dAngle = 0
            End If

            If dAngle = dStartAngle Then
               dOffset = dOffset + dOffsetIncrease
            End If

            If dOffset > 100 Then
               Exit Do
            End If

         Loop

         ' Save this TOOLTIPS rectangle for the marker
         CType(mMarker, Core.Marker_Pin).TooltipRectangle = rect
         CType(mMarker, Core.Marker_Pin).TooltipShown = True

         ' Draw the JOINING LINE
         g.DrawLine(Stroke, mMarker.ToolTipPosition.X, CInt(mMarker.ToolTipPosition.Y), rect.X, CInt(rect.Y + rect.Height / 2))

         Dim objGP As New GraphicsPath
         With objGP
            g.FillRectangle(Fill, rect)
            g.DrawRectangle(Stroke, rect)
            g.DrawString(mMarker.ToolTipText, Font, Brushes.Black, rect, Format)
         End With

         objGP.Dispose()

      End Sub

   End Class

End Namespace

 

 

 

Radioman,  as you can see, i dont have access to the mMarker   from within my interied marker, because its declared INTERNAL i think in your core code. Please make this available as FRIEND (in VB.net)  to allow inherited classes access to it.

Also ,   do you have any suggestions as to how we can get to all the layers/overlays from a tooltip to allow this class to perform non-overlap on all layers.

Thanks again.

 

 

 

 

 

 

 

Oct 8, 2010 at 4:47 PM

ah, take out this section of code in the loop, it was part of my testing

      If dOffset > 100 Then
               Exit Do
            End If

 

 

Oct 8, 2010 at 4:57 PM

great, but can we use forks, cuting vb code from forums and converting it to c# is not very pleasing ;} i can't know if it even builds

Oct 8, 2010 at 4:59 PM

yes, sorry, but i donnt know how to create forks.

any ideas about accessing the other layers from within a Tooltip OnDraw ?

 

Oct 8, 2010 at 5:21 PM
  1. http://blogs.msdn.com/b/codeplex/archive/2010/03/05/codeplex-mercurial-support-for-forks.aspx
  2. http://codeplex.codeplex.com/wikipage?title=Using%20TortoiseHG%20with%20CodePlex
  3. i don't thing that calculation in render functions is good, what if you have 1000 markers with low zoom and many overlapping objects...

...what we need is fast marker clustering with overlapping detection, that would be great and that's the goal ;}

 

 

 

 

 

 

 

 

Oct 8, 2010 at 7:01 PM

Agreed, calculating in the render function is not ideal, but it is now the only method.

Previously, at the Overlays/Layer level was better.  But we can only work with

what we have, and for now, at least it provides a basic solution.

Over to the community to expand upon and improve :-)

 

Oct 26, 2010 at 12:43 AM

Dear Paulmoliver and radioman

I just got from vacation and found your feedback.

I have managed to implement it in my solution so I would like to thank you all for your support and help regard my issue.

Best regards and Thank you.

 

 

Dec 6, 2010 at 4:21 PM

radioman,

Can you expose the  mMarker variable in the   GMap.NET.WindowsForms.GMapToolTip   (PROTECTED FRIEND or whatever the c# equiv is) , its marked INTERNAL and

i can't get to it when i inherit using VB.NET .

 

Thanks

 

Dec 6, 2010 at 6:48 PM

done! ;}

Jan 16, 2014 at 4:23 PM
Edited Jan 21, 2014 at 9:31 AM
Hi Radioman,

Is any of the build-in Marker's Tooltips has a Non-Overlapping properties?

I could not find the property within the layer to say AUTO POSITION TOOLTIPS = TRUE/FALSE.

Can you please help me with these.

Thanks,
Jeno
Mar 18, 2015 at 7:04 PM
Was anything ever created to auto position the tool tips / marker labels so that close markers don't have labels that overlap?

Thanks
FW