Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

An Interactive Periodic Table of the Elements

0.00/5 (No votes)
4 Oct 2003 1  
An article using GDI to create a scalable periodic table of the elements

PTE screenshot

Introduction

This article presents an example of using GDI to create an interactive Periodic table of the Elements (PTE). The PTE is resizable and the controls on the dialog also resize. This article is meant to demonstrate GDI, dynamically resizing controls and simple database access.

I added the ability to select a chemical group (halogens, noble gases, etc.) and then use slider controls to set the color for that group. Double-clicking an element will open a dialog box displaying details for that element. Right clicking an element will add that element to a listbox.

This app is a good starting point for further development and touches on the following MFC capabilities:

  • Database use
  • Slider controls
  • GDI output
  • Control size scaling

Using the Code

NOTE: Included with the demo project is an MS Access file - you must configure your ODBC manager to include a mapping to this MDB file - the datasource name must be PeriodicTableApp!

Two classes are presented which encapsulate the behaviors of the PTE:

  • pteTable - the Periodic table
  • pteElement - an element in the PTE

The table consists of an array of pteElement objects and members that define the size of the element cells, the offsets for the text and access methods to the element objects.

The pteTable Class

#ifndef __PTETABLE_H__
#define __PTETABLE_H__
#include "pteElement.h"
#include <afxdb.h>

#define NUM_ELEMENTS 118
#define NUM_GROUPS   10  

class pteTable 
{
    public:
                   pteTable::pteTable();
                   ~pteTable(); 
        // Reads in the element data from the database 
        void       pteTable::InitElements();
        // Sets the initial state of the chemical groups
        void       pteTable::InitGroups();
        // Draws the PTE
        void       pteTable::Draw(CPaintDC*, int);
        // Maps a mouse click to the element
        int        pteTable::FindElement(CPoint);
        // Returns a specified element
        pteElement pteTable::GetElement(int);
        // Returens the color for that chemical group
        COLORREF   pteTable::GetGroupBGColor(int);
        // Sets the color for a specified chemical group
        void       pteTable::SetGroupBGColor(int, COLORREF);
        // Used when drawing rectangles
        int        pteTable::GetCellWidth();
        // Used when drawing rectangles
        void       pteTable::SetCellWidth(int);
        // Sets the font to be used in the PTE
        void       pteTable::SetFontFace(CString);
        // Returns the current font used in the PTE
        CString    pteTable::GetFontFace();
        // Sets the point size for the font used in the PTE 
        void       pteTable::SetFontSize(int);
        // Returns the current font size
        int        pteTable::GetFontSize();
        // Used for positioning text during Draw
        int        pteTable::GetAtomicNumberXOffset();
        // Used for positioning text during Draw
        int        pteTable::GetAtomicNumberYOffset();
        // Used for positioning text during Draw
        int        pteTable::GetAtomicSymbolXOffset();
        // Used for positioning text during Draw
        int        pteTable::GetAtomicSymbolYOffset();
        // Get the row of an element
        int        pteTable::GetRow(int);
        // Get the column of an element
        int        pteTable::GetColumn(int);
        // Get the Atomic number of an element
        CString    pteTable::GetAtomicNumber(int);
        // Get the atomic symbol of an element
        CString    pteTable::GetAtomicSymbol(int);
        // Get the name of an element
        CString    pteTable::GetElementName(int);
        // Get the chemical group of an element
        CString    pteTable::GetGroup(int);
        // Sets the current element
        void       pteTable::SetCurrentElement(int);
        // Creates the invalidated region in prep for painting
        void       pteTable::BuildUpdateRegion(CDialog*, int, int);
        // Creates the invalidated region in prep for painting
        void       pteTable::BuildUpdateRegion(CDialog*, int);
        // Sets the pteElement CRect objects 
        void       pteTable::BuildElementRects();

    protected:
        // The array of elements
        pteElement Elements[NUM_ELEMENTS]; 
        // The width of the table cells
        int        CellWidth;
        // TextOut offset
        int        AtomicNumberXOffset;
        // TextOut offset
        int        AtomicNumberYOffset;
        // TextOut offset
        int        AtomicSymbolXOffset;
        // TextOut offset
        int        AtomicSymbolYOffset;
        // The currently active pteElement
        int        CurrentElement;
        // The background colors for each chemical group
        COLORREF   GroupBGColors[NUM_GROUPS];
        // Current font size
        int        FontSize;
        // Current font face
        CString    FontFace;
};

#endif //__PTETABLE_H__</afxdb.h>

The pteElement Class

#ifndef CLASSX_H_INCLUDED
#define CLASSX_H_INCLUDED 

class pteElement
{
    public:
                pteElement::pteElement();
                ~pteElement();
        // Simple access methods - the set methods are
        // called during initialization and
        // are populated from the database
        void    pteElement::SetAtomicNumber(CString);
        CString pteElement::GetAtomicNumber();
        void    pteElement::SetAtomicWeight(CString);
        CString pteElement::GetAtomicWeight();
        void    pteElement::SetGroupNumber(CString);
        CString pteElement::GetGroupNumber();
        void    pteElement::SetGroupName(CString);
        CString pteElement::GetGroupName();
        void    pteElement::SetPeriod(CString);
        CString pteElement::GetPeriod();
        void    pteElement::SetBlock(CString);
        CString pteElement::GetBlock();
        void    pteElement::SetCASRegistryID(CString);
        CString pteElement::GetCASRegistryID();
        void    pteElement::SetElementName(CString);
        CString pteElement::GetElementName();
        void    pteElement::SetElementSymbol(CString);
        CString pteElement::GetElementSymbol();
        void    pteElement::SetDiscoveryDate(CString);
        CString pteElement::GetDiscoveryDate();
        void    pteElement::SetDiscovererName(CString);
        CString pteElement::GetDiscovererName();
        void    pteElement::SetRow(CString);
        int     pteElement::GetRow();
        void    pteElement::SetColumn(CString);
        int     pteElement::GetColumn();

        // sets the members of the CRect object
        // member variable. Passed the width of the cell.
        void    pteElement::BuildRect(int);
        // returns TRUE if the point passed
        // to it lies within its CRect
        BOOL    pteElement::CheckHit(CPoint);
        // returns the CRect ElementArea
        CRect*  pteElement::GetRect();
        
    protected:
        // populated from database
        CString  AtomicNumber;
        CString  AtomicWeight;
        CString  GroupNumber;
        CString  GroupName;
        CString  Period;
        CString  Block;
        CString  CASRegistryID;
        CString  ElementName;
        CString  ElementSymbol;
        CString  DiscoveryDate;
        CString  DiscovererName;
        int      Row;
        int      Column;

        // the current screen coordinates
        // that this element occupies
        CRect    ElementArea;                    
        // The color for this element
        COLORREF CellColor;
};

#endif

Points of Interest

This project started last week when a user asked about implementing a PTE and wanted to know how best to determine which element was chosen by the user. Three options were suggested:

  • Using a button for each element
  • Using a single bitmap to represent the PTE and another bitmap in a MemDC with different colors for each element
  • Using GDI

I discounted using buttons because it is very unattractive and uses a lot of resources. I discounted the second because the text would look terrible as you scaled the window and because making changes would be very time consuming. I decided on GDI. The actual code to handle the drawing is only around 14 lines and allows for highlighting all of the elements in the current chemical group:

/* ******************************** */
/* Draw the table                   */
/*
The actual drawing of the entire table is 
accomplished in about a dozen lines.
Very simple and it allows for automatic resizing 
when the window size changes.

Because the row and column within the table 
is stored as a class member variable
in the pteElement class, computing the screen 
coordinates for that element is
very straightforward.

In drawing the elements, a few class methods are called:

COLORREF GetGroupBGColor(int);
This method takes an int representing the 
chemical group and returns 
the color for that groups background color

When selecting the brush to draw with, 
the following call is used:
GroupBrushes[atoi(Elements[x].GetGroupNumber())]
Elements[] is an array of pteElement objects. 
One of pteElement's methods is:
int GetGroupNumber();
and it returns the chemical group for that element. 
The resulting number is used as an index into the GroupBrush
array of CBrushes

Determining the location for the cell is 
accomplished with the calls
GetColumn() and GetRow()

The return value from those calls is multiplied by the current 
cellwidth - and this gives you the coordinates for
drawing the rectangle. 

Drawing the text is accomplished the same way.
*/
/* ******************************** */
void pteTable::Draw(CPaintDC* dc, int group)
{
    // declare and initialize the fonts
    CFont*  OldFont;
    CFont   NumberFont;
    CFont   SymbolFont;
    NumberFont.CreatePointFont(FontSize - 40, FontFace, NULL);
    SymbolFont.CreatePointFont(FontSize, FontFace, NULL);

    // declare and initialize the brushes
    CBrush* OldBrush;
    CBrush  GroupBrushes[NUM_GROUPS]; 
    for(int x = 0; x < NUM_GROUPS; x++)
        GroupBrushes[x].CreateSolidBrush(GetGroupBGColor(x));

    // declare and initialize the pens
    CPen*   OldPen;
    CPen    RedOutLinePen(PS_SOLID, 1, RGB(255, 0, 0));
    CPen    BlackOutLinePen(PS_SOLID, 1, RGB(0, 0, 0));

    // draw the elements
    for(x = 0; x < NUM_ELEMENTS; x++)
    {
        OldBrush = dc->SelectObject
          (&GroupBrushes[atoi(Elements[x].GetGroupNumber())]);

        // if you are drawing the active group, 
        //outline it in red, otherwise black
        if(atoi(Elements[x].GetGroupNumber()) == group)
            OldPen = dc->SelectObject(&RedOutLinePen);
        else
            OldPen = dc->SelectObject(&BlackOutLinePen);

        dc->Rectangle((
           (Elements[x].GetColumn() - 1) * CellWidth), 
           ((Elements[x].GetRow() - 1) * CellWidth), 
           ((Elements[x].GetColumn() - 1) * CellWidth) 
           + CellWidth, 
           ((Elements[x].GetRow() - 1) * CellWidth) + 
           CellWidth);
        dc->SelectObject(OldBrush);
        dc->SelectObject(OldPen);
        dc->SetBkMode(TRANSPARENT);
        
        OldFont = dc->SelectObject(&NumberFont);
        dc->TextOut((
          (Elements[x].GetColumn() - 1) * CellWidth) + 
          AtomicNumberXOffset, ((Elements[x].GetRow() - 1) 
          * CellWidth) + AtomicNumberYOffset, 
          Elements[x].GetAtomicNumber(), 
          Elements[x].GetAtomicNumber().GetLength());
        dc->SelectObject(&SymbolFont);
        dc->TextOut(((Elements[x].GetColumn() - 1) * 
          CellWidth) + AtomicSymbolXOffset, 
          ((Elements[x].GetRow()    - 1) * CellWidth) + 
          AtomicSymbolYOffset, 
          Elements[x].GetElementSymbol(), 
          Elements[x].GetElementSymbol().GetLength());
        dc->SelectObject(OldFont);
    }

    // clean-up fonts
    SymbolFont.DeleteObject();
    NumberFont.DeleteObject();
    // clean-up brushes
    for(x = 0; x < NUM_GROUPS; x++)
        GroupBrushes[x].DeleteObject();
    // clean-up pens
    RedOutLinePen.DeleteObject();
    BlackOutLinePen.DeleteObject();
}

History

  • Version 1.0 - Submitted 3rd October, 2003

License

This article has no explicit license attached to it, but may contain usage terms in the article text or the download files themselves. If in doubt, please contact the author via the discussion board below.

A list of licenses authors might use can be found here.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here