2015-01-05 23:32:49 +00:00
|
|
|
{ fpspreadsheetchart.pas }
|
2010-05-01 18:10:38 +00:00
|
|
|
|
2015-01-05 23:32:49 +00:00
|
|
|
{@@ ----------------------------------------------------------------------------
|
|
|
|
Chart data source designed to work together with TChart from Lazarus
|
|
|
|
to display the data and with FPSpreadsheet to load data.
|
|
|
|
|
|
|
|
AUTHORS: Felipe Monteiro de Carvalho, Werner Pamler
|
|
|
|
|
|
|
|
LICENSE: See the file COPYING.modifiedLGPL.txt, included in the Lazarus
|
|
|
|
distribution, for details about the license.
|
|
|
|
-------------------------------------------------------------------------------}
|
2010-05-01 18:10:38 +00:00
|
|
|
|
|
|
|
unit fpspreadsheetchart;
|
|
|
|
|
|
|
|
{$mode objfpc}{$H+}
|
|
|
|
|
|
|
|
interface
|
|
|
|
|
|
|
|
uses
|
2014-06-19 19:25:40 +00:00
|
|
|
Classes, SysUtils, LResources, Forms, Controls, Graphics, Dialogs,
|
2010-05-01 18:10:38 +00:00
|
|
|
// TChart
|
2023-11-21 15:18:51 +00:00
|
|
|
TATypes, TATextElements, TACustomSource, TAGraph,
|
2015-01-05 23:32:49 +00:00
|
|
|
// FPSpreadsheet Visual
|
2023-11-21 15:18:51 +00:00
|
|
|
fpSpreadsheetCtrls, fpSpreadsheetGrid, fpsVisualUtils,
|
2018-07-09 17:27:22 +00:00
|
|
|
// FPSpreadsheet
|
2023-11-21 15:18:51 +00:00
|
|
|
fpsTypes, fpSpreadsheet, fpsUtils, fpsChart;
|
2010-05-01 18:10:38 +00:00
|
|
|
|
|
|
|
type
|
|
|
|
|
|
|
|
{@@ Chart data source designed to work together with TChart from Lazarus
|
|
|
|
to display the data.
|
|
|
|
|
|
|
|
The data can be loaded from a TsWorksheetGrid Grid component or
|
|
|
|
directly from a TsWorksheet FPSpreadsheet Worksheet }
|
|
|
|
|
2015-01-03 20:03:55 +00:00
|
|
|
{ TsWorkbookChartSource }
|
|
|
|
|
2018-07-22 13:58:46 +00:00
|
|
|
TsXYLRange = (rngX, rngY, rngLabel);
|
2015-01-03 20:03:55 +00:00
|
|
|
|
2015-01-06 17:42:54 +00:00
|
|
|
TsWorkbookChartSource = class(TCustomChartSource, IsSpreadsheetControl)
|
2015-01-03 20:03:55 +00:00
|
|
|
private
|
|
|
|
FWorkbookSource: TsWorkbookSource;
|
2016-03-18 19:50:40 +00:00
|
|
|
// FWorkbook: TsWorkbook;
|
2018-07-22 13:58:46 +00:00
|
|
|
FWorksheets: array[TsXYLRange] of TsWorksheet;
|
|
|
|
FRangeStr: array[TsXYLRange] of String;
|
|
|
|
FRanges: array[TsXYLRange] of TsCellRangeArray;
|
2015-01-03 20:03:55 +00:00
|
|
|
FPointsNumber: Cardinal;
|
2018-07-22 13:58:46 +00:00
|
|
|
function GetRange(AIndex: TsXYLRange): String;
|
2015-01-03 20:03:55 +00:00
|
|
|
function GetWorkbook: TsWorkbook;
|
2018-07-22 13:58:46 +00:00
|
|
|
procedure GetXYItem(ARangeIndex:TsXYLRange; APointIndex: Integer;
|
2015-01-03 20:03:55 +00:00
|
|
|
out ANumber: Double; out AText: String);
|
2018-07-22 13:58:46 +00:00
|
|
|
procedure SetRange(AIndex: TsXYLRange; const AValue: String);
|
2015-01-03 20:03:55 +00:00
|
|
|
procedure SetWorkbookSource(AValue: TsWorkbookSource);
|
|
|
|
protected
|
|
|
|
FCurItem: TChartDataItem;
|
2018-07-22 13:58:46 +00:00
|
|
|
function BuildRangeStr(AIndex: TsXYLRange; AListSeparator: char = #0): String;
|
|
|
|
function CountValues(AIndex: TsXYLRange): Integer;
|
2015-01-03 20:03:55 +00:00
|
|
|
function GetCount: Integer; override;
|
|
|
|
function GetItem(AIndex: Integer): PChartDataItem; override;
|
|
|
|
procedure Notification(AComponent: TComponent; Operation: TOperation); override;
|
2015-01-05 23:32:49 +00:00
|
|
|
procedure Prepare; overload;
|
2018-07-22 13:58:46 +00:00
|
|
|
procedure Prepare(AIndex: TsXYLRange); overload;
|
2015-01-03 20:03:55 +00:00
|
|
|
procedure SetYCount(AValue: Cardinal); override;
|
|
|
|
public
|
|
|
|
destructor Destroy; override;
|
|
|
|
procedure Reset;
|
|
|
|
property PointsNumber: Cardinal read FPointsNumber;
|
|
|
|
property Workbook: TsWorkbook read GetWorkbook;
|
2015-01-06 17:42:54 +00:00
|
|
|
public
|
|
|
|
// Interface to TsWorkbookSource
|
|
|
|
procedure ListenerNotification(AChangedItems: TsNotificationItems; AData: Pointer = nil);
|
|
|
|
procedure RemoveWorkbookSource;
|
2015-01-03 20:03:55 +00:00
|
|
|
published
|
|
|
|
property WorkbookSource: TsWorkbookSource read FWorkbookSource write SetWorkbookSource;
|
2018-07-22 13:58:46 +00:00
|
|
|
property LabelRange: String index rngLabel read GetRange write SetRange;
|
2015-01-03 20:03:55 +00:00
|
|
|
property XRange: String index rngX read GetRange write SetRange;
|
|
|
|
property YRange: String index rngY read GetRange write SetRange;
|
|
|
|
end;
|
|
|
|
|
2023-11-21 15:18:51 +00:00
|
|
|
{@@ Link between TAChart and the fpspreadsheet chart class }
|
|
|
|
|
|
|
|
{ TsWorkbookChartLink }
|
|
|
|
|
|
|
|
TsWorkbookChartLink = class(TComponent, IsSpreadsheetControl)
|
|
|
|
private
|
|
|
|
FChart: TChart;
|
|
|
|
FWorkbookSource: TsWorkbookSource;
|
|
|
|
FWorkbook: TsWorkbook;
|
|
|
|
FWorkbookChartIndex: Integer;
|
|
|
|
procedure SetChart(AValue: TChart);
|
|
|
|
procedure SetWorkbookChartIndex(AValue: Integer);
|
|
|
|
procedure SetWorkbookSource(AValue: TsWorkbookSource);
|
|
|
|
|
|
|
|
protected
|
|
|
|
procedure Notification(AComponent: TComponent; Operation: TOperation); override;
|
|
|
|
|
|
|
|
procedure ClearChart;
|
|
|
|
function GetWorkbookChart: TsChart;
|
|
|
|
procedure PopulateChart;
|
|
|
|
procedure UpdateChartBrush(AWorkbookFill: TsChartFill; ABrush: TBrush);
|
|
|
|
procedure UpdateChartPen(AWorkbookLine: TsChartLine; APen: TPen);
|
|
|
|
procedure UpdateChartTitle(AWorkbookTitle: TsChartText; AChartTitle: TChartTitle);
|
|
|
|
|
|
|
|
public
|
|
|
|
constructor Create(AOwner: TComponent); override;
|
|
|
|
destructor Destroy; override;
|
|
|
|
|
|
|
|
{ Interfacing with WorkbookSource}
|
|
|
|
procedure ListenerNotification(AChangedItems: TsNotificationItems; AData: Pointer = nil);
|
|
|
|
procedure RemoveWorkbookSource;
|
|
|
|
|
|
|
|
published
|
|
|
|
property Chart: TChart read FChart write SetChart;
|
|
|
|
property WorkbookChartIndex: Integer read FWorkbookChartIndex write SetWorkbookChartIndex;
|
|
|
|
property WorkbookSource: TsWorkbookSource read FWorkbookSource write SetWorkbookSource;
|
|
|
|
|
|
|
|
end;
|
|
|
|
|
2015-01-03 20:03:55 +00:00
|
|
|
|
2010-05-01 18:10:38 +00:00
|
|
|
implementation
|
|
|
|
|
2015-01-03 20:03:55 +00:00
|
|
|
uses
|
|
|
|
Math;
|
|
|
|
|
|
|
|
{------------------------------------------------------------------------------}
|
|
|
|
{ TsWorkbookChartSource }
|
|
|
|
{------------------------------------------------------------------------------}
|
|
|
|
|
2015-01-05 23:32:49 +00:00
|
|
|
{@@ ----------------------------------------------------------------------------
|
|
|
|
Destructor of the WorkbookChartSource.
|
|
|
|
Removes itself from the WorkbookSource's listener list.
|
|
|
|
-------------------------------------------------------------------------------}
|
2015-01-03 20:03:55 +00:00
|
|
|
destructor TsWorkbookChartSource.Destroy;
|
|
|
|
begin
|
|
|
|
if FWorkbookSource <> nil then FWorkbookSource.RemoveListener(self);
|
|
|
|
inherited Destroy;
|
|
|
|
end;
|
|
|
|
|
2015-01-05 23:32:49 +00:00
|
|
|
{@@ ----------------------------------------------------------------------------
|
|
|
|
Constructs the range string from the stored internal information. Is needed
|
|
|
|
to have the worksheet name in the range string in order to make the range
|
|
|
|
string unique.
|
|
|
|
-------------------------------------------------------------------------------}
|
2018-07-22 13:58:46 +00:00
|
|
|
function TsWorkbookChartSource.BuildRangeStr(AIndex: TsXYLRange;
|
2015-01-05 23:32:49 +00:00
|
|
|
AListSeparator: Char = #0): String;
|
|
|
|
var
|
|
|
|
L: TStrings;
|
|
|
|
range: TsCellRange;
|
|
|
|
begin
|
2016-03-18 19:50:40 +00:00
|
|
|
if (Workbook = nil) or (FWorksheets[AIndex] = nil) or (Length(FRanges) = 0) then
|
2015-01-05 23:32:49 +00:00
|
|
|
exit('');
|
|
|
|
|
|
|
|
L := TStringList.Create;
|
|
|
|
try
|
|
|
|
if AListSeparator = #0 then
|
2016-03-18 19:50:40 +00:00
|
|
|
L.Delimiter := Workbook.FormatSettings.ListSeparator
|
2015-01-05 23:32:49 +00:00
|
|
|
else
|
|
|
|
L.Delimiter := AListSeparator;
|
|
|
|
L.StrictDelimiter := true;
|
|
|
|
for range in FRanges[AIndex] do
|
|
|
|
L.Add(GetCellRangeString(range, rfAllRel, true));
|
|
|
|
Result := FWorksheets[AIndex].Name + SHEETSEPARATOR + L.DelimitedText;
|
|
|
|
finally
|
|
|
|
L.Free;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
2015-01-03 20:03:55 +00:00
|
|
|
{@@ ----------------------------------------------------------------------------
|
|
|
|
Counts the number of x or y values contained in the x/y ranges
|
2015-01-05 23:32:49 +00:00
|
|
|
|
|
|
|
@param AIndex Identifies whether values in the x or y ranges are counted.
|
2015-01-03 20:03:55 +00:00
|
|
|
-------------------------------------------------------------------------------}
|
2018-07-22 13:58:46 +00:00
|
|
|
function TsWorkbookChartSource.CountValues(AIndex: TsXYLRange): Integer;
|
2015-01-03 20:03:55 +00:00
|
|
|
var
|
2015-01-05 23:32:49 +00:00
|
|
|
range: TsCellRange;
|
2015-01-03 20:03:55 +00:00
|
|
|
begin
|
|
|
|
Result := 0;
|
2015-01-05 23:32:49 +00:00
|
|
|
for range in FRanges[AIndex] do
|
|
|
|
begin
|
|
|
|
if range.Col1 = range.Col2 then
|
|
|
|
inc(Result, range.Row2 - range.Row1 + 1)
|
|
|
|
else
|
|
|
|
if range.Row1 = range.Row2 then
|
|
|
|
inc(Result, range.Col2 - range.Col1 + 1)
|
|
|
|
else
|
|
|
|
raise Exception.Create('x/y ranges can only be 1 column wide or 1 row high.');
|
2015-01-03 20:03:55 +00:00
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
|
|
|
{@@ ----------------------------------------------------------------------------
|
|
|
|
Inherited ChartSource method telling the series how many data points are
|
|
|
|
available
|
|
|
|
-------------------------------------------------------------------------------}
|
|
|
|
function TsWorkbookChartSource.GetCount: Integer;
|
|
|
|
begin
|
|
|
|
Result := FPointsNumber;
|
|
|
|
end;
|
|
|
|
|
|
|
|
{@@ ----------------------------------------------------------------------------
|
|
|
|
Main ChartSource method called from the series requiring data for plotting.
|
|
|
|
Retrieves the data from the workbook.
|
2015-01-05 23:32:49 +00:00
|
|
|
|
|
|
|
@param AIndex Index of the data point in the series.
|
|
|
|
@return Pointer to a TChartDataItem record containing the x and y coordinates,
|
|
|
|
the data point mark text, and the individual data point color.
|
2015-01-03 20:03:55 +00:00
|
|
|
-------------------------------------------------------------------------------}
|
|
|
|
function TsWorkbookChartSource.GetItem(AIndex: Integer): PChartDataItem;
|
|
|
|
var
|
2018-07-22 13:58:46 +00:00
|
|
|
dummyNumber: Double;
|
|
|
|
dummyString: String;
|
|
|
|
tmpLabel: String;
|
2015-01-03 20:03:55 +00:00
|
|
|
begin
|
2018-07-22 13:58:46 +00:00
|
|
|
GetXYItem(rngX, AIndex, FCurItem.X, tmpLabel);
|
|
|
|
GetXYItem(rngY, AIndex, FCurItem.Y, dummyString);
|
|
|
|
GetXYItem(rngLabel, AIndex, dummyNumber, FCurItem.Text);
|
|
|
|
if FCurItem.Text = '' then FCurItem.Text := tmpLabel;
|
2015-01-08 11:23:07 +00:00
|
|
|
FCurItem.Color := clDefault;
|
2015-01-03 20:03:55 +00:00
|
|
|
Result := @FCurItem;
|
|
|
|
end;
|
|
|
|
|
|
|
|
{@@ ----------------------------------------------------------------------------
|
2018-07-22 13:58:46 +00:00
|
|
|
Getter method for the cell range used for x or y coordinates or x labels
|
2015-01-03 20:03:55 +00:00
|
|
|
|
2018-07-22 13:58:46 +00:00
|
|
|
@param AIndex Determines whether the methods deals with x, y values or
|
|
|
|
vakze labels.
|
2015-01-03 20:03:55 +00:00
|
|
|
@return An Excel string containing workbookname and cell block(s) in A1
|
|
|
|
notation. Multiple blocks are separated by the ListSeparator defined
|
|
|
|
by the workbook's FormatSettings.
|
|
|
|
-------------------------------------------------------------------------------}
|
2018-07-22 13:58:46 +00:00
|
|
|
function TsWorkbookChartsource.GetRange(AIndex: TsXYLRange): String;
|
2015-01-03 20:03:55 +00:00
|
|
|
begin
|
2015-01-05 23:32:49 +00:00
|
|
|
Result := FRangeStr[AIndex];
|
2015-01-03 20:03:55 +00:00
|
|
|
end;
|
|
|
|
|
|
|
|
{@@ ----------------------------------------------------------------------------
|
|
|
|
Getter method for the linked workbook
|
|
|
|
-------------------------------------------------------------------------------}
|
|
|
|
function TsWorkbookChartSource.GetWorkbook: TsWorkbook;
|
|
|
|
begin
|
|
|
|
if FWorkbookSource <> nil then
|
|
|
|
Result := WorkbookSource.Workbook
|
|
|
|
else
|
|
|
|
Result := nil;
|
2016-03-18 19:50:40 +00:00
|
|
|
// FWorkbook := Result;
|
2015-01-03 20:03:55 +00:00
|
|
|
end;
|
|
|
|
|
2015-01-05 23:32:49 +00:00
|
|
|
{@@ ----------------------------------------------------------------------------
|
|
|
|
Helper method the prepare the information required for the series data point.
|
|
|
|
|
2018-07-22 13:58:46 +00:00
|
|
|
@param ARangeIndex Identifies whether the method retrieves the x or y
|
|
|
|
coordinate, or the label text
|
2015-01-05 23:32:49 +00:00
|
|
|
@param APointIndex Index of the data point for which the data are required
|
|
|
|
@param ANumber (output) x or y coordinate of the data point
|
|
|
|
@param AText Data point marks label text
|
|
|
|
-------------------------------------------------------------------------------}
|
2018-07-22 13:58:46 +00:00
|
|
|
procedure TsWorkbookChartSource.GetXYItem(ARangeIndex:TsXYLRange;
|
|
|
|
APointIndex: Integer; out ANumber: Double; out AText: String);
|
2015-01-03 20:03:55 +00:00
|
|
|
var
|
|
|
|
range: TsCellRange;
|
2015-01-09 22:25:20 +00:00
|
|
|
idx: Integer;
|
2015-01-03 20:03:55 +00:00
|
|
|
len: Integer;
|
|
|
|
row, col: Cardinal;
|
|
|
|
cell: PCell;
|
|
|
|
begin
|
2018-07-22 13:58:46 +00:00
|
|
|
ANumber := NaN;
|
|
|
|
AText := '';
|
|
|
|
if FRanges[ARangeIndex] = nil then
|
|
|
|
exit;
|
|
|
|
if FWorksheets[ARangeIndex] = nil then
|
|
|
|
exit;
|
|
|
|
|
2015-01-03 20:03:55 +00:00
|
|
|
cell := nil;
|
2015-01-05 23:32:49 +00:00
|
|
|
idx := 0;
|
2015-01-03 20:03:55 +00:00
|
|
|
|
2018-07-22 13:58:46 +00:00
|
|
|
for range in FRanges[ARangeIndex] do
|
2015-01-05 23:32:49 +00:00
|
|
|
begin
|
|
|
|
if (range.Col1 = range.Col2) then // vertical range
|
|
|
|
begin
|
|
|
|
len := range.Row2 - range.Row1 + 1;
|
|
|
|
if (APointIndex >= idx) and (APointIndex < idx + len) then
|
|
|
|
begin
|
2015-01-09 22:25:20 +00:00
|
|
|
row := longint(range.Row1) + APointIndex - idx;
|
2015-01-05 23:32:49 +00:00
|
|
|
col := range.Col1;
|
|
|
|
break;
|
2015-01-03 20:03:55 +00:00
|
|
|
end;
|
2015-01-05 23:32:49 +00:00
|
|
|
inc(idx, len);
|
|
|
|
end else // horizontal range
|
|
|
|
if (range.Row1 = range.Row2) then
|
|
|
|
begin
|
2015-01-09 22:25:20 +00:00
|
|
|
len := longint(range.Col2) - range.Col1 + 1;
|
2015-01-05 23:32:49 +00:00
|
|
|
if (APointIndex >= idx) and (APointIndex < idx + len) then
|
|
|
|
begin
|
|
|
|
row := range.Row1;
|
2015-01-09 22:25:20 +00:00
|
|
|
col := longint(range.Col1) + APointIndex - idx;
|
2015-01-05 23:32:49 +00:00
|
|
|
break;
|
|
|
|
end;
|
|
|
|
end else
|
2018-07-22 13:58:46 +00:00
|
|
|
raise Exception.Create('Ranges can only be 1 column wide or 1 row high');
|
2015-01-03 20:03:55 +00:00
|
|
|
end;
|
2015-01-05 23:32:49 +00:00
|
|
|
|
2018-07-22 13:58:46 +00:00
|
|
|
cell := FWorksheets[ARangeIndex].FindCell(row, col);
|
2015-01-05 23:32:49 +00:00
|
|
|
|
2018-07-22 13:58:46 +00:00
|
|
|
if cell <> nil then
|
|
|
|
case cell^.ContentType of
|
|
|
|
cctUTF8String:
|
|
|
|
begin
|
|
|
|
ANumber := APointIndex;
|
|
|
|
AText := FWorksheets[ARangeIndex].ReadAsText(cell);
|
|
|
|
end;
|
|
|
|
else
|
|
|
|
ANumber := FWorksheets[ARangeIndex].ReadAsNumber(cell);
|
|
|
|
AText := '';
|
|
|
|
end;
|
2015-01-03 20:03:55 +00:00
|
|
|
end;
|
|
|
|
|
|
|
|
{@@ ----------------------------------------------------------------------------
|
|
|
|
Notification message received from the WorkbookSource telling which
|
|
|
|
spreadsheet item has changed.
|
|
|
|
Responds to workbook changes by reading the worksheet names into the tabs,
|
|
|
|
and to worksheet changes by selecting the tab corresponding to the selected
|
|
|
|
worksheet.
|
|
|
|
|
2015-01-05 23:32:49 +00:00
|
|
|
@param AChangedItems Set with elements identifying whether workbook,
|
|
|
|
worksheet, cell content or cell formatting has changed
|
2015-01-03 20:03:55 +00:00
|
|
|
@param AData Additional data, not used here
|
2015-01-05 23:32:49 +00:00
|
|
|
|
|
|
|
@see TsNotificationItem
|
2015-01-03 20:03:55 +00:00
|
|
|
-------------------------------------------------------------------------------}
|
|
|
|
procedure TsWorkbookChartSource.ListenerNotification(
|
|
|
|
AChangedItems: TsNotificationItems; AData: Pointer = nil);
|
|
|
|
var
|
|
|
|
ir: Integer;
|
|
|
|
cell: PCell;
|
|
|
|
ResetDone: Boolean;
|
2018-07-22 13:58:46 +00:00
|
|
|
rng: TsXYLRange;
|
2015-01-03 20:03:55 +00:00
|
|
|
begin
|
|
|
|
Unused(AData);
|
|
|
|
|
2015-01-05 23:32:49 +00:00
|
|
|
// Workbook has been successfully loaded, all sheets are ready
|
|
|
|
if (lniWorkbook in AChangedItems) then
|
|
|
|
Prepare;
|
|
|
|
|
|
|
|
// Used worksheet has been renamed?
|
|
|
|
if (lniWorksheetRename in AChangedItems) then
|
2018-07-22 13:58:46 +00:00
|
|
|
for rng in TsXYLRange do
|
|
|
|
if TsWorksheet(AData) = FWorksheets[rng] then begin
|
|
|
|
FRangeStr[rng] := BuildRangeStr(rng);
|
|
|
|
Prepare(rng);
|
2015-01-05 23:32:49 +00:00
|
|
|
end;
|
|
|
|
|
|
|
|
// Used worksheet will be deleted?
|
|
|
|
if (lniWorksheetRemoving in AChangedItems) then
|
2018-07-22 13:58:46 +00:00
|
|
|
for rng in TsXYLRange do
|
|
|
|
if TsWorksheet(AData) = FWorksheets[rng] then begin
|
|
|
|
FWorksheets[rng] := nil;
|
|
|
|
FRangeStr[rng] := BuildRangeStr(rng);
|
|
|
|
Prepare(rng);
|
2015-01-05 23:32:49 +00:00
|
|
|
end;
|
2015-01-03 20:03:55 +00:00
|
|
|
|
|
|
|
// Cell changes: Enforce recalculation of axes if modified cell is within the
|
|
|
|
// x or y range(s).
|
|
|
|
if (lniCell in AChangedItems) and (Workbook <> nil) then
|
|
|
|
begin
|
|
|
|
cell := PCell(AData);
|
|
|
|
if (cell <> nil) then begin
|
|
|
|
ResetDone := false;
|
2018-07-22 13:58:46 +00:00
|
|
|
for rng in TsXYLRange do
|
|
|
|
for ir:=0 to High(FRanges[rng]) do
|
2015-01-03 20:03:55 +00:00
|
|
|
begin
|
2018-07-22 13:58:46 +00:00
|
|
|
if FWorksheets[rng].CellInRange(cell^.Row, cell^.Col, FRanges[rng, ir]) then
|
2015-01-03 20:03:55 +00:00
|
|
|
begin
|
|
|
|
Reset;
|
|
|
|
ResetDone := true;
|
|
|
|
break;
|
|
|
|
end;
|
|
|
|
if ResetDone then break;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
|
|
|
{@@ ----------------------------------------------------------------------------
|
|
|
|
Standard component notification: The ChartSource is notified that the
|
|
|
|
WorkbookSource is being removed.
|
|
|
|
-------------------------------------------------------------------------------}
|
|
|
|
procedure TsWorkbookChartSource.Notification(AComponent: TComponent;
|
|
|
|
Operation: TOperation);
|
|
|
|
begin
|
|
|
|
inherited Notification(AComponent, Operation);
|
|
|
|
if (Operation = opRemove) and (AComponent = FWorkbookSource) then
|
|
|
|
SetWorkbookSource(nil);
|
|
|
|
end;
|
|
|
|
|
2015-01-05 23:32:49 +00:00
|
|
|
{@@ ----------------------------------------------------------------------------
|
|
|
|
Parses the x and y cell range strings and extracts internal information
|
|
|
|
(worksheet used, cell range coordinates)
|
|
|
|
-------------------------------------------------------------------------------}
|
|
|
|
procedure TsWorkbookChartSource.Prepare;
|
|
|
|
begin
|
2018-07-22 13:58:46 +00:00
|
|
|
Prepare(rngLabel);
|
2015-01-05 23:32:49 +00:00
|
|
|
Prepare(rngX);
|
|
|
|
Prepare(rngY);
|
|
|
|
end;
|
|
|
|
|
|
|
|
{@@ ----------------------------------------------------------------------------
|
|
|
|
Parses the range string of the data specified by AIndex and extracts internal
|
|
|
|
information (worksheet used, cell range coordinates)
|
|
|
|
|
2018-07-22 13:58:46 +00:00
|
|
|
@param AIndex Identifies whether x or y or label cell ranges are analyzed
|
2015-01-05 23:32:49 +00:00
|
|
|
-------------------------------------------------------------------------------}
|
2018-07-22 13:58:46 +00:00
|
|
|
procedure TsWorkbookChartSource.Prepare(AIndex: TsXYLRange);
|
|
|
|
{
|
2015-01-05 23:32:49 +00:00
|
|
|
const
|
2018-07-22 13:58:46 +00:00
|
|
|
XY: array[TsXYRange] of string = ('x', 'y', '');
|
|
|
|
}
|
2015-01-05 23:32:49 +00:00
|
|
|
var
|
|
|
|
range: TsCellRange;
|
|
|
|
begin
|
2016-03-18 19:50:40 +00:00
|
|
|
if (Workbook = nil) or (FRangeStr[AIndex] = '') //or (FWorksheets[AIndex] = nil)
|
2015-01-08 17:36:41 +00:00
|
|
|
then begin
|
2015-01-05 23:32:49 +00:00
|
|
|
FWorksheets[AIndex] := nil;
|
|
|
|
SetLength(FRanges[AIndex], 0);
|
|
|
|
FPointsNumber := 0;
|
|
|
|
Reset;
|
|
|
|
exit;
|
|
|
|
end;
|
|
|
|
|
2016-03-18 19:50:40 +00:00
|
|
|
if Workbook.TryStrToCellRanges(FRangeStr[AIndex], FWorksheets[AIndex], FRanges[AIndex])
|
2015-01-05 23:32:49 +00:00
|
|
|
then begin
|
|
|
|
for range in FRanges[AIndex] do
|
|
|
|
if (range.Col1 <> range.Col2) and (range.Row1 <> range.Row2) then
|
|
|
|
raise Exception.Create('x/y ranges can only be 1 column wide or 1 row high');
|
|
|
|
FPointsNumber := Max(CountValues(rngX), CountValues(rngY));
|
|
|
|
// If x and y ranges are of different size empty data points will be plotted.
|
|
|
|
Reset;
|
|
|
|
// Make sure to include worksheet name in RangeString.
|
|
|
|
FRangeStr[AIndex] := BuildRangeStr(AIndex);
|
|
|
|
end else
|
2016-03-18 19:50:40 +00:00
|
|
|
if (Workbook.GetWorksheetCount > 0) then begin
|
2015-01-05 23:32:49 +00:00
|
|
|
if FWorksheets[AIndex] = nil then
|
2018-07-22 13:58:46 +00:00
|
|
|
exit;
|
|
|
|
{
|
2015-01-05 23:32:49 +00:00
|
|
|
raise Exception.CreateFmt('Worksheet of %s cell range "%s" does not exist.',
|
|
|
|
[XY[AIndex], FRangeStr[AIndex]])
|
|
|
|
else
|
|
|
|
raise Exception.CreateFmt('No valid %s cell range in "%s".',
|
|
|
|
[XY[AIndex], FRangeStr[AIndex]]);
|
2018-07-22 13:58:46 +00:00
|
|
|
}
|
2015-01-05 23:32:49 +00:00
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
2015-01-06 17:42:54 +00:00
|
|
|
{@@ ----------------------------------------------------------------------------
|
|
|
|
Removes the link of the ChartSource to the WorkbookSource.
|
|
|
|
Required before destruction.
|
|
|
|
-------------------------------------------------------------------------------}
|
|
|
|
procedure TsWorkbookChartSource.RemoveWorkbookSource;
|
|
|
|
begin
|
|
|
|
SetWorkbookSource(nil);
|
|
|
|
end;
|
|
|
|
|
2015-01-03 20:03:55 +00:00
|
|
|
{@@ ----------------------------------------------------------------------------
|
|
|
|
Resets internal buffers and notfies chart elements of the changes,
|
|
|
|
in particular, enforces recalculation of axis limits
|
|
|
|
-------------------------------------------------------------------------------}
|
|
|
|
procedure TsWorkbookChartSource.Reset;
|
|
|
|
begin
|
|
|
|
InvalidateCaches;
|
|
|
|
Notify;
|
|
|
|
end;
|
|
|
|
|
|
|
|
{@@ ----------------------------------------------------------------------------
|
|
|
|
Setter method for the cell range used for x or y data (or labels) in the chart
|
|
|
|
If it does not contain the worksheet name the currently active worksheet of
|
|
|
|
the WorkbookSource is assumed.
|
|
|
|
|
2018-07-22 13:58:46 +00:00
|
|
|
@param AIndex Distinguishes whether the method deals with x, y or
|
|
|
|
label ranges.
|
2015-01-03 20:03:55 +00:00
|
|
|
@param AValue String in Excel syntax containing the cell range to be
|
|
|
|
used for x or y (depending on AIndex). Can contain multiple
|
|
|
|
cell blocks which must be separator by the ListSeparator
|
|
|
|
character defined in the Workbook's FormatSettings.
|
|
|
|
-------------------------------------------------------------------------------}
|
2018-07-22 13:58:46 +00:00
|
|
|
procedure TsWorkbookChartSource.SetRange(AIndex: TsXYLRange;
|
|
|
|
const AValue: String);
|
2015-01-03 20:03:55 +00:00
|
|
|
begin
|
2015-01-05 23:32:49 +00:00
|
|
|
FRangeStr[AIndex] := AValue;
|
|
|
|
Prepare;
|
2015-01-03 20:03:55 +00:00
|
|
|
end;
|
|
|
|
|
|
|
|
{@@ ----------------------------------------------------------------------------
|
|
|
|
Setter method for the WorkbookSource
|
|
|
|
-------------------------------------------------------------------------------}
|
|
|
|
procedure TsWorkbookChartSource.SetWorkbookSource(AValue: TsWorkbookSource);
|
|
|
|
begin
|
|
|
|
if AValue = FWorkbookSource then
|
|
|
|
exit;
|
|
|
|
if FWorkbookSource <> nil then
|
|
|
|
FWorkbookSource.RemoveListener(self);
|
|
|
|
FWorkbookSource := AValue;
|
|
|
|
if FWorkbookSource <> nil then
|
|
|
|
FWorkbookSource.AddListener(self);
|
2016-03-18 19:50:40 +00:00
|
|
|
// FWorkbook := GetWorkbook;
|
2015-01-03 20:03:55 +00:00
|
|
|
ListenerNotification([lniWorkbook, lniWorksheet]);
|
2018-07-22 08:42:38 +00:00
|
|
|
Prepare;
|
2015-01-03 20:03:55 +00:00
|
|
|
end;
|
|
|
|
|
|
|
|
{@@ ----------------------------------------------------------------------------
|
|
|
|
Inherited ChartSource method telling the series how many y values are used.
|
2015-01-14 15:34:20 +00:00
|
|
|
Currently we support only single valued data (YCount = 1, by default).
|
2015-01-03 20:03:55 +00:00
|
|
|
-------------------------------------------------------------------------------}
|
|
|
|
procedure TsWorkbookChartSource.SetYCount(AValue: Cardinal);
|
|
|
|
begin
|
|
|
|
FYCount := AValue;
|
|
|
|
end;
|
|
|
|
|
|
|
|
|
2023-11-21 15:18:51 +00:00
|
|
|
{------------------------------------------------------------------------------}
|
|
|
|
{ TsWorkbookChartLink }
|
|
|
|
{------------------------------------------------------------------------------}
|
|
|
|
|
|
|
|
constructor TsWorkbookChartLink.Create(AOwner: TComponent);
|
|
|
|
begin
|
|
|
|
inherited;
|
|
|
|
FWorkbookChartIndex := -1;
|
|
|
|
end;
|
|
|
|
|
|
|
|
{@@ ----------------------------------------------------------------------------
|
|
|
|
Destructor of the WorkbookChartLink.
|
|
|
|
Removes itself from the WorkbookSource's listener list.
|
|
|
|
-------------------------------------------------------------------------------}
|
|
|
|
destructor TsWorkbookChartLink.Destroy;
|
|
|
|
begin
|
|
|
|
if FWorkbookSource <> nil then FWorkbookSource.RemoveListener(self);
|
|
|
|
inherited;
|
|
|
|
end;
|
|
|
|
|
|
|
|
procedure TsWorkbookChartLink.ClearChart;
|
|
|
|
begin
|
|
|
|
if FChart = nil then
|
|
|
|
exit;
|
|
|
|
// Clear the title
|
|
|
|
FChart.Title.Text.Clear;
|
|
|
|
// Clear the footer
|
|
|
|
FChart.Foot.Text.Clear;
|
|
|
|
// Clear the series
|
|
|
|
FChart.ClearSeries;
|
|
|
|
end;
|
|
|
|
|
|
|
|
function TsWorkbookChartLink.GetWorkbookChart: TsChart;
|
|
|
|
begin
|
|
|
|
if (FWorkbook <> nil) and (FWorkbookChartIndex > -1) then
|
|
|
|
Result := FWorkbook.GetChartByIndex(FWorkbookChartIndex)
|
|
|
|
else
|
|
|
|
Result := nil;
|
|
|
|
end;
|
|
|
|
|
|
|
|
procedure TsWorkbookChartLink.ListenerNotification(AChangedItems: TsNotificationItems;
|
|
|
|
AData: Pointer = nil);
|
|
|
|
begin
|
|
|
|
// to be completed
|
|
|
|
end;
|
|
|
|
|
|
|
|
procedure TsWorkbookChartLink.Notification(AComponent: TComponent; Operation: TOperation);
|
|
|
|
begin
|
|
|
|
inherited Notification(AComponent, Operation);
|
|
|
|
if (Operation = opRemove) then
|
|
|
|
begin
|
|
|
|
if (AComponent = FWorkbookSource) then
|
|
|
|
SetWorkbookSource(nil)
|
|
|
|
else
|
|
|
|
if (AComponent = FChart) then
|
|
|
|
SetChart(nil);
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
|
|
|
procedure TsWorkbookChartLink.RemoveWorkbookSource;
|
|
|
|
begin
|
|
|
|
SetWorkbookSource(nil);
|
|
|
|
end;
|
|
|
|
|
|
|
|
procedure TsWorkbookChartLink.PopulateChart;
|
|
|
|
var
|
|
|
|
ch: TsChart;
|
|
|
|
begin
|
|
|
|
if (FChart = nil) then
|
|
|
|
exit;
|
|
|
|
if (FWorkbookSource = nil) or (FWorkbookChartIndex < 0) then
|
|
|
|
begin
|
|
|
|
ClearChart;
|
|
|
|
exit;
|
|
|
|
end;
|
|
|
|
|
|
|
|
ch := GetWorkbookChart;
|
|
|
|
UpdateChartTitle(ch.Title, FChart.Title);
|
|
|
|
UpdateChartTitle(ch.Subtitle, FChart.Foot);
|
|
|
|
|
|
|
|
// ...
|
|
|
|
end;
|
|
|
|
|
|
|
|
procedure TsWorkbookChartLink.SetChart(AValue: TChart);
|
|
|
|
begin
|
|
|
|
if FChart = AValue then
|
|
|
|
exit;
|
|
|
|
FChart := AValue;
|
|
|
|
PopulateChart;
|
|
|
|
end;
|
|
|
|
|
|
|
|
procedure TSWorkbookChartLink.SetWorkbookChartIndex(AValue: Integer);
|
|
|
|
begin
|
|
|
|
if AValue = FWorkbookChartIndex then
|
|
|
|
exit;
|
|
|
|
FWorkbookChartIndex := AValue;
|
|
|
|
PopulateChart;
|
|
|
|
end;
|
|
|
|
|
|
|
|
procedure TsWorkbookChartLink.SetWorkbookSource(AValue: TsWorkbookSource);
|
|
|
|
begin
|
|
|
|
if AValue = FWorkbookSource then
|
|
|
|
exit;
|
|
|
|
if FWorkbookSource <> nil then
|
|
|
|
FWorkbookSource.RemoveListener(self);
|
|
|
|
FWorkbookSource := AValue;
|
|
|
|
if FWorkbookSource <> nil then
|
|
|
|
begin
|
|
|
|
FWorkbookSource.AddListener(self);
|
|
|
|
FWorkbook := FWorkbookSource.Workbook;
|
|
|
|
end else
|
|
|
|
FWorkbook := nil;
|
|
|
|
ListenerNotification([lniWorkbook, lniWorksheet]);
|
|
|
|
PopulateChart;
|
|
|
|
end;
|
|
|
|
|
|
|
|
procedure TsWorkbookChartLink.UpdateChartBrush(AWorkbookFill: TsChartFill;
|
|
|
|
ABrush: TBrush);
|
|
|
|
begin
|
|
|
|
if (AWorkbookFill <> nil) and (ABrush <> nil) then
|
|
|
|
begin
|
|
|
|
ABrush.Color := Convert_sColor_to_Color(AWorkbookFill.Color);
|
|
|
|
if AWorkbookFill.Style = cfsNoFill then
|
|
|
|
ABrush.Style := bsClear
|
|
|
|
else
|
|
|
|
ABrush.Style := bsSolid;
|
|
|
|
// NOTE: TAChart will ignore gradient.
|
|
|
|
// To be completed: hatched filles.
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
|
|
|
procedure TsWorkbookChartLink.UpdateChartPen(AWorkbookLine: TsChartLine;
|
|
|
|
APen: TPen);
|
|
|
|
begin
|
|
|
|
if (AWorkbookLine <> nil) and (APen <> nil) then
|
|
|
|
begin
|
|
|
|
APen.Color := Convert_sColor_to_Color(AWorkbookLine.Color);
|
|
|
|
APen.Width := round(mmToIn(AWorkbookLine.Width) * GetParentForm(FChart).PixelsPerInch);
|
|
|
|
case AWorkbookLine.Style of
|
|
|
|
clsNoLine:
|
|
|
|
APen.Style := psClear;
|
|
|
|
clsSolid:
|
|
|
|
APen.Style := psSolid;
|
|
|
|
else // to be fixed
|
|
|
|
APen.Style := psSolid;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
|
|
|
{@@ Updates title and footer of the linked TAChart.
|
|
|
|
NOTE: the workbook chart's subtitle is converted to TAChart's footer! }
|
|
|
|
procedure TsWorkbookChartLink.UpdateChartTitle(AWorkbookTitle: TsChartText;
|
|
|
|
AChartTitle: TChartTitle);
|
|
|
|
begin
|
|
|
|
if (AWorkbookTitle <> nil) and (AChartTitle <> nil) then
|
|
|
|
begin
|
|
|
|
AChartTitle.Text.Text := AWorkbookTitle.Caption;
|
|
|
|
AChartTitle.Visible := AWorkbookTitle.Visible;
|
|
|
|
Convert_sFont_to_Font(AWorkbookTitle.Font, AChartTitle.Font);
|
|
|
|
UpdateChartPen(AWorkbookTitle.Border, AChartTitle.Frame);
|
|
|
|
UpdateChartBrush(AWorkbookTitle.Background, AChartTitle.Brush);
|
|
|
|
AChartTitle.Font.Orientation := round(AWorkbookTitle.RotationAngle * 10);
|
|
|
|
AChartTitle.Frame.Visible := (AChartTitle.Frame.Style <> psClear);
|
|
|
|
AChartTitle.Alignment := taCenter;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
2010-05-01 18:10:38 +00:00
|
|
|
end.
|