January 1999

Using TChart with C++Builder Professional and Client/Server

by Bill Whitney

The C++ Builder 3.0 professional and Client/Server edition ships with a VCL component called TChart. It's a powerful tool for creating charts and graphs, but the TChart printed documentation and online Help were (unfortunately) created for Delphi--leaving the C++ programmer with a bit of guesswork. Our goal in this article is to introduce you to TChart by showing you how to integrate some very basic charts into your C++Builder applications. The finished product that will be created can be seen in Figure A.

Figure A: The final graph incorporates all our desired features.

The basics

Let's start with a simple pie chart containing some fictitious data about programming language usage in the telecommunications industry, as shown in Table A. To follow along, start C++Builder and drop a button and a TChart onto the blank form. The form should now look something like Figure B.

Table A: Programming language usage
Language %
C++ 22
C 29
COBOL 35
Smalltalk 10
Ada 4

Figure B: The TChart sample application looks like this at design time.

Next, add the following code to Button1's OnClick event handler (double-click Button1 to open the OnClick event handler method in the code window):

TPieSeries *langs = new TPieSeries(this);
langs->ParentChart = Chart1;
langs->Add(22.0, "C++", clRed);
langs->Add(29.0, "C", clBlue);
langs->Add(35.0, "COBOL", clGreen);
langs->Add(10.0, "FORTRAN", clYellow);
langs->Add(4.0, "Ada", clBlack);
Run the program and click Button1 to see the pie chart. That was simple enough! You may find it peculiar that we never told Chart1, via its properties, that it was going to be a pie chart. That's taken care of by attaching a series to the TChart. A series is nothing more than a data set that contains a list of values along with their labels and colors. The type of series defines the way its data values will be plotted; we created an instance of TPieSeries to get a pie chart, for example. We then attached the series to the chart by setting langs' ParentChart property to Chart1.

Finally, the Add method creates each slice of the pie. The Add method accepts three arguments: the value to plot, a label, and the color that should be used to represent this value on the chart. Here's a look at Add's prototype:

int __fastcall Add(const double AValue, 
    const System::AnsiString ALabel,
    Graphics::TColor AColor);

Now, let's try our hand at a bar chart. Table B shows the number of CDs and cassettes sold between 1988 and 1996, along with their net dollar value (all figures are in millions).

Table B: CD and cassette sales in millions of units and dollars from 1988 to 1996
Year CD units CD value CAS units CAS value
1988 149.9 2089.9 450.1 3385.1
1989 207.5 2587.5 446.2 3345.8
1990 286.5 3451.6 442.2 3472.4
1991 333.3 4337.7 360.1 3019.6
1992 407.5 5326.5 336.4 3116.3
1993 495.4 6511.4 339.5 2915.8
1994 662.1 8464.5 345.4 2976.4
1995 722.9 9377.4 272.6 2303.6
1996 778.9 9934.7 225.3 1905.3

Replace the pie chart code in Button1's OnClick event handler with the following (be sure you delete all of the old code):

TBarSeries* cdSeries = 
    new TBarSeries(this);
cdSeries->ParentChart = Chart1;
cdSeries->Add(149.7, "", clRed);
cdSeries->Add(207.2, "", clRed);
cdSeries->Add(286.5, "", clRed);
cdSeries->Add(333.3, "", clRed);
cdSeries->Add(407.5, "", clRed);
cdSeries->Add(495.4, "", clRed);
cdSeries->Add(662.1, "", clRed);
cdSeries->Add(722.9, "", clRed);
cdSeries->Add(778.2, "", clRed);
Now, when you run the program, you've got a bar chart representing CD units shipped. You simply associated an instance of TBarSeries with Chart1 and added individual bars for each year. Notice that you can elect not to specify a label for each value by supplying empty quotes ("") as the label argument. If you don't specify a label, the value (first argument) will appear on the chart as the label. One look at the list of Add methods in our event handler would leave even the fastest typist searching for an easier way to specify a large number of values--and you're in luck. You can add the values to the cdSeries using the AddArray method. To do so, replace the OnClick event code in Button1 with the following:
double cdUnits[] = {149.7,207.2,286.5,
  333.3,407.5,495.4,662.1,722.9,778.9};

TBarSeries* cdSeries = 
    new TBarSeries(this);
cdSeries->ParentChart = Chart1;
cdSeries->
    AddArray(cdVals,
    sizeof(cdUnits)/sizeof(cdUnits[0]))-1);

The call to AddArray replaces the list of Add calls you had to type earlier. Keep in mind that if you use AddArray, labels will default to the value of each series element, and TChart will automatically select a color for you. You can also control the color of an entire series by setting the SeriesColor property, like this:

cdSeries->SeriesColor = clBlue;

Window dressing

Before you add the values for cassettes, let's dress things up a bit. You'll begin by changing Chart1's title to something meaningful. First, you'll clear the list of TStrings at Chart1->Title->Text (to remove the default value, TChart); then, you'll add your own title. Here's how it's done:
Chart1->Title->Text->Clear();
Chart1->Title->Text->
Add("Comparison of CD and Cassette Sales");
Chart1->Title->Text->
Add("From 1988 Through 1996");

Next, you add the units sold figures for cassettes (in millions). Here's what Button1's OnClick event handler should look like with the cassette data added:

double cdUnits[] = {149.7,207.2,286.5,
  333.3,407.5,495.4,662.1,722.9,778.9};
double caUnits[] = {450.1,446.2,442.2,
  360.1,366.4,339.5,345.4,272.6,225.3};

TBarSeries* cdSeries = new TBarSeries(this);
TBarSeries* caSeries = new TBarSeries(this);
cdSeries->Title = "CD Units";
caSeries->Title = "Cassette Units";
cdSeries->ParentChart = Chart1;
caSeries->ParentChart = Chart1;
cdSeries->AddArray(cdUnits,   
  (sizeof(cdUnits)/sizeof(cdUnits[0]))-1);
caSeries->AddArray(caUnits, 
  (sizeof(caUnits)/sizeof(caUnits[0]))-1);
Chart1->Title->Text->Clear();
Chart1->Title->Text->
  Add("Comparison of CD and Cassette Sales");
Chart1->Title->Text->
  Add("From 1988 Through 1996");

When you run the program, you'll notice that TChart has automatically assigned a unique color to each series (remember, you can control the color for an entire series with the SeriesColor property). The only problem is those little labels above each column--they make it difficult to see all of the bars. You can remove them by setting the Marks->Visible property to false for each series. Try adding these two lines just before setting cdSeries->ParentChart to Chart1:

cdSeries->Marks->Visible = false;
caSeries->Marks->Visible = false;

Not bad so far, but the chart is still missing the dollar value figures. Since adding another set of bars for the dollar values will really muddy up the chart, let's add the dollar figures using another type of series called TFastLine (you can check the online Help for a complete list of series types and their properties). In addition to allowing you to associate multiple series with a single chart, TChart lets you combine different types of series, as well. Because the dollar figures are considerably larger than the units sold figures, you'll move the decimal point one space to the left (stating the figures in tens of millions). The new Button1 OnClick event handler appears in Listing A.

Listing A: Button1 OnClick event handler

double cdUnits[] = {149.7,207.2,286.5,
  333.3,407.5,495.4,662.1,722.9,778.9};
double cdValue[] = {208.99,258.75,345.16,
  433.77,532.65,651.14,846.45,937.74,993.47};
double caUnits[] = {450.1,446.2,442.2,
  360.1,366.4,339.5,345.4,272.6,225.3};
double caValue[] = {338.51,334.58,347.24,
  301.96,311.63,291.58,297.64,230.36,190.53};

TBarSeries* cdSeries = new TBarSeries(this);
TBarSeries* caSeries = new TBarSeries(this);
TFastLineSeries *cdDollars = new 
    TFastLineSeries(this);
TFastLineSeries *caDollars = new 
    TFastLineSeries(this);
cdSeries->Title = "CD Units";
caSeries->Title = "Cassette Units";
cdDollars->Title = "CD $ (x10 Mil)";
caDollars->Title = "Cassette $ (x10 Mil)";
cdSeries->Marks->Visible = false;
caSeries->Marks->Visible = false;
cdSeries->ParentChart = Chart1;
caSeries->ParentChart = Chart1;
cdDollars->ParentChart = Chart1;
caDollars->ParentChart = Chart1;
cdDollars->SeriesColor = 
    cdSeries->SeriesColor;
caDollars->SeriesColor = 
    caSeries->SeriesColor;
cdSeries->AddArray(cdUnits, 
  (sizeof(cdUnits)/sizeof(cdUnits[0]))-1);
caSeries->AddArray(caUnits, 
  (sizeof(caUnits)/sizeof(caUnits[0]))-1);
cdDollars->AddArray(cdValue, 
  (sizeof(cdValue)/sizeof(cdValue[0]))-1);
caDollars->AddArray(caValue, 
  (sizeof(caValue)/sizeof(caValue[0]))-1);
Chart1->Title->Text->Clear();
Chart1->Title->Text->
  Add("Comparison of CD and Cassette Sales");
Chart1->Title->Text->
  Add("From 1988 Through 1996");

Notice how the code uses the SeriesColor property to set cdDollars and caDollars so that they matched their counterparts (cdSeries and caSeries). It wasn't necessary to set the Marks->Visible property to false for the TFastLine series, because it doesn't support Marks (you'd need to use TLineSeries to get them--TFastLine's interface isn't as robust, as it was meant for operations where speed counts).

When you run the program now, you'll notice that you still need to do something with the X-axis labels. They're supposed to relay the year to which the sales figures apply, but default to the values 0 to 8 instead (the offsets of the series values in the array). Let's change that now by placing the following for loop after the AddArray statements:

for ( int i = 1988; i < 1997; i++ )
   cdSeries->XLabel[i - 1988] = i;

Each series element has an associated label stored in the XLabel array. You can change any value's label by changing elements in this array. Figure A, on the cover, shows the final product.

Some other useful points

If you run the application again, you'll notice that the chart is actually three-dimensional. You can control the appearance of the chart's depth by adjusting the Chart3DPercent property. If you ever need to find out how many values a series contains, you can call the series' Count() method like this:
long x = caDollars->Count();
Removing a series from a chart is no big deal--simply deactivate it, disassociate it from the parent TChart, and then delete it, as follows:
caSeries->Active= false;
caSeries->ParentChart = NULL;
delete caSeries;
You can prevent a series from appearing in your chart's legend by setting its ShowInLegend property to false:
cdSeries->ShowInLegend = false;   

If you look at the online Help and Object Inspector for TChart, you'll see that many methods and properties are available. Your charts can even respond to events (such as drag and drop).

Conclusion

We haven't shown you all the options and chart types available with the TChart component, but you should now be able to build simple charts into any C++Builder application. If you like the capabilities of TChart, be sure to check out the database and report versions (DBChart and QRChart respectively).