Shiv Kumar
 Hobbyist Filmmaker / Editor

Using a TChart Control in an ISAPI Application

Not Rated YetNot Rated YetNot Rated YetNot Rated YetNot Rated Yet0votes
January 08, 2008 12:05 PM  Views: 1661   Favorited: 0 Favorite It Comments: 0
Filed Under:  Programming
Tags:  Delphi, ISAPI
 
What is a web site without a graph of some sort huh ? I mean, it's just another image and web sites are full of images. Besides, the TChart control that comes with Delphi is a really nice control. It would be a shame not to be able to use it for graphical reports etc. in an ISAPI application. The TChart is an extensive graphing control. This tutorial is by no means a tutorial on how to use the TChart per se, but more on how to use it in an ISAPI application. If you're not familiar with the TChart control, it may be a good option to first play with it in a standar PC application before embarking on the ISAPI version and try to tweek the chart to the way you'd like it to be.

The Web Data Module by design does not allow us to Drop TControl descendants on it. This is normally not a problem, since an ISAPI/CGI application is a server side application. But in this case, since we want to generate a graph as an image and have the web browser display the graph as an image, we would have liked to be able to drop a TChart onto the web data module and use it. The solution is to create the chart on the fly at run time. Once we've created the chart, we can set the various properties we're interested in and have it generate the graph the way we'd like to see it.

The Project

The ISAPI application we are about to embark on here has only one action. This action expects to Query strings that give it enough information to be able to create a chart and stream it back as a JPEG image. The Query string should contain:
  1. The title of the Graph
  2. The title of each Bar and its value in the form of a Name=Value pair
The number of bars is virtually unlimited in this case due to the design of the aplication. The links below are demos of the application we are about to build.

Years Experience in Programming Languages

Motorcycles used for Years

Have a look at the source of this page to see how the parameters are being sent to the ISAPI application and relate it to the graph you see for each link.

exclamation.gif Note that there is no need to have the "Charting" functionlaity in a seperate ISAPI application. I've done this here to isolate other ISAPI related and database related issues and to show the "demos" above.
We could/should use a single Charting ISAPI in this way if there are a number of ISAPI applications that will need this functionality. Similar to the reason one would have DLLs or COM objects as part of an application. We should be aware however of the limitations of such a design. Since the QueryString variable in IIS is limited to 48K we may fall short in really extensive parameter passing using this method (GET method). There are atleast two options:
  1. Use some form of coded parameters in the URL rather than human readable parameters
  2. Use the POST method to send "data" to this ISAPI and use the ContentFields property of the Request object inside this ISAPI.

The Code

There is only one action is this application (waDrawChart) and its default property has been set to True. All this action really does is send back an JPEG image in the form of a stream.
procedure TWebModule1.WebModule1waDrawChartAction(Sender: TObject;
  Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
begin
  { The Query String for this action should be of the Format :
    Title=xxxx&Bar1Title=Bar1Value&Bar2Title=Bar2Value....
    If "Titles" has "spaces" then they should be replaced with "_" (underscore)
    this pertains to all titles
  }
  Response.ContentType := 'image/jpeg';
  Response.ContentStream := GenerateChart;
  Response.SendResponse;
end;
The meat of the work is done in the function GenerateChart. This function basically parses the query string received in the URL and constructs two dynamic arrays. One to hold the titles of each bar and the other to hold the corresponding values. This function also extracts the Title of for the Graph from the query string.
function TWebModule1.GenerateChart : TMemoryStream;
var
  ChartTitle : string;
  NoOfBars   : Integer;
  BarTitles  : array of string;
  BarValues  : array of Integer;
  i          : Integer;
  MemStrm    : TMemoryStream;
begin
  MemStrm := TMemoryStream.Create;
  try
  { No of Bars is 1 less than count, since TITLE is the first parameter }
  NoOfBars := Request.QueryFields.Count -1;
  { Set the size of the dynamic arrays }
  SetLength(BarTitles,NoOfBars);
  SetLength(BarValues, NoOfBars);
  { Get the Title of the Chart }
  ChartTitle := StringReplace(Request.QueryFields.Values['Title'],'_',' ',[rfReplaceAll]);
  { Start from 1 since we don't need the Chart Title here }
  for i := 1 to Request.QueryFields.Count -1 do
  begin
    { Don't Remove the underscores as yet ! }
    { Dynamic arrays are zero based ! }
    BarTitles[i-1] := Request.QueryFields.Names[i];
    BarValues[i-1] := StrToInt(Request.QueryFields.Values[BarTitles[i-1]]);
  end;
  { We now have a populated arrays of Bar Titles and their respective values }
  finally
  end;
  DrawChart(MemStrm, ChartTitle, BarTitles, BarValues);
  Result := MemStrm;
end;
Once all this information has been parsed, it in turn calls the procedure DrawChart sending it all the values along with the Memory stream object into which the graphs image should be saved.
procedure TWebModule1.DrawChart(var MemStream : TMemoryStream;ChartTitle: string;
  BarTitles: array of string; BarValues : array of Integer);
var
  HorizBarSeries : THorizBarSeries;
  FChart         : TChart;
  FJPEG          : TJPEGImage;
  Bitmap         : TBitmap;
  Rect           : TRect;
  i              : Integer;
begin
{ This is the function the actually creates the TChart on the fly and sends
  back a MemoryStream (containing a JPEG image
  You need to add the units: JPEG, Chart, Series, Graphics, Controls, TeEngine
  to the uses clause in the implementation section.
}
  FChart := TChart.Create(nil);
  FJPEG := TJPEGImage.Create;
  Bitmap := nil;
  try
    { Initialize Chart Properties }
    FChart.Color := clWhite;
    FChart.BevelOuter := bvNone;
    FChart.Legend.Visible := False;
    { Define the Size of the Chart Image }
    Rect.Left := 0;
    Rect.Top := 0;
    Rect.Right := 250;
    Rect.Bottom := 180;
    { Draw the Chart }
    HorizBarSeries := THorizBarSeries.Create(FChart);
    HorizBarSeries.BarStyle := bsRectGradient;
    HorizBarSeries.ParentChart := FChart;
    HorizBarSeries.ShowInLegend := False;
    HorizBarSeries.Marks.Style := smsValue;
    Randomize;
    with FChart do
    begin
      SeriesList.Clear;
      for i := Low(BarTitles) to High(BarTitles) do
      begin
         with HorizBarSeries do
        begin
          Add(BarValues[i], StringReplace(BarTitles[i],'_',' ',[rfReplaceAll]),Random(2147483648));
        end;
      end; { for i := Low(BarTitles) to High(BarTitles) do }
      SeriesList.Add(HorizBarSeries);
      with Title do
      begin
        Font.Size := 10;
        Font.Color := clBlack;
        Text.Clear;
        Text.Add(ChartTitle);
      end;
    end;
    { Generate the Chart as a Bitmap }
    Bitmap := FChart.TeeCreateBitmap(clWhite, Rect);
    FJPEG.Assign(Bitmap);
    FJPEG.SaveToStream(MemStream);
    MemStream.Position := 0;
  finally
    Bitmap.Free;
    FChart.Free;
    FJPEG.Free;
    { No need to Free MemStrm. The ISAPI Framework will take care of that }
  end;
end;
The source code is quite well commented and so I won't go into the explanation of what we're doing here.

Other Tricks

You will have noticed that I use this technique on my web site quite a bit. For example, the image you see at each tutorial for Topic Ratings. You can see the ratings of each of the topics from the Site Statistics page as well. Another place I use this technique is in the graphical report of the hits I receive for each of the pages on this site. You can see such this in the Bar Chart link of the Site Statistics page.

The Topic Ratings image that is produced is actually 4 (or more) images "concatenated" to form one single image. Basically, in this case, I generate the individual images and then draw them onto a larger image that is as big as would be required depending on the layout. Each image is drawn onto a specific portion of the larger image. By the time of all this is done, the result is what you see at the end of each tutorial. Once single image.

You might want to try the above technique as an exercise after you've played around with this project a bit.

Comments have been Disabled for this post





Leave A Comment

Shiv Kumar
Gainesville, Virginia,
United States
Member Bio Member Skills/Specialization

Bio

close

Specializations

close
Photographer
Landscape
Nature
Portrait
Videographer/Cinematographer
Interview
Landscape
Nature
Portrait
 
Privacy Policy | Terms Of Service | Contact Us | Support | Help/FAQ | News