Graph Marker with Hit-Testing | CodeGuru

Graph Marker with Hit-Testing

This article discusses how to create a graph marker class featuring * Hit-testing. * Shape change (circle, rectangle, triangle, and diamond). * Fill /line option. * Size change. A demo project is provided, in which you can find how to use hit-testing to display tool tip for a marker. In CMarker class, attributes include center […]

Written By
CodeGuru Staff
CodeGuru Staff
Feb 11, 1999
1 minute read
CodeGuru content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More

This article discusses how to create a graph marker class featuring

* Hit-testing.

* Shape change (circle, rectangle, triangle, and diamond).

* Fill /line option.

* Size change.

A demo project is provided, in which you can find how to use hit-testing to display tool tip for a marker.

In CMarker class, attributes include center point, radius, color, shape, size, and fill/line option. A number of get/set interface functions are provided. Three major member functions are Create(), DrawMarker(), and HitTest().

enum TMarkerShape {MARKER_CIRCLE, MARKER_RECTANGLE, MARKER_TRIANGLE, MARKER_DIAMOND};
enum TMarkerFilled {MARKER_FILL, MARKER_LINE};
class CMarker
{
public:
 CMarker();
 ~CMarker();
 void Create(int nRadius, COLORREF Color, TMarkerShape eShape, TMarkerFilled eFill);
// Attributes
private:
 CPoint m_ptCenter;
 int m_nRadius;
 COLORREF m_Color;
 TMarkerShape m_tShape;
 TMarkerFilled m_tType;
 CPen m_pen;
 CBrush m_brush;
public:
 // Shape
 int GetShape() { return int(m_tShape); }
 void SetShape(TMarkerShape eShape) { m_tShape = eShape;}
 // Fill
 int GetType() { return int(m_tType); }
 void SetFill() { m_tType = MARKER_FILL; }
 void SetLine() { m_tType = MARKER_LINE; }
 void SetType(TMarkerFilled eFill) { m_tType = eFill; }
 // Size
 int GetSize() const { return m_nRadius; }
 void SetSize(int m_sz) {m_nRadius = (m_sz<1) ? 1 : m_sz; }

 // Color
 COLORREF GetColor() const { return m_Color; }
 void SetColor(COLORREF m_cl) { m_Color = m_cl; }

 // Center
 CPoint GetCenter() const { return m_ptCenter; }
 void SetCenter(CPoint pt) { m_ptCenter = pt; }

 void DrawMarker(CDC* pDC) const;
 BOOL HitTest(const CPoint& Point) const;
};

When you create a marker, you have to specify its radius,
color, and shape. The center point is (0,0) by default.

void CMarker::Create(int nRadius, COLORREF Color, TMarkerShape eShape, TMarkerFilled eFill)
{
 m_nRadius = nRadius;
 m_Color = Color;
 m_tShape = eShape;
 m_tType = eFill;
 switch(m_tType)
 {
 case MARKER_FILL:
  m_brush.CreateSolidBrush(m_Color);
 break;
 case MARKER_LINE:
  m_brush.CreateSolidBrush(RGB(255,255,255));
 break;
}
 m_pen.CreatePen(PS_SOLID, 1, m_Color);
 m_ptCenter.x = m_ptCenter.y =0;
}

The drawing function DrawMarker() Operates depending
on the shape and fill/line option of a marker.

void CMarker::DrawMarker(CDC* pDC) const
{
 ASSERT_VALID(pDC);
 CPen* pOldPen = pDC->SelectObject((CPen*) &m_pen);
 CBrush* pOldBrush = pDC->SelectObject((CBrush*) &m_brush);
 switch(m_tShape)
 {
  case MARKER_CIRCLE:
  {
   CRect Rect;
   Rect.top = m_ptCenter.y – m_nRadius;
   Rect.bottom = m_ptCenter.y + m_nRadius;
   Rect.left = m_ptCenter.x – m_nRadius;
   Rect.right = m_ptCenter.x + m_nRadius;
   VERIFY(pDC->Ellipse(Rect));
   break;
  }
  case MARKER_RECTANGLE:
  {
   CRect Rect;
   Rect.top = m_ptCenter.y – m_nRadius;
   Rect.bottom = m_ptCenter.y + m_nRadius;
   Rect.left = m_ptCenter.x – m_nRadius;
   Rect.right = m_ptCenter.x + m_nRadius;
   VERIFY(pDC->Rectangle(Rect));
   break;
  }
  case MARKER_TRIANGLE:
  {
   CPoint p[3];
   p[0].x = m_ptCenter.x;             p[0].y = m_ptCenter.y – m_nRadius;
   p[1].x = m_ptCenter.x – m_nRadius; p[1].y = m_ptCenter.y + m_nRadius;
   p[2].x = m_ptCenter.x + m_nRadius; p[2].y = p[1].y;
   VERIFY(pDC->Polygon(p, 3));
   break;
  }
  case MARKER_DIAMOND:
  {
   CPoint p[4];
   p[0].x = m_ptCenter.x;             p[0].y = m_ptCenter.y – m_nRadius;
   p[1].x = m_ptCenter.x + m_nRadius; p[1].y = m_ptCenter.y;
   p[2].x = m_ptCenter.x;             p[2].y = m_ptCenter.y + m_nRadius;
   p[3].x = m_ptCenter.x – m_nRadius; p[3].y = m_ptCenter.y;
   VERIFY(pDC->Polygon(p, 4));
   break;
  }
 }
 pDC->SelectObject(pOldBrush);
 pDC->SelectObject(pOldPen);
}

Hit-testing function HitTest() tests if the cursor hits a marker.

BOOL CMarker::HitTest(const CPoint& Point) const
{
 // Hit-test works best in device coordinates.
 switch(m_tShape)
 {
  case MARKER_CIRCLE:
  {
   CPoint Diff = m_ptCenter – Point;
   return ((Diff.x*Diff.x + Diff.y*Diff.y) <= m_nRadius*m_nRadius);

   break;
  }

  case MARKER_RECTANGLE:
  {
   CRgn rgn;
   rgn.CreateRectRgn(m_ptCenter.x-m_nRadius, m_ptCenter.y-m_nRadius,
   m_ptCenter.x+m_nRadius, m_ptCenter.y+m_nRadius);
   return rgn.PtInRegion(Point);

   break;
  }

  case MARKER_TRIANGLE:
  {
   CPoint p[3];
   p[0].x = m_ptCenter.x;             p[0].y = m_ptCenter.y - m_nRadius;
   p[1].x = m_ptCenter.x - m_nRadius; p[1].y = m_ptCenter.y + m_nRadius;
   p[2].x = m_ptCenter.x + m_nRadius; p[2].y = p[1].y;
   CRgn rgn;
   rgn.CreatePolygonRgn(p, 3, ALTERNATE);
   return rgn.PtInRegion(Point);

   break;
  }

  case MARKER_DIAMOND:
  {
   CPoint p[4];
   p[0].x = m_ptCenter.x;             p[0].y = m_ptCenter.y - m_nRadius;
   p[1].x = m_ptCenter.x + m_nRadius; p[1].y = m_ptCenter.y;
   p[2].x = m_ptCenter.x;             p[2].y = m_ptCenter.y + m_nRadius;
   p[3].x = m_ptCenter.x - m_nRadius; p[3].y = m_ptCenter.y;
   CRgn rgn;
   rgn.CreatePolygonRgn(p, 4, ALTERNATE);
   return rgn.PtInRegion(Point);

   break;
  }

  default:
   return FALSE;
 }
}

In the demo project, I create a simple graph with four
series of data. It demonstrates different options of
marker, such as shapes, colors, etc. When the cursor
hits a marker, the background data value will be
displayed by tool tip. Some API functions, such as
CRgn::PtInRegion(), in HitTest() work best in device
coordinates. It may be a good practice to convert the
test-point into device coordinates. In the demo, I create
one marker for each data series. If you want to use
different marker option within one data series, you
have to create one marker for each data point.

Download demo project – 26 KB

Date Last Updated: February 3, 1999

CodeGuru Logo

CodeGuru covers topics related to Microsoft-related software development, mobile development, database management, and web application programming. In addition to tutorials and how-tos that teach programmers how to code in Microsoft-related languages and frameworks like C# and .Net, we also publish articles on software development tools, the latest in developer news, and advice for project managers. Cloud services such as Microsoft Azure and database options including SQL Server and MSSQL are also frequently covered.

Property of TechnologyAdvice. © 2026 TechnologyAdvice. All Rights Reserved

Advertiser Disclosure: Some of the products that appear on this site are from companies from which TechnologyAdvice receives compensation. This compensation may impact how and where products appear on this site including, for example, the order in which they appear. TechnologyAdvice does not include all companies or all types of products available in the marketplace.