home *** CD-ROM | disk | FTP | other *** search
- {**************************************************}
- { This unit defines the chart types used in the }
- { Windows charting program PCHART.PAS. }
- { Zack Urlocker }
- { 05/02/91 }
- { }
- { Five types are defined: }
- { TChart: formal type for inheritance }
- { THBarChart: horizontal bar chart }
- { TVBarChart: vertical bar chart }
- { TV3DBarChart: vertical 3D bar chart }
- { TPieChart: pie chart }
- { all types have a common protocol that includes }
- { drawing, rescaling and stream storage }
- {**************************************************}
-
- unit Charts;
-
- {$IFDEF Final} { Remove debug code for final version}
- {$D-,I-,L-,R-,S-}
- {$ELSE}
- {$D+,I+,L+,R+,S+}
- {$ENDIF}
- interface
-
- uses WObjects, Dicts, WinTypes, WinProcs, Strings, StdDlgs, WinDOS;
-
- type
-
- { Abstract type provides inheritance for other chart types }
- PChart = ^TChart;
- TChart = object(TObject)
- { Object fields }
- Name : PChar; { title string }
- Scale : TPoint; { scaling factor }
- Area : TPoint; { size of the chart }
- Lead : TPoint; { lead before edges }
- Space : Integer; { space between items }
- Items : PDict; { key->value pairs }
-
- { Functions and procedures }
- constructor Init; { so that inheritance works }
- destructor Done; virtual;{ to clean up memory }
- procedure Draw(DC : HDC); virtual;
- procedure DrawTitle(DC : HDC); virtual;
- procedure DrawLabels(DC : HDC); virtual;
- procedure DrawData(DC : HDC); virtual;
- procedure ReScale; virtual;
- procedure AdjustScale(max : Integer); virtual;
- function getItem(x, y : integer) : PAssoc; virtual;
- constructor Load(var S: TStream);
- procedure Store(var S: TStream);
- procedure add(Key : PChar; Value : Integer);
- procedure remove(Key : PChar);
- procedure ResetLead; virtual;
- procedure ResetSpace; virtual;
- end; { Chart }
-
- PHBarChart = ^THBarChart;
- THBarChart = object(TChart) { Horizontal bars }
- procedure DrawLabels(DC : HDC); virtual;
- procedure DrawData(DC : HDC); virtual;
- procedure AdjustScale(max : Integer); virtual;
- function getItem(x, y : integer) : PAssoc; virtual;
- procedure ResetLead; virtual;
- end; { THBarChart }
-
- PVBarChart = ^TVBarChart;
- TVBarChart = object(TChart) { Vertical bars }
- procedure DrawLabels(DC : HDC); virtual;
- procedure DrawData(DC : HDC); virtual;
- procedure AdjustScale(max : Integer); virtual;
- function getItem(x, y : integer) : PAssoc; virtual;
- procedure ResetSpace; virtual;
- procedure ResetLead; virtual;
- end; { TVBarChart }
-
- PV3DBarChart = ^TV3DBarChart; { Vertical 3D bars }
- TV3DBarChart = object(TVBarChart)
- procedure DrawData(DC : HDC); virtual;
- end; { V3DBarChart }
-
- PPieChart = ^TPieChart;
- TPieChart = object(TChart) { Pie charts }
- procedure DrawLabels(DC : HDC); virtual;
- procedure DrawData(DC : HDC); virtual;
- procedure AdjustScale(max : Integer); virtual;
- function getItem(x, y : integer) : PAssoc; virtual;
- procedure ResetSpace; virtual;
- end; { TPieChart }
-
-
- implementation
-
- const
- Black = $000000; { Windows color constants }
- White = $FFFFFF;
- Blue = $FF0000;
- Green = $00FF00;
- Red = $0000FF;
-
-
- { ********* Chart ********* }
-
- constructor TChart.Init;
- begin
- GetMem(Name, 255);
- Scale.x := 0;
- Scale.y := 0;
- Area.x := 0;
- Area.y := 0;
- ResetLead;
- ResetSpace;
- new(Items, init(10,5));
- end;
-
- { Dispose of the chart by deallocating memory. }
- destructor TChart.Done;
- begin
- StrDispose(Name);
- Items^.Done;
- end;
-
- { Draw a chart in the area }
- procedure TChart.Draw(DC : HDC);
- var s : array[0..16] of char;
- begin
- if Name <> nil then
- DrawTitle(DC);
- if items^.size > 0 then
- begin
- DrawLabels(DC);
- DrawData(DC);
- end
- else
- begin
- strPCopy(S, '(Empty chart)');
- TextOut(DC, 1, 2, s, strLen(s));
- end;
- end;
-
- { Draw the title, centered in a custom font}
- procedure TChart.DrawTitle(DC : HDC);
- var FontInfo: TLogFont;
- oldFont, newFont : HFont;
- x : Integer;
- begin
- { set the font }
- with FontInfo do
- begin
- lfHeight := 30;
- lfWidth := 0;
- lfWeight := 700;
- lfItalic := 0;
- lfUnderLine := 0;
- lfStrikeOut := 0;
- lfQuality := Proof_Quality;
- strPcopy(lfFaceName, 'Tms Rmn');
- end;
-
- newFont := createFontIndirect(FontInfo);
- OldFont := SelectObject(DC, newFont);
-
- x := area.x div 2 - strLen(Name) * 10;
- TextOut(DC, x, 1, Name, strLen(Name));
-
- { Reset the font when done }
- selectObject(DC, oldFont);
- DeleteObject(newFont);
- end;
-
- { Force the chart to adjust its scale }
- procedure TChart.ReScale;
- var Max : Integer;
- begin
- Max := Items^.MaxValue;
- If Max > 0 then
- begin
- resetLead;
- resetSpace;
- adjustScale(Max);
- end;
- end;
-
- { Abstract methods that must be implemented in descendant classes. }
- procedure TChart.DrawData(DC : HDC);
- begin
- abstract;
- end;
-
- procedure TChart.DrawLabels(DC : HDC);
- begin
- abstract;
- end;
-
- procedure TChart.AdjustScale(max:Integer);
- begin
- abstract;
- end;
-
- function TChart.getItem(x, y : integer) : PAssoc;
- begin
- abstract;
- end;
-
-
- { File and stream I/O methods }
-
- constructor TChart.Load(var S:TStream);
- { Load a chart from a stream. Must be read in same order written. }
- begin
- Name := S.StrRead;
- Items := PDict(S.Get);
- end;
-
- procedure TChart.Store(var S:TStream);
- { Store a chart onto a stream. Not all object fields are stored.
- For example, scale, area, lead, space are set properly when
- you rescale. Must be read in the exact same order. }
- begin
- S.StrWrite(Name);
- S.Put(Items);
- end;
-
- { Miscelaneous access methods }
-
- procedure TChart.add(Key : PChar; Value : Integer);
- begin
- Items^.update(Key, Value);
- end;
-
- procedure TChart.remove(Key : PChar);
- begin
- Items^.remove(Key);
- end;
-
- procedure TChart.ResetLead;
- begin
- Lead.x := 10;
- Lead.y := 30;
- end;
-
- procedure TChart.ResetSpace;
- begin
- Space := 10;
- end;
-
-
- { ********* THBarChart ********* }
-
- { Draw labels with a stock font }
- procedure THBarChart.DrawLabels(DC : HDC);
- var I, x, y : Integer;
- str : PChar;
-
- procedure DrawLabel(Item : PAssoc); far;
- begin
- y := Lead.y + i*(Scale.y + space);
- str := Item^.key;
- TextOut(DC, x, y, str, strLen(str));
- inc(i);
- end;
-
- begin
- x := 1;
- i := 0;
- selectObject(DC, getStockObject(ansi_fixed_font));
- Items^.ForEach(@DrawLabel);
- selectObject(DC, getStockObject(system_font));
- end;
-
- { Draw the bars in the chart }
- procedure THBarChart.DrawData(DC : HDC);
- var I, x, y : Integer;
-
- procedure DrawItem(Item : PAssoc); far;
- begin
- y := Lead.y + i*(Scale.y + space);
- Rectangle(DC, x, y, round(x+Item^.value*scale.x), y+scale.y);
- inc(i);
- end;
-
- begin
- x := lead.x;
- i := 0;
- SelectObject(DC, CreateSolidBrush(Blue));
- Items^.ForEach(@DrawItem);
- DeleteObject(SelectObject(DC, GetStockObject(Black_Brush)));
- end;
-
- { Adjust the scale horizontally }
- procedure THBarChart.AdjustScale(max : Integer);
- begin
- scale.x := (area.x - 2 * lead.x) div max;
- scale.y := 25;
- end;
-
- { Return item found at location x, y }
- function THBarChart.getItem(x, y : integer) : PAssoc;
- var index : Integer;
- begin
- index := trunc((y - lead.y)/ (scale.y + space));
- if index < Items^.size then
- getItem := Items^.at(index)
- else
- getItem := nil;
- end;
-
- { Reset the lead for this type of chart }
- procedure THBarChart.resetLead;
- begin
- lead.x := 60;
- lead.y := 30;
- end;
-
-
- { ********* TVBarChart ********* }
-
- { Draw labels in color font }
- procedure TVBarChart.DrawLabels(DC : HDC);
- var I, x, y : Integer;
- str : PChar;
-
- procedure DrawLabel(Item : PAssoc); far;
- begin
- x := i*(Scale.x+space) + lead.x;
- str := Item^.key;
- TextOut(DC, x, y, str, strLen(str));
- inc(i);
- end;
-
- begin
- i := 0;
- y := area.y - lead.y+1;
- setTextColor(DC, Blue);
- Items^.ForEach(@DrawLabel);
- setTextColor(DC, Black);
- end;
-
- { Draw the bars in the chart }
- procedure TVBarChart.DrawData(DC : HDC);
- var I, x, y : Integer;
-
- procedure DrawItem(Item : PAssoc); far;
- begin
- x := Lead.x + i*(Scale.x + space);
- Rectangle(DC, x+Scale.x, area.y - lead.y, x,
- round(area.y-lead.y-Item^.value*scale.y));
- inc(i);
- end;
-
- begin
- i := 0;
- SelectObject(DC, CreateSolidBrush(Red));
- Items^.ForEach(@DrawItem);
- DeleteObject(SelectObject(DC, GetStockObject(Black_Brush)));
- end;
-
- { Adjust the scale vertically }
- procedure TVBarChart.AdjustScale(max : Integer);
- begin
- scale.x := 30;
- scale.y := (area.y - 2 * lead.y) div max;
- end;
-
- { Return item found at location x, y }
- function TVBarChart.getItem(x, y : integer) : PAssoc;
- var index : Integer;
- begin
- index := trunc((x - lead.x)/ (scale.x + space));
- if index < items^.size then
- getItem := Items^.at(index)
- else
- getItem := nil;
- end;
-
- { Reset the lead for this type of chart }
- procedure TVBarChart.resetLead;
- begin
- lead.x := 10;
- lead.y := 30;
- end;
-
- { Reset the space for this type of chart }
- procedure TVBarChart.ResetSpace;
- begin
- Space := 30;
- end;
-
-
- { ********* V3DBarChart *********}
-
- { Draw each 3D bar as a vertical bar, side and top polygons }
- procedure TV3DBarChart.DrawData(DC : HDC);
- var I, x, y : Integer;
-
- procedure DrawItem(Item : PAssoc); far;
- var points : array[1..4] of TPoint;
- begin
- x := Lead.x + i*(Scale.x + space);
- y := area.y-lead.y-Item^.value*scale.y;
- { regular vertical bar }
- Rectangle(DC, x+Scale.x, area.y - lead.y, x, y);
- { right side }
- points[1].x := x+Scale.x - 1 ;
- points[1].y := area.y - lead.y - 1;
- points[2].x := x+Scale.x + space div 2 - 1;
- points[2].y := area.y - lead.y - space div 2 - 1;
- points[3].x := points[2].x;
- points[3].y := y - space div 2;
- points[4].x := x+Scale.x - 1;
- points[4].y := y;
- Polygon(DC, points, 4);
- { top }
- points[1].x := x;
- points[1].y := points[4].y;
- points[2].x := x + space div 2;
- points[2].y := points[3].y;
- Polygon(DC, points, 4);
- inc(i);
- end;
-
- begin
- i := 0;
- SelectObject(DC, CreateSolidBrush(Green));
- Items^.ForEach(@DrawItem);
- DeleteObject(SelectObject(DC, GetStockObject(Black_Brush)));
- end;
-
-
- { ********* TPieChart ********* }
-
- const
- { This table is used to cycle through RGB values of 0,
- 128, 255 for each color. This provides 27 patterns,
- of which normally any consecutive 10 are unique. }
- colors : array[0..2] of byte = (0, 128, 255);
-
- { Draw the labels and legends using a custom logical font }
- procedure TPieChart.DrawLabels(DC : HDC);
- var I, x, y : Integer;
- s : PChar;
- newFont, oldFont : hFont;
- FontInfo : TLogFont;
-
- procedure DrawLabel(Item : PAssoc); far;
- var color : integer;
- begin
- y := lead.y + i * space;
- s := Item^.key;
- TextOut(DC, x, y, s, strLen(s));
-
- {$R- can cause a range error }
- color := RGB(colors[I mod 3],
- colors[(I div 3) mod 3],
- colors[(I div 9) mod 3]);
- {$R+ can cause a range error }
- SelectObject(DC, CreateSolidBrush(color));
- Rectangle(DC, x + 60, y, x + 90, y + space div 2);
- DeleteObject(SelectObject(DC, GetStockObject(Black_Brush)));
- inc(i);
- end;
-
- begin
- { Create a logical font and select it }
- with FontInfo do
- begin
- lfHeight := 18;
- lfWidth := 0;
- lfWeight := 700;
- lfUnderLine := 0;
- lfStrikeOut := 0;
- lfItalic := 0;
- strPcopy(lfFaceName, 'Tms Rmn');
- end;
- newFont := createFontIndirect(FontInfo);
- OldFont := SelectObject(DC, newFont);
- x := scale.x + space;
- i := 0;
- Items^.ForEach(@DrawLabel);
- { Reset the font when done }
- selectObject(DC, oldFont);
- DeleteObject(newFont);
- end;
-
- const TWO_PI = Pi * 2.0;
-
- { Draw the wedges in the pie }
- procedure TPieChart.DrawData(DC : HDC);
- var i, x, y, total : Integer;
- nsum : array [0..26] of Integer;
-
- { Accumulate running total for Pies }
- procedure addItems(Item : PAssoc); far;
- begin
- nsum[i+1] := nsum[i] + Item^.Value;
- inc(i);
- end;
-
- procedure DrawItem(Item : PAssoc); far;
- var color : Integer;
- begin
- {$R- can cause a range error }
- color := RGB(colors[I mod 3],
- colors[(I div 3) mod 3],
- colors[(I div 9) mod 3]);
- {$R+ can cause a range error }
- SelectObject(DC, CreateSolidBrush(color));
- Pie(DC, lead.x, lead.y,
- scale.x+lead.x, scale.y+lead.y,
- round(((x*cos(TWO_PI*nSum[i+1]/total)))+x)+lead.x,
- round(((y*sin(TWO_PI*nSum[i+1]/total)))+y)+lead.y,
- round(((x*cos(TWO_PI*nSum[i]/total)))+x)+lead.x,
- round(((y*sin(TWO_PI*nSum[i]/total)))+y)+lead.y);
- DeleteObject(SelectObject(DC, GetStockObject(Black_Brush)));
- inc(i);
- end;
-
- begin
- nsum[0] := 0;
- i := 0;
- Items^.ForEach(@AddItems);
- total := nsum[items^.size];
- x := scale.x div 2;
- y := scale.y div 2;
- i := 0;
- Items^.ForEach(@DrawItem);
- end;
-
- { Adjust the scale horizontally }
- procedure TPieChart.AdjustScale(max : Integer);
- begin
- scale.x := round(0.95 *(area.y - lead.y));
- scale.y := scale.x;
- end;
-
- { Return item found at legend location x, y }
- function TPieChart.getItem(x, y : integer) : PAssoc;
- var index : Integer;
- begin
- index := trunc((y - lead.y)/ (space));
- if (index < items^.size) and (x >= scale.x + space) then
- getItem := Items^.at(index)
- else
- getItem := nil;
- end;
-
- { Adjust the space for this type of chart }
- procedure TPieChart.resetSpace;
- begin
- space := area.y div 7;
- end;
-
-
- { Stream Registration records for each chart type }
-
- const
- RChart: TStreamRec = (
- ObjType: 1002;
- VmtLink: Ofs(TypeOf(TChart)^);
- Load: @TChart.load;
- Store: @TChart.store);
-
- RHBarChart: TStreamRec = (
- ObjType: 1003;
- VmtLink: Ofs(TypeOf(THBarChart)^);
- Load: @THBarChart.load;
- Store: @THBarChart.store);
-
- RVBarChart: TStreamRec = (
- ObjType: 1004;
- VmtLink: Ofs(TypeOf(TVBarChart)^);
- Load: @TVBarChart.load;
- Store: @TVBarChart.store);
-
- RV3DBarChart: TStreamRec = (
- ObjType: 1005;
- VmtLink: Ofs(TypeOf(TV3DBarChart)^);
- Load: @TV3DBarChart.load;
- Store: @TV3DBarChart.store);
-
- RPieChart: TStreamRec = (
- ObjType: 1006;
- VmtLink: Ofs(TypeOf(TPieChart)^);
- Load: @TPieChart.load;
- Store: @TPieChart.store);
-
-
- { Initialization }
- begin
- RegisterType(RChart);
- RegisterType(RHBarChart);
- RegisterType(RVBarChart);
- RegisterType(RV3DBarChart);
- RegisterType(RPieChart);
- end.
-