teedbcrosstab.pas

来自「Delphi TeeChartPro.6.01的源代码」· PAS 代码 · 共 588 行

PAS
588
字号
{**********************************************}
{   TeeChart Database Cross-Tab Source         }
{   Copyright (c) 1996-2003 by David Berneda   }
{**********************************************}
unit TeeDBCrossTab;
{$I TeeDefs.inc}

interface

{ The procedure below creates an array of Chart Series
  and fills them using the DataSet parameter.

  The Series are created using the "AGroupField" parameter.

  The "ASeries" parameter will be used to duplicate it many times,
  one for each "group", thus using it as a template.

  Example of use:
  ---------------

  Imagine you have a table with "Product sales".

  In this table you have the following fields:

  Product    ( Cars, Bikes, Trucks... )
  Country    ( USA, UK, Germany, Australia... )
  Amount     ( $1234... )

  Now we want to create a crosstab Chart consisting of one Bar
  Series for each "Product", each one showing the sum of
  "Amount" for each "Country".

  So,
  our "GroupField" is "Product",
  our "LabelField" is "Country" and
  our "ValueField" is "Amount".

  Choose between 1 or 2:

  1) Calling a direct global procedure:

  FillDataSet( Table1, BarSeries1, "Product", "Country", "Amount", gfSum );

  After calling this procedure, the Chart will show several Series,
  one for each "Product".
  Each series will show the "Sum of Amount" by "Country".

  You can access and modify these Series as usually, like for example
  changing the Series Color, Title, etc.

  2) Use a TDBCrossTabSource component, set properties and Active:=True;

}
uses
  {$IFNDEF LINUX}
  Windows, Messages,
  {$ENDIF}
  SysUtils, Classes,
  {$IFDEF CLX}
  QGraphics, QControls, QForms, QDialogs, QStdCtrls, QExtCtrls,
  {$ELSE}
  Graphics, Controls, Forms, Dialogs, StdCtrls, ExtCtrls,
  {$ENDIF}
  DB, Chart, DBChart, TeEngine, TeeProcs, TeeSourceEdit, TeeDBEdit, TeCanvas;

type
  TGroupFormula = (gfCount, gfSum); { count records or sum record values }

  TDBCrossTabSource=class(TTeeSeriesDBSource)
  private
    FCase   : Boolean;
    FDataSet: TDataSet;
    FFormula: TGroupFormula;
    FGroup  : String;
    FLabel  : String;
    FValue  : String;

    ISource : TDBChartDataSource;
    procedure DataSourceCheckDataSet(ADataSet: TDataSet);
    procedure DataSourceCloseDataSet(ADataSet: TDataSet);
    Procedure LoadDataSet;
    procedure RemoveSeries;
    procedure SetDataSet(const Value: TDataSet);
    procedure SetFormula(const Value: TGroupFormula);
    procedure SetGroup(const Value: String);
    procedure SetLabel(const Value: String);
    procedure SetValue(const Value: String);
    procedure SetCase(const Value: Boolean);
  protected
    KeepDataOnClose : Boolean;
    procedure SetActive(const Value:Boolean); override;
  public
    Constructor Create(AOwner:TComponent); override;
    Destructor Destroy; override;

    class Function Available(AChart: TCustomAxisPanel):Boolean; override;
    class Function Description:String; override;
    class Function Editor:TComponentClass; override;
    class Function HasSeries(ASeries:TChartSeries):Boolean; override;
    Procedure Load; override;
  published
    property Active;
    property CaseSensitive:Boolean read FCase write SetCase default True;
    property DataSet: TDataSet read FDataSet write SetDataSet;
    property Formula : TGroupFormula read FFormula write SetFormula default gfSum;
    property GroupField : String read FGroup write SetGroup;
    property LabelField : String read FLabel write SetLabel;
    property Series;
    property ValueField : String read FValue write SetValue;
  end;

  TDBChartCrossTabEditor = class(TBaseDBChartEditor)
    Label1: TLabel;
    Label2: TLabel;
    Label3: TLabel;
    CBAgg: TComboFlat;
    CBValue: TComboFlat;
    CBGroup: TComboFlat;
    Label4: TLabel;
    CBLabels: TComboFlat;
    CBActive: TCheckBox;
    CBCase: TCheckBox;
    procedure CBSourcesChange(Sender: TObject);
    procedure FormShow(Sender: TObject);
    procedure BApplyClick(Sender: TObject);
    procedure CBAggChange(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure CBActiveClick(Sender: TObject);
    procedure CBCaseClick(Sender: TObject);
  private
    { Private declarations }
    DataSource : TDBCrossTabSource;
    Procedure EnableCombos;
  public
    { Public declarations }
  end;

Procedure FillDataSet( ADataSet:TDataSet;
                       ASeries:TChartSeries;
                       Const AGroupField,ALabelField,AValueField:String;
                       GroupFormula:TGroupFormula);

implementation

{$IFNDEF CLX}
{$R *.DFM}
{$ELSE}
{$R *.xfm}
{$ENDIF}

Const TeeMsg_CrossTab ='CrossTab';

type TSeriesAccess=class(TChartSeries);

{ Create a cross-tab of Series using DataSet records. }
Procedure FillDataSet( ADataSet:TDataSet;
                       ASeries:TChartSeries;
                       Const AGroupField,ALabelField,AValueField:String;
                       GroupFormula:TGroupFormula);
begin
  with TDBCrossTabSource.Create(nil) do
  try
    Series:=ASeries;
    DataSet:=ADataSet;
    GroupField:=AGroupField;
    LabelField:=ALabelField;
    ValueField:=AValueField;
    Formula:=GroupFormula;
    KeepDataOnClose:=True;
    Open;
  finally
    Free;
  end;
end;

type TChartAccess=class(TCustomAxisPanel);

Procedure TDBCrossTabSource.LoadDataSet;

   Function LocateSeries(Const ATitle:String):TChartSeries;
   var t : Integer;
       tmp : String;
   begin
     With Series.ParentChart do
     if FCase then
     begin
       for t:=0 to SeriesCount-1 do
           if Series[t].Title=ATitle then
           begin
             result:=Series[t];
             exit;
           end;
     end
     else
     begin
       tmp:=UpperCase(ATitle);
       for t:=0 to SeriesCount-1 do
           if UpperCase(Series[t].Title)=tmp then
           begin
             result:=Series[t];
             exit;
           end;
     end;

     result:=nil;
   end;

var tmpGroup   : String;
    tmpSeries  : TChartSeries;
    tmpLabel   : String;
    tmpValue   : Double;
    t,tt       : Integer;
    tmpPoint   : Integer;
    tmpBookMark : TBookMark;
    tmpGroupField,
    tmpValueField,
    tmpLabelField : TField;
begin
  RemoveSeries;

  With DataSet do
  begin
    tmpBookMark:=GetBookMark;
    DisableControls;
    try
      if LabelField='' then tmpLabelField:=nil
                       else tmpLabelField:=FieldByName(LabelField);

      if GroupField='' then
      begin
        tmpSeries:=Series;
        Series.Title:=ValueField;
        tmpGroupField:=nil;
      end
      else
      begin
        tmpSeries:=nil;
        tmpGroupField:=FieldByName(GroupField);
      end;

      tmpValueField:=FieldByName(ValueField);

      tmpValue:=1;

      First;
      While not eof do
      begin
        if GroupField<>'' then
        begin
          tmpGroup:=tmpGroupField.AsString;
          tmpSeries:=LocateSeries(tmpGroup);

          if tmpSeries=nil then
          begin
            if Series.Title='' then
               tmpSeries:=Series
            else
            begin
              with Series do
              begin
                tmpSeries:=CreateNewSeries(Owner,ParentChart,
                                          TChartSeriesClass(ClassType));

                tmpSeries.Clear;
                TSeriesAccess(tmpSeries).ManualData:=True;
                tmpSeries.AssignFormat(Series);
                tmpSeries.ShowInLegend:=ShowInLegend;
                tmpSeries.Active:=Active;
                tmpSeries.Brush:=Brush;
                tmpSeries.Pen:=Pen;
                TSeriesAccess(tmpSeries).InternalUse:=True;
              end;

              tmpSeries.SeriesColor:=Series.ParentChart.GetFreeSeriesColor;
              tmpSeries.Tag:=Integer(Series);

              for t:=0 to Series.Count-1 do
                  tmpSeries.Add(0,Series.Labels[t]);
            end;

            tmpSeries.Title:=tmpGroup;
          end;
        end;

        if Formula<>gfCount then tmpValue:=tmpValueField.AsFloat;

        if not Assigned(tmpLabelField) then
        begin
          tmpLabel:='';
          if tmpSeries.Count>0 then tmpPoint:=0
                               else tmpPoint:=-1;
        end
        else
        begin
          tmpLabel:=tmpLabelField.AsString;
          tmpPoint:=tmpSeries.Labels.IndexOfLabel(tmpLabel,FCase);
        end;

        if tmpPoint=-1 then
        begin
          tmpSeries.Add(tmpValue,tmpLabel,clTeeColor);

          with Series.ParentChart do
          for t:=0 to SeriesCount-1 do
          if tmpSeries<>Series[t] then
             if tmpSeries.Count>Series[t].Count then
                for tt:=1 to (tmpSeries.Count-Series[t].Count) do
                    Series[t].Add(0,tmpLabel);
        end
        else
        begin
          With tmpSeries.MandatoryValueList do
          case Formula of
            gfCount,
            gfSum: Value[tmpPoint]:=Value[tmpPoint]+tmpValue;
          end;
        end;

        Next;
      end;
    finally
      GotoBookMark(tmpBookMark);
      FreeBookmark(tmpBookMark);
      EnableControls;
      TChartAccess(Series.ParentChart).BroadcastSeriesEvent(Series,seAdd);
    end;
  end;
end;

Procedure TDBChartCrossTabEditor.EnableCombos;
begin
  EnableControls(DataSet<>nil,[CBAgg,CBValue,CBGroup,CBLabels]);
end;

procedure TDBChartCrossTabEditor.CBSourcesChange(Sender: TObject);
begin
  inherited;
  EnableCombos;
  FillFields([CBValue,CBGroup,CBLabels]);
  BApply.Enabled:=True;
end;

procedure TDBChartCrossTabEditor.FormShow(Sender: TObject);
begin
  SkipValidation:=True;

  inherited;

  if not Assigned(TheSeries) then exit;

  if TheSeries.DataSource is TDBCrossTabSource then
     DataSource:=TDBCrossTabSource(TheSeries.DataSource)
  else
  begin
    DataSource:=TDBCrossTabSource.Create(TheSeries.Owner);
    DataSource.Name:=TeeGetUniqueName(DataSource.Owner,'DBCrossTabSource');
  end;

  With CBSources do
       ItemIndex:=Items.IndexOfObject(DataSource.DataSet);

  EnableCombos;
  FillFields([CBValue,CBGroup,CBLabels]);

  if DataSource.Formula=gfSum then CBAgg.ItemIndex:=0
                              else CBAgg.ItemIndex:=1;

  With CBValue do ItemIndex:=Items.IndexOf(DataSource.ValueField);
  With CBGroup do ItemIndex:=Items.IndexOf(DataSource.GroupField);
  With CBLabels do ItemIndex:=Items.IndexOf(DataSource.LabelField);

  CBActive.Checked:=DataSource.Active;
  CBCase.Checked:=DataSource.CaseSensitive;

  BApply.Enabled:=Assigned(TheSeries) and (DataSource<>TheSeries.DataSource);
end;

{ TDBCrossTabSource }

constructor TDBCrossTabSource.Create(AOwner: TComponent);
begin
  inherited;
  FFormula:=gfSum;
  FCase:=True;
end;

class function TDBCrossTabSource.Description: String;
begin
  result:=TeeMsg_CrossTab;
end;

class function TDBCrossTabSource.Editor: TComponentClass;
begin
  result:=TDBChartCrossTabEditor;
end;

class function TDBCrossTabSource.HasSeries(
  ASeries: TChartSeries): Boolean;
begin
  result:=(ASeries.DataSource is TDBCrossTabSource);
end;

procedure TDBChartCrossTabEditor.BApplyClick(Sender: TObject);

  Function GetFieldCombo(Combo:TComboBox):String;
  begin
    With Combo do
    if ItemIndex=-1 then result:=Text
                    else result:=Items[ItemIndex];
  end;

begin
  inherited;

  CheckReplaceSource(DataSource);

  TheSeries.Tag:=0;

  with DataSource do
  begin
    Case CBAgg.ItemIndex of
      0: Formula:=gfSum;
    else
      Formula:=gfCount;
    end;

    ValueField:=GetFieldCombo(CBValue);
    GroupField:=GetFieldCombo(CBGroup);
    LabelField:=GetFieldCombo(CBLabels);
    DataSet:=Self.DataSet;

    CaseSensitive:=CBCase.Checked;
    Active:=CBActive.Checked;
  end;

  BApply.Enabled:=False;
end;

procedure TDBCrossTabSource.Load;
begin
  if Assigned(Series) and Assigned(DataSet) and
     (ValueField<>'') and DataSet.Active then
         LoadDataSet;
end;

procedure TDBChartCrossTabEditor.CBAggChange(Sender: TObject);
begin
  inherited;
  BApply.Enabled:=True;
end;

procedure TDBChartCrossTabEditor.FormDestroy(Sender: TObject);
begin
  if Assigned(DataSource) and
     (not Assigned(DataSource.Series)) then
          DataSource.Free;
  inherited;
end;

type TDBChartDataSourceAccess=class(TDBChartDataSource);

procedure TDBCrossTabSource.SetDataSet(const Value: TDataSet);
begin
  if FDataSet<>Value then
  begin
    Close;
    FDataSet:=Value;

    ISource.Free;
    ISource:=TDBChartDataSource.Create(nil); { 5.02 }
    with TDBChartDataSourceAccess(ISource) do
    begin
      SetDataSet(FDataSet);
      OnCheckDataSet:=DataSourceCheckDataSet;
      OnCloseDataSet:=DataSourceCloseDataSet;
    end;
  end;
end;

procedure TDBCrossTabSource.RemoveSeries;
var t : Integer;
begin
  t:=0;
  if Assigned(Series.ParentChart) then
  with Series.ParentChart do
  while t<SeriesCount do
    if (Series[t]<>Self.Series) and (TChartSeries(Series[t].Tag)=Self.Series) then
        Series[t].Free
    else
      Inc(t);

  if not (csDestroying in Series.ComponentState) then
  begin
    Series.Clear;
    Series.Title:='';
  end;
end;

procedure TDBCrossTabSource.DataSourceCloseDataSet(ADataSet: TDataSet);
begin
  if (not KeepDataOnClose) and Assigned(Series) then
     RemoveSeries;
end;

procedure TDBCrossTabSource.SetActive(const Value:Boolean);
begin
  inherited;
  if not Active then DataSourceCloseDataSet(DataSet);
end;

procedure TDBCrossTabSource.DataSourceCheckDataSet(ADataSet: TDataSet);
begin
  Refresh;
end;

class function TDBCrossTabSource.Available(AChart: TCustomAxisPanel):Boolean;
begin
  result:=AChart is TCustomChart;
end;

procedure TDBCrossTabSource.SetFormula(const Value: TGroupFormula);
begin
  if FFormula<>Value then
  begin
    Close;
    FFormula:=Value;
  end;
end;

procedure TDBCrossTabSource.SetGroup(const Value: String);
begin
  if FGroup<>Value then
  begin
    Close;
    FGroup:=Value;
  end;
end;

procedure TDBCrossTabSource.SetLabel(const Value: String);
begin
  if FLabel<>Value then
  begin
    Close;
    FLabel:=Value;
  end;
end;

procedure TDBCrossTabSource.SetValue(const Value: String);
begin
  if FValue<>Value then
  begin
    Close;
    FValue:=Value;
  end;
end;

destructor TDBCrossTabSource.Destroy;
begin
  ISource.Free;
  inherited;
end;

procedure TDBChartCrossTabEditor.CBActiveClick(Sender: TObject);
begin
  BApply.Enabled:=True;
end;

procedure TDBCrossTabSource.SetCase(const Value: Boolean);
begin
  if FCase<>Value then
  begin
    Close;
    FCase:=Value;
  end;
end;

procedure TDBChartCrossTabEditor.CBCaseClick(Sender: TObject);
begin
  BApply.Enabled:=True;
end;

initialization
  RegisterClass(TDBCrossTabSource);
  TeeSources.Add(TDBCrossTabSource);
finalization
  TeeSources.Remove(TDBCrossTabSource);
end.

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?