mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-07-23 17:06:20 +02:00
224 lines
6.9 KiB
ObjectPascal
224 lines
6.9 KiB
ObjectPascal
{ Demonstration how a Gantt chart can be constructed out of a TStateSeries.
|
|
It contains two overlaid StateSeries, one for the planned tasks and one
|
|
for the achieved tasks.
|
|
|
|
Since in this constellation the same data (start/end date, task name) are
|
|
used at several places, a common data structure is provided in unit uGanttData.
|
|
|
|
The StateSeries get their its data from this data structure via
|
|
UserDefinedChartSources.
|
|
}
|
|
|
|
unit uGanttFrame;
|
|
|
|
{$mode ObjFPC}{$H+}
|
|
|
|
interface
|
|
|
|
uses
|
|
SysUtils, Classes, Math,
|
|
Graphics, Forms, Controls, StdCtrls, ExtCtrls,
|
|
TAGraph, TAChartUtils, TACustomSource, TAIntervalSources, TASources,
|
|
TACustomSeries, TASeries, TAMultiSeries, TATools,
|
|
uGanttData;
|
|
|
|
type
|
|
TGanttFrame = class(TFrame)
|
|
cbShowCompleteness: TCheckBox;
|
|
cbRotated: TCheckBox;
|
|
DateTimeIntervalChartSource: TDateTimeIntervalChartSource;
|
|
GanttChart: TChart;
|
|
GanttChartToolset: TChartToolset;
|
|
GanttCompletedSeries: TStateSeries;
|
|
GanttDataPointHintTool: TDataPointHintTool;
|
|
GanttPanDragTool: TPanDragTool;
|
|
GanttSeries: TStateSeries;
|
|
GanttZoomDragTool: TZoomDragTool;
|
|
Panel1: TPanel;
|
|
Tasks_ChartSource: TUserDefinedChartSource;
|
|
Completed_ChartSource: TUserDefinedChartSource;
|
|
procedure cbRotatedChange(Sender: TObject);
|
|
procedure cbShowCompletenessChange(Sender: TObject);
|
|
procedure GanttSeriesGetMarkText({%H-}ASeries: TChartSeries;
|
|
APointIndex, {%H-}AXIndex, {%H-}AYIndex: Integer; var AFormattedMark: String);
|
|
procedure Tasks_ChartSourceGetChartDataItem(ASource: TUserDefinedChartSource;
|
|
AIndex: Integer; var AItem: TChartDataItem);
|
|
private
|
|
FTasks: TGanttTaskList;
|
|
procedure PrepareData;
|
|
|
|
public
|
|
constructor Create(AOwner: TComponent); override;
|
|
destructor Destroy; override;
|
|
|
|
end;
|
|
|
|
implementation
|
|
|
|
{$R *.lfm}
|
|
|
|
constructor TGanttFrame.Create(AOwner: TComponent);
|
|
begin
|
|
inherited;
|
|
|
|
FTasks := TGanttTaskList.Create;
|
|
FTasks.AddTask('Kick-off', EncodeDate(2024,2,3), 1, 100);
|
|
FTasks.AddTask('Activity 1', EncodeDate(2024,2,4), 4, 100);
|
|
FTasks.AddTask('Activity 2', EncodeDate(2024,2,8), 3, 100);
|
|
FTasks.AddTask('Activity 3', EncodeDate(2024,2,11), 10, 100);
|
|
FTasks.AddMilestone('Intermediate Milestone', EncodeDate(2024,2,21), true);
|
|
FTasks.AddTask('Activity 4', EncodeDate(2024,2,21), 7, 80);
|
|
FTasks.AddTask('Activity 5', EncodeDate(2024,2,28), 8, 10);
|
|
FTasks.AddMilestone('Final Milestone', EncodeDate(2024,3,7), false);
|
|
|
|
PrepareData;
|
|
end;
|
|
|
|
destructor TGanttFrame.Destroy;
|
|
begin
|
|
FTasks.Free;
|
|
inherited Destroy;
|
|
end;
|
|
|
|
// Checkbox clicked to toggle between "normal" and "rotated" state series
|
|
// (horizontal or vertical orientation)
|
|
procedure TGanttFrame.cbRotatedChange(Sender: TObject);
|
|
begin
|
|
if cbRotated.Checked then
|
|
begin
|
|
GanttSeries.AxisIndexX := 0;
|
|
GanttSeries.AxisIndexY := 1;
|
|
GanttCompletedSeries.AxisIndexX := 0;
|
|
GanttCompletedSeries.AxisIndexY := 1;
|
|
|
|
// Bottom axis marks
|
|
GanttChart.BottomAxis.Marks.Source := Tasks_ChartSource;
|
|
GanttChart.BottomAxis.Marks.Style := smsLabel;
|
|
GanttChart.BottomAxis.Marks.SourceExchangeXY := true;
|
|
GanttChart.BottomAxis.Marks.LabelFont.Orientation := 900;
|
|
GanttChart.BottomAxis.Grid.Visible := false;
|
|
|
|
// Left axis marks
|
|
GanttChart.LeftAxis.Marks.Source := DateTimeIntervalChartSource;
|
|
GanttChart.LeftAxis.Marks.Style := smsLabel;
|
|
GanttChart.LeftAxis.Grid.Visible := true;
|
|
|
|
GanttChart.Margins.Top := 0;
|
|
GanttChart.Margins.Left := 10;
|
|
end else
|
|
begin
|
|
GanttSeries.AxisIndexX := 1;
|
|
GanttSeries.AxisIndexY := 0;
|
|
GanttCompletedSeries.AxisIndexX := 1;
|
|
GanttCompletedSeries.AxisIndexY := 0;
|
|
|
|
// Left axis marks
|
|
GanttChart.LeftAxis.Marks.Source := Tasks_ChartSource;
|
|
GanttChart.LeftAxis.Marks.Style := smsLabel;
|
|
GanttChart.LeftAxis.Grid.Visible := false;
|
|
|
|
// Bottom axis marks
|
|
GanttChart.BottomAxis.Marks.Source := DateTimeIntervalChartSource;
|
|
GanttChart.BottomAxis.Marks.Style := smsLabel;
|
|
GanttChart.BottomAxis.Marks.SourceExchangeXY := false;
|
|
GanttChart.BottomAxis.Marks.LabelFont.Orientation := 0;
|
|
GanttChart.BottomAxis.Grid.Visible := true;
|
|
|
|
GanttChart.Margins.Left := 0;
|
|
GanttChart.Margins.Top := 10;
|
|
end;
|
|
end;
|
|
|
|
procedure TGanttFrame.cbShowCompletenessChange(Sender: TObject);
|
|
begin
|
|
GanttCompletedSeries.Active := cbShowCompleteness.Checked;
|
|
end;
|
|
|
|
{ Constructs the Mark text to be used in the popup hint. }
|
|
procedure TGanttFrame.GanttSeriesGetMarkText(ASeries: TChartSeries;
|
|
APointIndex, AXIndex, AYIndex: Integer; var AFormattedMark: String);
|
|
const
|
|
NO_Yes: array[boolean] of string = ('not ', '');
|
|
var
|
|
t, tPlan1, tPlan2: Double;
|
|
completeness: Double;
|
|
txt: String;
|
|
item: TBasicGanttItem;
|
|
begin
|
|
item := FTasks[APointIndex];
|
|
txt := item.Title;
|
|
if item is TGanttTask then
|
|
begin
|
|
tPlan1 := TGanttTask(item).StartDate;
|
|
tPlan2 := TGanttTask(item).EndDate;
|
|
completeness := TGanttTask(item).PercentageComplete;
|
|
AFormattedMark := Format(
|
|
'Task "%s":' + LineEnding +
|
|
'⯄ %s - %s' + LineEnding +
|
|
'⯄ %.0f%% complete', [
|
|
txt, DateToStr(tPlan1), DateToStr(tPlan2), completeness
|
|
]);
|
|
end
|
|
else
|
|
if item is TGanttMilestone then
|
|
begin
|
|
t := TGanttMileStone(item).DateDue;
|
|
AFormattedMark := Format(
|
|
'"%s":' + LineEnding +
|
|
'⯄ due %s' + LineEnding +
|
|
'⯄ %scomplete', [
|
|
txt, DateToStr(t), NO_YES[TGanttMilestone(item).Complete]
|
|
]);
|
|
end;
|
|
end;
|
|
|
|
{ Extracts the data for the data point at index AIndex and transfers them
|
|
to the chartdataitem AItem needed by the series. }
|
|
procedure TGanttFrame.Tasks_ChartSourceGetChartDataItem(ASource:
|
|
TUserDefinedChartSource; AIndex: Integer; var AItem: TChartDataItem);
|
|
var
|
|
dt1, dt2: TDateTime;
|
|
begin
|
|
if FTasks[AIndex] is TGanttTask then
|
|
begin
|
|
dt1 := TGanttTask(FTasks[AIndex]).StartDate;
|
|
dt2 := TGanttTask(FTasks[AIndex]).EndDate;
|
|
if ASource = Completed_ChartSource then
|
|
dt2 := dt1 + TGanttTask(FTasks[AIndex]).PercentageComplete/100 * (dt2 - dt1);
|
|
end else
|
|
if FTasks[AIndex] is TGanttMilestone then
|
|
begin
|
|
if TGanttMilestone(FTasks[AIndex]).Complete or (ASource = Tasks_ChartSource) then
|
|
dt1 := TGanttMilestone(FTasks[AIndex]).DateDue
|
|
else
|
|
dt1 := NaN;
|
|
dt2 := dt1;
|
|
end;
|
|
AItem.Text := FTasks[AIndex].Title;
|
|
AItem.SetX(0, dt1);
|
|
AItem.SetX(1, dt2);
|
|
AItem.SetY(AIndex);
|
|
end;
|
|
|
|
procedure TGanttFrame.PrepareData;
|
|
begin
|
|
// Series with planned tasks
|
|
Tasks_ChartSource.XCount := 2;
|
|
Tasks_ChartSource.PointsNumber := FTasks.Count;
|
|
GanttSeries.Source := Tasks_ChartSource;
|
|
|
|
// Series showing to which percentage the planned tasks are completed
|
|
Completed_ChartSource.XCount := 2;
|
|
Completed_ChartSource.PointsNumber := FTasks.Count;
|
|
GanttCompletedSeries.Source := Completed_ChartSource;
|
|
GanttCompletedSeries.ZPosition := 1; // Draw it before GanttSeries
|
|
|
|
// Show the task titles as y axis labels
|
|
GanttChart.LeftAxis.Marks.Source := Tasks_ChartSource;
|
|
GanttChart.LeftAxis.Marks.Style := smsLabel;
|
|
GanttChart.LeftAxis.Inverted := true;
|
|
end;
|
|
|
|
end.
|
|
|