Selectable 3-D Scattergraph Using OpenGL

Environment: VC6

Introduction

This sample uses OpenGl to create a 3-D Scattergraph that the user can view from any angle by rotating with the mouse. The data can be shown with either orthographic or perspective projection. The data, and optionally the color of each point, are loaded from a text file. The application can be re-sized in the usual manner, and this re-sizes the graph. The slightly unusual feature is that the user can draw around points in the graph to select them, and then zoom the graph so as to magnify the region containing the selected points.

The project is an MFC dialog-based application. The dialog shows all the user-accessed controls, and it contains a CWnd-derived class that encapsulates all the graphing functionality.



Click here for larger image

In the figure above, the user has selected the cyan-coloured cluster of points by drawing around them after clicking Make in the Selection box. If the Zoom button were to be clicked, the graph would rescale so as to show only those points within the selected cluster. The whole dataset can be shown by selecting the Autoscale options for each dimension.

PLEASE NOTE: I do not claim to be an expert on OpenGL. I have coded this project using the OpenGL Programming Guide (3rd ed), other projects on the CodeGuru site as examples, and a whole load of trial and error. Feedback is welcomed!

Graphing Classes

class COpenGLWnd : public CWnd

This class is a generic base class for projects needing a CWnd-derived class to display OpenGL graphics. It draws heavily on the code architecture for the class GLEnabledView described in the Code Guru article "GL enabled view for MDI environment" by Alessandro Falappa, whose work I gratefully acknowledge. For simplicity I have removed some of the functionality (tessellator and quadric code) contained in that class because it was not needed for this project, but you can easily restore it by consulting that article.

The main additions/changes are as follows:

  1. COpenGLWnd derives from CWnd, rather than CView, and so can easily be incorporated into non-mdi/sdi projects.
  2. Optional text support has been added to the class, using the windows-specific wglUseFontBitmaps function. This is a lot easier than mapping out one's own font bitmaps, which seems to be the standard OpenGL method. To activate text support call COpenGLWnd::MakeFont() at some point in the initialisation process. At present this just makes the sysem font available for use, but it would not be hard to extend this to allow user-selection of any available font. To print text call glRasterPos3f to set the printing position, then call COpenGLWnd::PrintString(const char* str) to print the string str.
  3. GDI drawing support has been added. There is a virtual function COpenGLWnd::OnDrawGDI(CPaintDC *pDC) that gets called from OnPaint, and in which the user can write their own GDI code. I am not sure how robust this is, since the only information that I have been able to find on mixing OpenGL and GDI calls has been that it's not a good idea. However, the method I have used seems to work, at least on my computer...
  4. The rendered image can be copied to the clipboard as a bitmap.
class CGLMouseRotate : public COpenGLWnd

This class adds the button and mouse code that allows the user to rotate the image by dragging on the screen. A flag determines whether mouse dragging causes rotation or is ignored.

class CGLScatterGraph : public CGLMouseRotate

This class adds the main functionality for drawing the scatter graph.

Data are passed to the class using CGLScatterGraph::SetData(int count,COLORREF col,float *pCoords, COLORREF *pColList), where count is the number of points in the graph, col is the colour used to draw the points (only used if pColList==NULL), pCoords points to an array of floats containing the x, y and z coordinates of each point (so array length = count*3), and pColList is either NULL, or points to an array containing the colours for each point separately.
The array pointers pCoords and pColList have to be maintained by the calling class, and must not be deleted or go out of scope while the Scattergraph exists, unless a new call is made to SetData with count = 0.

Only data points whose coordinates fall within the maximum and minimum values for each of the axes are displayed. The axis scales can either be set explicitly by the user, or the graph can autoscale to the maximum and minimum values of any dimension.The graph can be set to display either an orthogonal or a perspective projection.

CGLSelectableScatterGraph : public CGLScatterGraph

This class adds the functionality that allows the user to select points in the graph by drawing around them. Selection mode is entered with a call to CGLSelectableScatterGraph::StartMakeSel(). This disables the mouse rotation facility, and enables a standard drawing routine rather like the original Scribble demo. The class maintains a list of CPoints that describe the shape drawn by the user. The shape can be erased and mouse rotation enabled by a call to CGLSelectableScatterGraph::CancelSel().

At the moment the only use that is made of the selection shape is in the CGLSelectableScatterGraph::ZoomSel() function.

The mapping of the screen shape to the data points is done using the OpenGL feedback mode. The following code shows how this is done:

BOOL CGLSelectableScatterGraph::ZoomSel()
{
   int i,j,count;
   int id=0;
   GLint rv;
   GLfloat token;
   GLfloat *pBuf;

// m_SelPts is a CArray of CPoints holding the coordinates 
// drawn previously by the user
  if (m_SelPts.GetSize()>2)
  {
  // make a region from selPts, need a standard array
     CPoint *pt=new CPoint[m_SelPts.GetSize()];
     for (i=0; i<m_SelPts.GetSize(); i++)
        pt[i]=m_SelPts.GetAt(i);
     CRgn rgn;
     VERIFY(rgn.CreatePolygonRgn(pt,
                                 m_SelPts.GetSize(),
                                 ALTERNATE));
     delete [] pt;

// get a list of coordinates using OpenGL feedback mode
// don't know proper way to calculate how much memory needed 
// for feedback buffer, but experiment shows the drawing has 
// 96 floats overhead for drawing the axes, + 4 floats per 
// point (makes sense as token + 3 coords) give some spare, 
// in case miscalculated !!
    pBuf=new GLfloat[200+m_Count*4]; // m_Count = number of points
                                     // in the graph
    BeginGLCommands(); // a base-class call that just enables the 
                       // OpenGL context
    glFeedbackBuffer(200+m_Count*4,GL_3D,pBuf);
    glRenderMode(GL_FEEDBACK);
    EndGLCommands();
// cause the window to be redrawn, thus calling the GL drawing
// code in feedback mode this puts a list of window coords for 
// each item drawn into pBuf, along with a token specifying type

    Invalidate();
    UpdateWindow();
    BeginGLCommands();
    rv=glRenderMode(GL_RENDER); // restore standard GL drawing mode

    int *pInSel=new int[m_Count]; // for each pt, will be 1 if 
// inside, 0 if outside shape need to know the viewport, so can 
// invert Y values (since OpenGL Y coords increase upwards, Win 
// coords increase downwards)

GLint viewport[4];
    glGetIntegerv(GL_VIEWPORT,viewport);
// now find which data points have which screen coords
// code for parsing feedback buffer comes from OpenGL Programming Guide
    count=rv;
    while (count)
    {
      token=pBuf[rv-count];
      count--;
      if (token==GL_POINT_TOKEN) // ignore the graph axis drawing code
      {
         GLdouble coords[3];
         for (j=0; j<3; j++)
         {
           coords[j]=pBuf[rv-count];
           count--;
         }
         CPoint pt;
         pt.x=int(coords[0]);
         pt.y=int(viewport[3]-coords[1]-1);
         pInSel[id++]=rgn.PtInRegion(pt);
      }
    }
    EndGLCommands();

// now we set the axis scales to draw the selected points 
// (plus any others within the axes limits)
    m_bAutoScaleX=m_bAutoScaleY=m_bAutoScaleZ=FALSE; // turn off
                                                     // autoscaling
// find max & min of pts within selection shape
    float xMax,xMin,yMax,yMin,zMax,zMin;
    xMax=yMax=zMax=-FLT_MAX;
    xMin=yMin=zMin=FLT_MAX;
    
// drawCount is index of points which were drawn within old axes, 
// and which therefore appear in pSelList
    int drawCount=-1; 
    for (i=0; i<m_Count; i++)
    {
// determine whether point was being drawn (is within old axes)
// and so will be in selection list (as either 1 if within shape 
// or 0 if outside it)
      if (!PtWithinAxes(m_pDat[i*3],m_pDat[i*3+1],m_pDat[i*3+2]))
         continue;  // ignore points outside axes
      drawCount++;
      if (pInSel[drawCount]==0)  // ignore this point, outside 
                                 // user selection
          continue;
       xMax=max(m_pDat[i*3],xMax);
       xMin=min(m_pDat[i*3],xMin);
       yMax=max(m_pDat[i*3+1],yMax);
       yMin=min(m_pDat[i*3+1],yMin);
       zMax=max(m_pDat[i*3+2],zMax);
       zMin=min(m_pDat[i*3+2],zMin);
     }
// prettify axis scales
    m_MaxX=NextAbove(xMax,5);
    m_MinX=NextBelow(xMin,5);
    m_MaxY=NextAbove(yMax,5);
    m_MinY=NextBelow(yMin,5);
    m_MaxZ=NextAbove(zMax,5);
    m_MinZ=NextBelow(zMin,5);
    Invalidate();

    delete [] pBuf;
    delete [] pInSel;
    rgn.DeleteObject();
    }
    
  m_bAllowMouseRotate=m_bOldAllowRotate; // restore ability to 
                                         // rotate using the mouse
  CancelSel();  // remove all points from m_SelPts, clear selection
                // shape from screen
  return TRUE;
}

Data Format

You can load data into the main program either using the Load button, or by dropping a file on the main window. The programme reads text files in any of 3 formats.

In all formats each point occupies a row of the text file.
Format 1: There are 3 tab- or space-separated columns, giving the x, y and z coordinates of the point. With this format the points are all drawn in one colour, as selected by the user from within the programme.
Format 2: There are 4 columns. The first three give the coordinates, the fourth is an integer value in the COLORREF range that gives the colour for that point.
Format 3: There are 6 columns. The first three give the coordinates, the next three give RGB values (range 0-255) for the colour for that point.

The files test1.txt, test2.txt and test3.txt give example data in the three formats.

Some problems

There is sometimes a nasty flicker when resizing the app. If anyone knows how to get rid of it, please let me know!

I tried putting some fog into the graph, which from the book looked like it should be pretty simple to do. But I got weird results - both the back and the front of the scene were fogged, while the mid-distance was clear. I then decided that life was short and that I didn't really want to fog my graph anyway. But it still irritates me, so if anyone actually knows how to get it to work properly, please let me know.

Downloads

Download demo project - 59 Kb


Comments

  • syntax error

    Posted by jag77 on 06/25/2009 12:29pm

    The code downloaded and when built, produced numerous warnings and two errors.  From file OpenGLWnd.cpp:
    
    void COpenGLWnd::StartStockDListDef()
    {
    // check if we aren't inside another couple begin/end
    	if(!m_bInsideDispList)
    	{
    // search a free slot
    		for (int c=0;m_DispListVector[c]!=0;c++);
    // check if we are inside a drawing session or not....
    		if(!( m_hRC==wglGetCurrentContext() && m_pCDC->GetSafeHdc()==wglGetCurrentDC() ))
    		{
    // ...if not specify the target DeviceContext of the subsequent OGL calls
    			wglMakeCurrent(m_pCDC->GetSafeHdc(), m_hRC);
    // set a warning for EndDispList
    			m_bExternDispListCall=TRUE;
    		};
    // create a handle to the disp list (actually an integer)
    		m_DispListVector[c]=glGenLists(1);
    // set a semaphore
    		m_bInsideDispList=TRUE;
    // start the disp list: all subsequent OGL calls will be redirected to the list
    		glNewList(m_DispListVector[c],GL_COMPILE);
    	};
    }
    
    In this line:
    for (int c=0;m_DispListVector[c]!=0;c++);
    
    variable C is declared, but is only local.
    
    In these two lines:
    m_DispListVector[c]=glGenLists(1);
    glNewList(m_DispListVector[c],GL_COMPILE);
    
    It is referenced, but no longer valid.  I will move the declaration out of the for loop and see what happens.  The original code needs a correction.

    • update

      Posted by jag77 on 06/25/2009 12:36pm

      I should have compiled and run first. It did compile and run so far as I can tell. Still playing with it. Other than the numerous warnings, looks good. Oh, environment is Windows XP, Visual Studio 2008.

      Reply
    Reply
  • Axis Color

    Posted by leedeblade on 04/10/2009 11:12am

    Some body know how to make the function change the color of X, Y Z axis and make gird for the coordinate ? Thanks in advance !

    Reply
  • How to blink a point in scatter plot

    Posted by bhanu3_vij on 09/12/2006 04:48am

    how to blink a point in a scatter plot. waiting for ur answers. Thank u all.

    Reply
  • how to modify the Scatter3DDlg to Formview?

    Posted by mark_maming on 11/14/2005 06:41am

    Your work is helpful. But I would like to use it as an embeded object, could you tell me how to modify the Scatter3DDlg to Formview? I tried several times but didn't work. Best Regards Mark

    Reply
  • How to put the left side in a pane and the other side in another pane?

    Posted by mark_maming on 11/11/2005 08:51am

    How to put the left side in a pane and the other side in another pane? Thanks in advance!

    Reply
  • How can I embed the set in my propertypage? Thanks in advance

    Posted by mark_maming on 10/10/2005 07:04pm

    My email address is: mark.ming.ma@gmail.com

    • William, thanks a lot, it's really helpful

      Posted by mark_maming on 10/26/2005 04:35am

      William: thanks a lot, it's really helpful! Best Regards, Mark

      Reply
    • putting it in a property page

      Posted by William Heitler on 10/26/2005 03:57am

      Putting the 3D graph into a property page is just like putting it into a standard dialog.
      
      In the *.h file you forward declare the class, and define a pointer to an instance:
      
      class CGLSelectableScatterGraph;
      class MyPropPage
      {
      ...
      	CGLSelectableScatterGraph m_p3DGraph;
      ...
      };
      
      In your resource file draw a frame in the location (e.g. IDC_3D) where you want the graph to appear, but make it invisible.
      
      In OnInitDialog of the property page have code something like this
      
      ...
      	CRect r;
      	CWnd *pW=GetDlgItem(IDC_3D);
      	pW->GetWindowRect(&r);	// top left marker
      	ScreenToClient(r);
      	m_p3DGraph= new CGLSelectableScatterGraph;
      	if (!m_p3DGraph->Create( NULL,   //CWnd default
      						NULL,   //has no name
      						WS_CHILD|WS_CLIPSIBLINGS|WS_CLIPCHILDREN|WS_VISIBLE,
      						r,
      						this,   //this is the parent
      						1002))     //this should really be a different number... check resource.h
      	{
      		TRACE("Failed to create Histogram window\n");
      		ASSERT(FALSE);
      	}
      
      	m_p3DGraph->SetProjection(0);
      	m_p3DGraph->SetSymbolSize(5.f);
      	m_p3DGraph->SetClearCol(RGB(255,255,255));
      	m_p3DGraph->SetRotationType(1);
      
      // some fake data
      	static float fake_data[]= {
             0.,			0.,	      0.,	
             1.,	      1.,	       1.,	
             0.5,	      1,	      0.,
      	   0.5,			0.5,		0.5
      	   };
      	static COLORREF fake_colList[] = {
      		RGB(255,0,0),	// red
      		RGB(255,255,0),	// yellow
      		RGB(0,255,255),	// cyan
      		RGB(255,0,255)
      	};
      	m_p3DGraph->SetData(4,fake_data,fake_colList);
      ...
      
      That should do it.

      Reply
    Reply
  • Thank you...

    Posted by strgzr on 07/04/2005 04:07am

    I'm new to OpenGL and find this article very useful to begin with.

    Reply
  • My thanks

    Posted by Legacy on 11/24/2003 12:00am

    Originally posted by: Dr. Ian D Wilson

    Very useful indeed!

    I am using it to generate topological graphs for a range of optimisation functions that will be 'solved' by final year mathematics under-graduates using genetic algorithms.

    If you can find the time to add a wire-frame to your scatter-graph I would be very grateful indeed (I simply cannot find the time at the moment).

    Best wishes

    Ian

    Reply
  • Just What I Needed

    Posted by Legacy on 03/28/2003 12:00am

    Originally posted by: Thomas Markas

    Just what I needed to get into OpenGL on the Windows environment. Its many moon again that I used OpenGL on a Unix box.

    Reply
  • Help!!! How to change the box size!

    Posted by Legacy on 02/06/2003 12:00am

    Originally posted by: Min Xu

    I have to question need help from you:
    1. how to change the box size easily by x, y, z.( eg.x=5cm, y=6cm z=8cm)
    2. how to move the image inside the box, up ,down, left, right, maybe controlled by keyboad?

    Thanks you very much!

    Reply
  • Loading, Please Wait ...

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • On-demand Event Event Date: September 10, 2014 Modern mobile applications connect systems-of-engagement (mobile apps) with systems-of-record (traditional IT) to deliver new and innovative business value. But the lifecycle for development of mobile apps is also new and different. Emerging trends in mobile development call for faster delivery of incremental features, coupled with feedback from the users of the app "in the wild." This loop of continuous delivery and continuous feedback is how the best mobile …

  • According to a recent Forrester total economic impact (TEI) study, enterprises can see a significant reduction in total cost of ownership by accessing Oracle Database in the cloud with a pay-as-you-go subscription model. This subscription service gives businesses the ability to scale up application environments for rapid prototyping, with far less time devoted to procuring licenses and deploying IT infrastructure. Read this study to learn how three different companies use Oracle Database in the cloud and the …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds