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
|
2023-11-22 22:08:47 +00:00
|
|
|
// RTL/FCL
|
2023-11-23 18:49:41 +00:00
|
|
|
Classes, Contnrs, SysUtils, Types,
|
2023-11-22 22:08:47 +00:00
|
|
|
// LCL
|
2023-11-23 18:49:41 +00:00
|
|
|
LCLVersion, Forms, Controls, Graphics, GraphUtil, Dialogs,
|
2023-11-22 22:08:47 +00:00
|
|
|
// TAChart
|
2023-11-23 00:08:33 +00:00
|
|
|
TATypes, TATextElements, TAChartUtils, TALegend, TACustomSource,
|
|
|
|
TACustomSeries, TASeries, TARadialSeries, TAFitUtils, TAFuncSeries,
|
|
|
|
TAChartAxisUtils, TAChartAxis, TAGraph,
|
2018-07-09 17:27:22 +00:00
|
|
|
// FPSpreadsheet
|
2023-11-22 22:08:47 +00:00
|
|
|
fpsTypes, fpSpreadsheet, fpsUtils, fpsChart,
|
|
|
|
// FPSpreadsheet Visual
|
|
|
|
fpSpreadsheetCtrls, fpSpreadsheetGrid, fpsVisualUtils;
|
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 }
|
|
|
|
|
2023-11-22 14:23:55 +00:00
|
|
|
TsXYLRange = (rngX, rngY, rngLabel, rngColor);
|
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;
|
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;
|
2023-11-22 14:23:55 +00:00
|
|
|
FTitleCol, FTitleRow: Cardinal;
|
|
|
|
FTitleSheetName: String;
|
2018-07-22 13:58:46 +00:00
|
|
|
function GetRange(AIndex: TsXYLRange): String;
|
2023-11-22 14:23:55 +00:00
|
|
|
function GetTitle: 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);
|
2023-11-22 14:23:55 +00:00
|
|
|
procedure SetRangeFromChart(AIndex: TsXYLRange; const ARange: TsChartRange);
|
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;
|
2023-11-22 14:23:55 +00:00
|
|
|
procedure SetColorRange(ARange: TsChartRange);
|
|
|
|
procedure SetLabelRange(ARange: TsChartRange);
|
|
|
|
procedure SetXRange(ARange: TsChartRange);
|
|
|
|
procedure SetYRange(ARange: TsChartRange);
|
|
|
|
procedure SetTitleAddr(Addr: TsChartCellAddr);
|
2015-01-03 20:03:55 +00:00
|
|
|
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;
|
2023-11-22 14:23:55 +00:00
|
|
|
property ColorRange: String index rngColor read GetRange write SetRange;
|
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;
|
2023-11-22 14:23:55 +00:00
|
|
|
property Title: String read GetTitle;
|
2015-01-03 20:03:55 +00:00
|
|
|
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;
|
2023-11-23 18:49:41 +00:00
|
|
|
FBrushBitmaps: TFPObjectList;
|
2023-11-21 15:18:51 +00:00
|
|
|
procedure SetChart(AValue: TChart);
|
|
|
|
procedure SetWorkbookChartIndex(AValue: Integer);
|
|
|
|
procedure SetWorkbookSource(AValue: TsWorkbookSource);
|
|
|
|
|
2023-11-23 00:08:33 +00:00
|
|
|
//procedure FitSeriesFitEquationText(ASeries: TFitSeries; AEquationText: IFitEquationText);
|
|
|
|
|
2023-11-21 15:18:51 +00:00
|
|
|
protected
|
|
|
|
procedure Notification(AComponent: TComponent; Operation: TOperation); override;
|
|
|
|
|
2023-11-22 14:23:55 +00:00
|
|
|
procedure AddSeries(ASeries: TsChartSeries);
|
|
|
|
procedure FixAreaSeries(AWorkbookChart: TsChart);
|
2023-11-21 15:18:51 +00:00
|
|
|
procedure ClearChart;
|
2023-11-22 22:08:47 +00:00
|
|
|
procedure ConstructSeriesMarks(AWorkbookSeries: TsChartSeries; AChartSeries: TChartSeries);
|
2023-11-21 15:18:51 +00:00
|
|
|
function GetWorkbookChart: TsChart;
|
2023-11-21 21:59:26 +00:00
|
|
|
procedure UpdateChartAxis(AWorkbookAxis: TsChartAxis);
|
2023-11-22 14:23:55 +00:00
|
|
|
procedure UpdateChartAxisLabels(AWorkbookChart: TsChart);
|
2023-11-21 21:59:26 +00:00
|
|
|
procedure UpdateChartBackground(AWorkbookChart: TsChart);
|
2023-11-22 14:23:55 +00:00
|
|
|
procedure UpdateBarSeries(AWorkbookChart: TsChart);
|
2023-11-23 18:49:41 +00:00
|
|
|
procedure UpdateChartBrush(AWorkbookChart: TsChart; AWorkbookFill: TsChartFill; ABrush: TBrush);
|
2023-11-21 21:59:26 +00:00
|
|
|
procedure UpdateChartLegend(AWorkbookLegend: TsChartLegend; ALegend: TChartLegend);
|
2023-11-21 15:18:51 +00:00
|
|
|
procedure UpdateChartPen(AWorkbookLine: TsChartLine; APen: TPen);
|
2023-11-22 22:08:47 +00:00
|
|
|
procedure UpdateChartSeriesMarks(AWorkbookSeries: TsChartSeries; AChartSeries: TChartSeries);
|
2023-11-21 15:18:51 +00:00
|
|
|
procedure UpdateChartTitle(AWorkbookTitle: TsChartText; AChartTitle: TChartTitle);
|
|
|
|
|
2023-11-23 18:49:41 +00:00
|
|
|
procedure UpdateAreaSeries(AWorkbookSeries: TsAreaSeries; AChartSeries: TAreaSeries);
|
|
|
|
procedure UpdateBarSeries(AWorkbookSeries: TsBarSeries; AChartSeries: TBarSeries);
|
2023-11-23 00:08:33 +00:00
|
|
|
procedure UpdateLineSeries(AWorkbookSeries: TsLineSeries; AChartSeries: TLineSeries);
|
|
|
|
procedure UpdatePieSeries(AWorkbookSeries: TsPieSeries; AChartSeries: TPieSeries);
|
|
|
|
procedure UpdateScatterSeries(AWorkbookSeries: TsScatterSeries; AChartSeries: TLineSeries);
|
|
|
|
|
2023-11-21 15:18:51 +00:00
|
|
|
public
|
|
|
|
constructor Create(AOwner: TComponent); override;
|
|
|
|
destructor Destroy; override;
|
|
|
|
|
2023-11-22 14:23:55 +00:00
|
|
|
procedure UpdateChart;
|
|
|
|
|
2023-11-21 15:18:51 +00:00
|
|
|
{ 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;
|
|
|
|
|
2023-11-22 22:08:47 +00:00
|
|
|
type
|
|
|
|
TBasicPointSeriesOpener = class(TBasicPointSeries);
|
|
|
|
|
2023-11-22 14:23:55 +00:00
|
|
|
function mmToPx(mm: Double; ppi: Integer): Integer;
|
|
|
|
begin
|
|
|
|
Result := round(mmToIn(mm * ppi));
|
|
|
|
end;
|
|
|
|
|
2015-01-03 20:03:55 +00:00
|
|
|
{------------------------------------------------------------------------------}
|
|
|
|
{ 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
|
2023-11-22 14:23:55 +00:00
|
|
|
if FRanges[rngX] <> nil then
|
|
|
|
GetXYItem(rngX, AIndex, FCurItem.X, tmpLabel)
|
|
|
|
else
|
|
|
|
FCurItem.X := AIndex;
|
|
|
|
|
2018-07-22 13:58:46 +00:00
|
|
|
GetXYItem(rngY, AIndex, FCurItem.Y, dummyString);
|
2023-11-22 14:23:55 +00:00
|
|
|
|
2018-07-22 13:58:46 +00:00
|
|
|
GetXYItem(rngLabel, AIndex, dummyNumber, FCurItem.Text);
|
|
|
|
if FCurItem.Text = '' then FCurItem.Text := tmpLabel;
|
2023-11-22 14:23:55 +00:00
|
|
|
|
|
|
|
if FRanges[rngColor] <> nil then
|
|
|
|
begin
|
|
|
|
GetXYItem(rngColor, AIndex, dummyNumber, dummyString);
|
|
|
|
FCurItem.Color := round(dummyNumber);
|
|
|
|
end else
|
|
|
|
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;
|
|
|
|
|
2023-11-22 14:23:55 +00:00
|
|
|
function TsWorkbookChartSource.GetTitle: String;
|
|
|
|
var
|
|
|
|
sheet: TsWorksheet;
|
|
|
|
begin
|
|
|
|
Result := '';
|
|
|
|
if FWorkbookSource = nil then
|
|
|
|
exit;
|
|
|
|
sheet := FWorkbookSource.Workbook.GetWorksheetByName(FTitleSheetName);
|
|
|
|
if sheet <> nil then
|
|
|
|
Result := sheet.ReadAsText(FTitleRow, FTitleCol);
|
|
|
|
end;
|
|
|
|
|
2015-01-03 20:03:55 +00:00
|
|
|
{@@ ----------------------------------------------------------------------------
|
|
|
|
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
|
2023-11-22 14:23:55 +00:00
|
|
|
Prepare(rngColor);
|
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)
|
|
|
|
|
2023-11-22 14:23:55 +00:00
|
|
|
@param AIndex Identifies whether x or y or label or color 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
|
|
|
var
|
|
|
|
range: TsCellRange;
|
|
|
|
begin
|
2023-11-22 14:23:55 +00:00
|
|
|
if (Workbook = nil) or (FRangeStr[AIndex] = '') then
|
|
|
|
begin
|
2015-01-05 23:32:49 +00:00
|
|
|
FWorksheets[AIndex] := nil;
|
|
|
|
SetLength(FRanges[AIndex], 0);
|
2023-11-22 14:23:55 +00:00
|
|
|
if AIndex = rngY then
|
|
|
|
FPointsNumber := 0;
|
2015-01-05 23:32:49 +00:00
|
|
|
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
|
|
|
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;
|
|
|
|
|
2023-11-22 14:23:55 +00:00
|
|
|
procedure TsWorkbookChartSource.SetColorRange(ARange: TsChartRange);
|
|
|
|
begin
|
|
|
|
SetRangeFromChart(rngColor, ARange);
|
|
|
|
end;
|
|
|
|
|
|
|
|
procedure TsWorkbookChartSource.SetLabelRange(ARange: TsChartRange);
|
|
|
|
begin
|
|
|
|
SetRangeFromChart(rngLabel, ARange);
|
|
|
|
end;
|
|
|
|
|
|
|
|
{@@ ----------------------------------------------------------------------------
|
|
|
|
Shared method to set the cell ranges for x, y, labels or colors directly from
|
|
|
|
the chart ranges.
|
|
|
|
-------------------------------------------------------------------------------}
|
|
|
|
procedure TsWorkbookChartSource.SetRangeFromChart(AIndex: TsXYLRange;
|
|
|
|
const ARange: TsChartRange);
|
|
|
|
begin
|
|
|
|
if ARange.Sheet1 <> ARange.Sheet2 then
|
|
|
|
raise Exception.Create('A chart cell range can only be from a single worksheet.');
|
|
|
|
SetLength(FRanges[AIndex], 1);
|
|
|
|
FRanges[AIndex,0].Row1 := ARange.Row1; // FIXME: Assuming here single-block range !!!
|
|
|
|
FRanges[AIndex,0].Col1 := ARange.Col1;
|
|
|
|
FRanges[AIndex,0].Row2 := ARange.Row2;
|
|
|
|
FRanges[AIndex,0].Col2 := ARange.Col2;
|
|
|
|
FWorksheets[AIndex] := FworkbookSource.Workbook.GetWorksheetByName(ARange.Sheet1);
|
|
|
|
if AIndex in [rngX, rngY] then
|
|
|
|
FPointsNumber := Max(CountValues(rngX), CountValues(rngY));
|
|
|
|
end;
|
|
|
|
|
2015-01-03 20:03:55 +00:00
|
|
|
{@@ ----------------------------------------------------------------------------
|
|
|
|
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;
|
|
|
|
|
2023-11-22 14:23:55 +00:00
|
|
|
procedure TsWorkbookChartSource.SetTitleAddr(Addr: TsChartCellAddr);
|
|
|
|
begin
|
|
|
|
FTitleRow := Addr.Row;
|
|
|
|
FTitleCol := Addr.Col;
|
|
|
|
FTitleSheetName := Addr.GetSheetName;
|
|
|
|
end;
|
|
|
|
|
|
|
|
procedure TsWorkbookChartSource.SetXRange(ARange: TsChartRange);
|
|
|
|
begin
|
|
|
|
SetRangeFromChart(rngX, ARange);
|
|
|
|
end;
|
|
|
|
|
|
|
|
procedure TsWorkbookChartSource.SetYRange(ARange: TsChartRange);
|
|
|
|
begin
|
|
|
|
SetRangeFromChart(rngY, ARange);
|
|
|
|
end;
|
|
|
|
|
2015-01-03 20:03:55 +00:00
|
|
|
{@@ ----------------------------------------------------------------------------
|
|
|
|
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;
|
2023-11-23 18:49:41 +00:00
|
|
|
FBrushBitmaps := TFPObjectList.Create;
|
2023-11-21 15:18:51 +00:00
|
|
|
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);
|
2023-11-23 18:49:41 +00:00
|
|
|
FBrushBitmaps.Free;
|
2023-11-21 15:18:51 +00:00
|
|
|
inherited;
|
|
|
|
end;
|
|
|
|
|
2023-11-22 14:23:55 +00:00
|
|
|
procedure TsWorkbookChartLink.AddSeries(ASeries: TsChartSeries);
|
|
|
|
var
|
|
|
|
src: TsWorkbookChartSource;
|
|
|
|
ser: TChartSeries;
|
|
|
|
begin
|
|
|
|
src := TsWorkbookChartSource.Create(self);
|
|
|
|
src.WorkbookSource := FWorkbookSource;
|
|
|
|
if not ASeries.LabelRange.IsEmpty then src.SetLabelRange(ASeries.LabelRange);
|
|
|
|
if not ASeries.XRange.IsEmpty then src.SetXRange(ASeries.XRange);
|
|
|
|
if not ASeries.YRange.IsEmpty then src.SetYRange(ASeries.YRange);
|
|
|
|
if not ASeries.FillColorRange.IsEmpty then src.SetColorRange(ASeries.FillColorRange);
|
|
|
|
|
|
|
|
case ASeries.ChartType of
|
|
|
|
ctBar:
|
2023-11-23 00:08:33 +00:00
|
|
|
ser := TBarSeries.Create(FChart);
|
2023-11-22 14:23:55 +00:00
|
|
|
ctLine, ctScatter:
|
2023-11-23 00:08:33 +00:00
|
|
|
ser := TLineSeries.Create(FChart);
|
2023-11-22 14:23:55 +00:00
|
|
|
ctArea:
|
2023-11-23 00:08:33 +00:00
|
|
|
ser := TAreaSeries.Create(FChart);
|
2023-11-22 22:08:47 +00:00
|
|
|
ctPie, ctRing:
|
2023-11-23 00:08:33 +00:00
|
|
|
ser := TPieSeries.Create(FChart);
|
2023-11-22 14:23:55 +00:00
|
|
|
end;
|
2023-11-22 22:08:47 +00:00
|
|
|
|
2023-11-22 14:23:55 +00:00
|
|
|
src.SetTitleAddr(ASeries.TitleAddr);
|
|
|
|
ser.Source := src;
|
|
|
|
ser.Title := src.Title;
|
|
|
|
ser.Transparency := round(ASeries.Fill.Transparency);
|
2023-11-22 22:08:47 +00:00
|
|
|
UpdateChartSeriesMarks(ASeries, ser);
|
|
|
|
|
2023-11-22 14:23:55 +00:00
|
|
|
FChart.AddSeries(ser);
|
2023-11-23 00:08:33 +00:00
|
|
|
|
|
|
|
case ASeries.ChartType of
|
|
|
|
ctArea:
|
2023-11-23 18:49:41 +00:00
|
|
|
UpdateAreaSeries(TsAreaSeries(ASeries), TAreaSeries(ser));
|
2023-11-23 00:08:33 +00:00
|
|
|
ctBar:
|
2023-11-23 18:49:41 +00:00
|
|
|
UpdateBarSeries(TsBarSeries(ASeries), TBarSeries(ser));
|
2023-11-23 00:08:33 +00:00
|
|
|
ctLine:
|
|
|
|
UpdateLineSeries(TsLineSeries(ASeries), TLineSeries(ser));
|
|
|
|
ctScatter:
|
|
|
|
UpdateScatterSeries(TsScatterSeries(ASeries), TLineSeries(ser));
|
|
|
|
ctPie, ctRing:
|
|
|
|
UpdatePieSeries(TsPieSeries(ASeries), TPieSeries(ser));
|
|
|
|
end;
|
2023-11-22 14:23:55 +00:00
|
|
|
end;
|
|
|
|
|
2023-11-21 15:18:51 +00:00
|
|
|
procedure TsWorkbookChartLink.ClearChart;
|
2023-11-21 21:59:26 +00:00
|
|
|
var
|
|
|
|
i, j: Integer;
|
2023-11-22 14:23:55 +00:00
|
|
|
ser: TChartSeries;
|
|
|
|
src: TCustomChartSource;
|
2023-11-21 15:18:51 +00:00
|
|
|
begin
|
|
|
|
if FChart = nil then
|
|
|
|
exit;
|
2023-11-21 21:59:26 +00:00
|
|
|
|
2023-11-22 14:23:55 +00:00
|
|
|
// Clear chart sources
|
|
|
|
for i := 0 to FChart.SeriesCount-1 do
|
|
|
|
begin
|
|
|
|
if (FChart.Series[i] is TChartSeries) then
|
|
|
|
begin
|
|
|
|
ser := TChartSeries(FChart.Series[i]);
|
|
|
|
src := ser.Source;
|
|
|
|
if src is TsWorkbookChartSource then
|
|
|
|
src.Free;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
2023-11-21 21:59:26 +00:00
|
|
|
// Clear the series
|
|
|
|
FChart.ClearSeries;
|
|
|
|
|
|
|
|
// Clear the axes
|
|
|
|
for i := FChart.AxisList.Count-1 downto 0 do
|
|
|
|
begin
|
|
|
|
case FChart.AxisList[i].Alignment of
|
|
|
|
calLeft, calBottom:
|
|
|
|
FChart.AxisList[i].Title.Caption := '';
|
|
|
|
calTop, calRight:
|
|
|
|
FChart.AxisList.Delete(i);
|
|
|
|
end;
|
|
|
|
for j := FChart.AxisList[i].Minors.Count-1 downto 0 do
|
|
|
|
FChart.AxisList[i].Minors.Delete(j);
|
|
|
|
end;
|
|
|
|
|
2023-11-21 15:18:51 +00:00
|
|
|
// Clear the title
|
|
|
|
FChart.Title.Text.Clear;
|
2023-11-21 21:59:26 +00:00
|
|
|
|
2023-11-21 15:18:51 +00:00
|
|
|
// Clear the footer
|
|
|
|
FChart.Foot.Text.Clear;
|
|
|
|
end;
|
|
|
|
|
2023-11-22 22:08:47 +00:00
|
|
|
procedure TsWorkbookChartLink.ConstructSeriesMarks(AWorkbookSeries: TsChartSeries;
|
|
|
|
AChartSeries: TChartSeries);
|
|
|
|
var
|
|
|
|
sep: String;
|
|
|
|
textFmt: String;
|
|
|
|
valueFmt: String;
|
|
|
|
percentFmt: String;
|
|
|
|
begin
|
|
|
|
if AWorkbookSeries.DataLabels = [cdlValue] then
|
|
|
|
AChartSeries.Marks.Style := smsValue
|
|
|
|
else if AWorkbookSeries.DataLabels = [cdlPercentage] then
|
|
|
|
AChartSeries.Marks.Style := smsPercent
|
|
|
|
else if AWorkbookSeries.DataLabels = [cdlCategory] then
|
|
|
|
AChartSeries.Marks.Style := smsLabel
|
|
|
|
else
|
|
|
|
begin
|
|
|
|
sep := AWorkbookSeries.LabelSeparator;
|
|
|
|
valueFmt := '%0:.9g';
|
|
|
|
percentFmt := '%1:.0f';
|
|
|
|
textFmt := '%2:s';
|
|
|
|
if (AWorkbookSeries.DataLabels * [cdlCategory, cdlValue, cdlPercentage] = [cdlCategory, cdlValue, cdlPercentage]) then
|
|
|
|
AChartSeries.Marks.Format := textFmt + sep + valueFmt + sep + percentFmt
|
|
|
|
else
|
|
|
|
if AWorkbookSeries.DataLabels * [cdlValue, cdlPercentage] = [cdlValue, cdlPercentage] then
|
|
|
|
AChartSeries.Marks.Format := valueFmt + sep + percentFmt
|
|
|
|
else if AWorkbookSeries.DataLabels * [cdlCategory, cdlValue] = [cdlCategory, cdlValue] then
|
|
|
|
AChartSeries.Marks.Format := textFmt + sep + valueFmt;
|
|
|
|
end;
|
|
|
|
AChartSeries.Marks.Alignment := taCenter;
|
|
|
|
end;
|
|
|
|
|
2023-11-23 00:08:33 +00:00
|
|
|
{
|
|
|
|
procedure TsWorkbookChartLink.FitSeriesFitEquationText(ASeries: TFitSeries;
|
|
|
|
AEquationText: IFitEquationText);
|
|
|
|
begin
|
|
|
|
if ASeries.ErrCode = fitOK then
|
|
|
|
begin
|
|
|
|
AEquationText.NumFormat('%.5f');
|
|
|
|
AEquationText.TextFormat(tfHtml)
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
}
|
2023-11-22 14:23:55 +00:00
|
|
|
// Fix area series zero level not being clipped at chart's plotrect.
|
|
|
|
procedure TsWorkbookChartLink.FixAreaSeries(AWorkbookChart: TsChart);
|
|
|
|
var
|
|
|
|
i: Integer;
|
|
|
|
ser: TAreaSeries;
|
|
|
|
ext: TDoubleRect;
|
|
|
|
begin
|
2023-11-22 22:08:47 +00:00
|
|
|
{$IF LCL_FullVersion < 3990000}
|
2023-11-22 14:23:55 +00:00
|
|
|
if AWorkbookChart.GetChartType <> ctArea then
|
|
|
|
exit;
|
|
|
|
|
|
|
|
ext := FChart.LogicalExtent;
|
|
|
|
for i := 0 to FChart.SeriesCount-1 do
|
|
|
|
if FChart.Series[i] is TAreaSeries then
|
|
|
|
begin
|
|
|
|
ser := TAreaSeries(FChart.Series[i]);
|
|
|
|
if ser.ZeroLevel < ext.a.y then
|
|
|
|
ser.ZeroLevel := ext.a.y;
|
|
|
|
if ser.ZeroLevel > ext.b.y then
|
|
|
|
ser.ZeroLevel := ext.b.y;
|
|
|
|
ser.UseZeroLevel := true;
|
|
|
|
end;
|
2023-11-22 22:08:47 +00:00
|
|
|
{$ENDIF}
|
2023-11-22 14:23:55 +00:00
|
|
|
end;
|
|
|
|
|
2023-11-21 15:18:51 +00:00
|
|
|
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.SetChart(AValue: TChart);
|
|
|
|
begin
|
|
|
|
if FChart = AValue then
|
|
|
|
exit;
|
|
|
|
FChart := AValue;
|
2023-11-22 14:23:55 +00:00
|
|
|
UpdateChart;
|
2023-11-21 15:18:51 +00:00
|
|
|
end;
|
|
|
|
|
|
|
|
procedure TSWorkbookChartLink.SetWorkbookChartIndex(AValue: Integer);
|
|
|
|
begin
|
|
|
|
if AValue = FWorkbookChartIndex then
|
|
|
|
exit;
|
|
|
|
FWorkbookChartIndex := AValue;
|
2023-11-22 14:23:55 +00:00
|
|
|
UpdateChart;
|
2023-11-21 15:18:51 +00:00
|
|
|
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]);
|
2023-11-22 14:23:55 +00:00
|
|
|
UpdateChart;
|
|
|
|
end;
|
|
|
|
|
2023-11-23 18:49:41 +00:00
|
|
|
procedure TsWorkbookChartLink.UpdateAreaSeries(AWorkbookSeries: TsAreaSeries;
|
|
|
|
AChartSeries: TAreaSeries);
|
|
|
|
begin
|
|
|
|
UpdateChartBrush(AWorkbookSeries.Chart, AWorkbookSeries.Fill, AChartSeries.AreaBrush);
|
|
|
|
UpdateChartPen(AWorkbookSeries.Line, AChartSeries.AreaContourPen);
|
|
|
|
AChartSeries.AreaLinesPen.Style := psClear;
|
|
|
|
end;
|
|
|
|
|
|
|
|
procedure TsWorkbookChartLink.UpdateBarSeries(AWorkbookSeries: TsBarSeries;
|
|
|
|
AChartSeries: TBarSeries);
|
|
|
|
begin
|
|
|
|
UpdateChartBrush(AWorkbookSeries.Chart, AWorkbookSeries.Fill, AChartSeries.BarBrush);
|
|
|
|
UpdateChartPen(AWorkbookSeries.Line, AChartSeries.BarPen);
|
|
|
|
end;
|
|
|
|
|
2023-11-22 14:23:55 +00:00
|
|
|
procedure TsWorkbookChartLink.UpdateChart;
|
|
|
|
var
|
|
|
|
ch: TsChart;
|
|
|
|
i: Integer;
|
|
|
|
begin
|
|
|
|
if (FChart = nil) then
|
|
|
|
exit;
|
|
|
|
if (FWorkbookSource = nil) or (FWorkbookChartIndex < 0) then
|
|
|
|
begin
|
|
|
|
ClearChart;
|
|
|
|
exit;
|
|
|
|
end;
|
|
|
|
|
|
|
|
ch := GetWorkbookChart;
|
|
|
|
UpdateChartBackground(ch);
|
|
|
|
UpdateChartTitle(ch.Title, FChart.Title);
|
|
|
|
UpdateChartTitle(ch.Subtitle, FChart.Foot);
|
|
|
|
UpdateChartLegend(ch.Legend, FChart.Legend);
|
|
|
|
UpdateChartAxis(ch.XAxis);
|
|
|
|
UpdateChartAxis(ch.YAxis);
|
|
|
|
UpdateChartAxis(ch.X2Axis);
|
|
|
|
UpdateChartAxis(ch.Y2Axis);
|
|
|
|
|
|
|
|
for i := 0 to ch.Series.Count-1 do
|
|
|
|
AddSeries(ch.Series[i]);
|
|
|
|
|
|
|
|
FChart.Prepare;
|
|
|
|
UpdateChartAxisLabels(ch);
|
|
|
|
UpdateBarSeries(ch);
|
|
|
|
FixAreaSeries(ch);
|
2023-11-21 15:18:51 +00:00
|
|
|
end;
|
|
|
|
|
2023-11-21 21:59:26 +00:00
|
|
|
procedure TsWorkbookChartLink.UpdateChartAxis(AWorkbookAxis: TsChartAxis);
|
|
|
|
var
|
|
|
|
align: TChartAxisAlignment;
|
|
|
|
axis: TChartAxis;
|
|
|
|
minorAxis: TChartMinorAxis;
|
|
|
|
begin
|
|
|
|
if AWorkbookAxis = nil then
|
|
|
|
exit;
|
|
|
|
if AWorkbookAxis = AWorkbookAxis.Chart.XAxis then
|
|
|
|
align := calBottom
|
|
|
|
else if AWorkbookAxis = AWorkbookAxis.Chart.X2Axis then
|
|
|
|
align := calTop
|
|
|
|
else if AWorkbookAxis = AWorkbookAxis.Chart.YAxis then
|
|
|
|
align := calLeft
|
|
|
|
else if AWorkbookAxis = AWorkbookAxis.Chart.Y2Axis then
|
|
|
|
align := calRight
|
|
|
|
else
|
|
|
|
raise Exception.Create('Unsupported axis alignment');
|
|
|
|
axis := FChart.AxisList.GetAxisByAlign(align);
|
|
|
|
|
|
|
|
if AWorkbookAxis.Visible and (axis = nil) then
|
|
|
|
begin
|
|
|
|
axis := FChart.AxisList.Add;
|
|
|
|
axis.Alignment := align;
|
|
|
|
end;
|
|
|
|
|
|
|
|
if axis = nil then
|
|
|
|
exit;
|
|
|
|
|
|
|
|
// Entire axis visible?
|
|
|
|
axis.Visible := AWorkbookAxis.Visible;
|
|
|
|
|
|
|
|
// Axis title
|
|
|
|
axis.Title.Caption := AWorkbookAxis.Title.Caption;
|
|
|
|
axis.Title.Visible := true;
|
|
|
|
Convert_sFont_to_Font(AWorkbookAxis.Title.Font, axis.Title.LabelFont);
|
|
|
|
|
|
|
|
// Labels
|
|
|
|
Convert_sFont_to_Font(AWorkbookAxis.LabelFont, axis.Marks.LabelFont);
|
2023-11-22 22:08:47 +00:00
|
|
|
axis.Marks.LabelFont.Orientation := round(AWorkbookAxis.LabelRotation * 10);
|
2023-11-21 21:59:26 +00:00
|
|
|
|
|
|
|
// Axis line
|
|
|
|
UpdateChartPen(AWorkbookAxis.AxisLine, axis.AxisPen);
|
|
|
|
axis.AxisPen.Visible := axis.AxisPen.Style <> psClear;
|
|
|
|
|
|
|
|
// Major axis grid
|
|
|
|
UpdateChartPen(AWorkbookAxis.MajorGridLines, axis.Grid);
|
|
|
|
axis.Grid.Visible := axis.Grid.Style <> psClear;
|
|
|
|
axis.TickLength := IfThen(catOutside in AWorkbookAxis.MajorTicks, 4, 0);
|
|
|
|
axis.TickInnerLength := IfThen(catInside in AWorkbookAxis.MajorTicks, 4, 0);
|
2023-11-22 22:08:47 +00:00
|
|
|
axis.TickColor := axis.AxisPen.Color;
|
|
|
|
axis.TickWidth := axis.AxisPen.Width;
|
2023-11-21 21:59:26 +00:00
|
|
|
|
|
|
|
// Minor axis grid
|
|
|
|
if AWorkbookAxis.MinorGridLines.Style <> clsNoLine then
|
|
|
|
begin
|
|
|
|
minorAxis := axis.Minors.Add;
|
|
|
|
UpdateChartPen(AWorkbookAxis.MinorGridLines, minorAxis.Grid);
|
|
|
|
minorAxis.Grid.Visible := true;
|
|
|
|
minorAxis.Intervals.Count := AWorkbookAxis.MinorCount;
|
|
|
|
minorAxis.TickLength := IfThen(catOutside in AWorkbookAxis.MinorTicks, 2, 0);
|
|
|
|
minorAxis.TickInnerLength := IfThen(catInside in AWorkbookAxis.MinorTicks, 2, 0);
|
2023-11-22 22:08:47 +00:00
|
|
|
minorAxis.TickColor := axis.AxisPen.Color;
|
2023-11-21 21:59:26 +00:00
|
|
|
minorAxis.TickWidth := minorAxis.Grid.Width;
|
|
|
|
end;
|
|
|
|
|
|
|
|
// Inverted?
|
|
|
|
axis.Inverted := AWorkbookAxis.Inverted;
|
|
|
|
|
|
|
|
// Logarithmic?
|
|
|
|
// to do....
|
|
|
|
|
|
|
|
// Scaling
|
|
|
|
axis.Range.UseMin := not AWorkbookAxis.AutomaticMin;
|
|
|
|
axis.Range.UseMax := not AWorkbookAxis.AutomaticMax;
|
|
|
|
axis.Range.Min := AWorkbookAxis.Min;
|
|
|
|
axis.Range.Max := AWorkbookAxis.Max;
|
|
|
|
end;
|
|
|
|
|
2023-11-22 14:23:55 +00:00
|
|
|
procedure TsWorkbookChartLink.UpdateChartAxisLabels(AWorkbookChart: TsChart);
|
|
|
|
begin
|
|
|
|
if (FChart.SeriesCount > 0) and
|
|
|
|
(AWorkbookChart.GetChartType in [ctBar, ctLine, ctArea]) then
|
|
|
|
begin
|
|
|
|
FChart.BottomAxis.Marks.Source := TChartSeries(FChart.Series[0]).Source;
|
|
|
|
if not AWorkbookChart.Series[0].LabelRange.IsEmpty then
|
|
|
|
FChart.BottomAxis.Marks.Style := smsLabel
|
|
|
|
else
|
|
|
|
FChart.BottomAxis.Marks.Style := smsXValue;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
2023-11-21 21:59:26 +00:00
|
|
|
procedure TsWorkbookChartLink.UpdateChartBackground(AWorkbookChart: TsChart);
|
|
|
|
begin
|
|
|
|
FChart.Color := Convert_sColor_to_Color(AWorkbookChart.Background.Color);
|
|
|
|
FChart.BackColor := Convert_sColor_to_Color(AWorkbookChart.PlotArea.Background.Color);
|
|
|
|
UpdateChartPen(AWorkbookChart.PlotArea.Border, FChart.Frame);
|
|
|
|
FChart.Frame.Visible := AWorkbookChart.PlotArea.Border.Style <> clsNoLine;
|
|
|
|
end;
|
|
|
|
|
2023-11-22 14:23:55 +00:00
|
|
|
procedure TsWorkbookChartLink.UpdateBarSeries(AWorkbookChart: TsChart);
|
|
|
|
var
|
|
|
|
i, n: Integer;
|
|
|
|
ser: TBarSeries;
|
|
|
|
barWidth, totalBarWidth: Integer;
|
|
|
|
begin
|
|
|
|
if AWorkbookChart.GetChartType <> ctBar then
|
|
|
|
exit;
|
|
|
|
|
|
|
|
// Count the bar series
|
|
|
|
n := 0;
|
|
|
|
for i := 0 to AWorkbookChart.Series.Count-1 do
|
|
|
|
begin
|
|
|
|
if AWorkbookChart.Series[i].ChartType = ctBar then
|
|
|
|
inc(n);
|
|
|
|
end;
|
|
|
|
|
|
|
|
// Iterate over bar series to put them side-by-side or to stack them
|
|
|
|
totalBarWidth := 90;
|
|
|
|
barWidth := round(totalBarWidth / n);
|
|
|
|
for i := 0 to FChart.SeriesCount-1 do
|
|
|
|
if FChart.Series[i] is TBarSeries then
|
|
|
|
begin
|
|
|
|
ser := TBarSeries(FChart.Series[i]);
|
|
|
|
case AWorkbookChart.Stackmode of
|
|
|
|
csmSideBySide:
|
|
|
|
begin
|
|
|
|
ser.BarWidthPercent := barWidth;
|
|
|
|
ser.BarWidthStyle := bwPercentMin;
|
|
|
|
ser.BarOffsetPercent := round((i - (n - 1)/2)*barWidth);
|
|
|
|
end;
|
|
|
|
csmStacked:
|
|
|
|
ser.Stacked := true;
|
|
|
|
csmStackedPercentage:
|
|
|
|
begin
|
|
|
|
ser.Stacked := true;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
2023-11-23 18:49:41 +00:00
|
|
|
procedure TsWorkbookChartLink.UpdateChartBrush(AWorkbookChart: TsChart;
|
|
|
|
AWorkbookFill: TsChartFill; ABrush: TBrush);
|
|
|
|
var
|
|
|
|
img: TsChartImage;
|
|
|
|
png: TCustomBitmap;
|
|
|
|
w, h, ppi: Integer;
|
2023-11-21 15:18:51 +00:00
|
|
|
begin
|
|
|
|
if (AWorkbookFill <> nil) and (ABrush <> nil) then
|
|
|
|
begin
|
|
|
|
ABrush.Color := Convert_sColor_to_Color(AWorkbookFill.Color);
|
2023-11-23 18:49:41 +00:00
|
|
|
case AWorkbookFill.Style of
|
|
|
|
cfsNoFill:
|
|
|
|
ABrush.Style := bsClear;
|
|
|
|
cfsSolid:
|
|
|
|
ABrush.Style := bsSolid;
|
|
|
|
cfsGradient:
|
|
|
|
ABrush.Style := bsSolid; // NOTE: TAChart cannot display gradients
|
|
|
|
cfsHatched, cfsSolidHatched:
|
|
|
|
ABrush.Style := bsSolid;
|
|
|
|
cfsImage:
|
|
|
|
begin
|
|
|
|
img := AWorkbookChart.Images[AWorkbookFill.Image];
|
|
|
|
if img <> nil then
|
|
|
|
begin
|
|
|
|
ppi := GetParentForm(FChart).PixelsPerInch;
|
|
|
|
w := mmToPx(img.Width, ppi);
|
|
|
|
h := mmToPx(img.Height, ppi);
|
|
|
|
png := TPortableNetworkGraphic.Create;
|
|
|
|
png.Assign(img.Image);
|
|
|
|
ScaleImg(png, w, h);
|
|
|
|
FBrushBitmaps.Add(png);
|
|
|
|
ABrush.Bitmap := png;
|
|
|
|
end else
|
|
|
|
ABrush.Style := bsSolid;
|
|
|
|
end;
|
|
|
|
end;
|
2023-11-21 15:18:51 +00:00
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
2023-11-21 21:59:26 +00:00
|
|
|
procedure TsWorkbookChartLink.UpdateChartLegend(AWorkbookLegend: TsChartLegend;
|
|
|
|
ALegend: TChartLegend);
|
|
|
|
const
|
|
|
|
LEG_POS: array[TsChartLegendPosition] of TLegendAlignment = (
|
|
|
|
laCenterRight, // lpRight
|
|
|
|
laTopCenter, // lpTop
|
|
|
|
laBottomCenter, // lpBottom
|
|
|
|
laCenterLeft // lpLeft
|
|
|
|
);
|
|
|
|
begin
|
|
|
|
if (AWorkbookLegend <> nil) and (ALegend <> nil) then
|
|
|
|
begin
|
|
|
|
Convert_sFont_to_Font(AWorkbookLegend.Font, ALegend.Font);
|
|
|
|
UpdateChartPen(AWorkbookLegend.Border, ALegend.Frame);
|
2023-11-23 18:49:41 +00:00
|
|
|
UpdateChartBrush(AWorkbookLegend.Chart, AWorkbookLegend.Background, ALegend.BackgroundBrush);
|
2023-11-21 21:59:26 +00:00
|
|
|
ALegend.Frame.Visible := (ALegend.Frame.Style <> psClear);
|
|
|
|
ALegend.Alignment := LEG_POS[AWorkbookLegend.Position];
|
|
|
|
ALegend.UseSidebar := not AWorkbookLegend.CanOverlapPlotArea;
|
|
|
|
ALegend.Visible := AWorkbookLegend.Visible;
|
2023-11-22 14:23:55 +00:00
|
|
|
ALegend.Inverted := true;
|
2023-11-23 00:08:33 +00:00
|
|
|
ALegend.TextFormat := tfHTML;
|
2023-11-21 21:59:26 +00:00
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
2023-11-21 15:18:51 +00:00
|
|
|
procedure TsWorkbookChartLink.UpdateChartPen(AWorkbookLine: TsChartLine;
|
|
|
|
APen: TPen);
|
|
|
|
begin
|
|
|
|
if (AWorkbookLine <> nil) and (APen <> nil) then
|
|
|
|
begin
|
|
|
|
APen.Color := Convert_sColor_to_Color(AWorkbookLine.Color);
|
2023-11-22 14:23:55 +00:00
|
|
|
APen.Width := mmToPx(AWorkbookLine.Width, GetParentForm(FChart).PixelsPerInch);
|
2023-11-21 15:18:51 +00:00
|
|
|
case AWorkbookLine.Style of
|
|
|
|
clsNoLine:
|
|
|
|
APen.Style := psClear;
|
|
|
|
clsSolid:
|
|
|
|
APen.Style := psSolid;
|
|
|
|
else // to be fixed
|
2023-11-21 21:59:26 +00:00
|
|
|
if (AWorkbookLine.Style in [clsDash, clsLongDash]) then
|
|
|
|
APen.Style := psDash
|
|
|
|
else
|
|
|
|
if (AWorkbookLine.Style = clsDot) then
|
|
|
|
APen.Style := psDot
|
|
|
|
else
|
|
|
|
if (AWorkbookLine.Style in [clsDashDot, clsLongDashDot]) then
|
|
|
|
APen.Style := psDashDot
|
|
|
|
else
|
|
|
|
if (AWorkbookLine.Style in [clsLongDashDotDot]) then
|
|
|
|
APen.Style := psDashDotDot
|
|
|
|
else
|
|
|
|
// to be fixed: create pattern as defined.
|
|
|
|
APen.Style := psDash;
|
2023-11-21 15:18:51 +00:00
|
|
|
end;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
2023-11-22 22:08:47 +00:00
|
|
|
procedure TsWorkbookChartLink.UpdateChartSeriesMarks(AWorkbookSeries: TsChartSeries;
|
|
|
|
AChartSeries: TChartSeries);
|
|
|
|
begin
|
|
|
|
ConstructSeriesMarks(AWorkbookSeries, AChartSeries);
|
|
|
|
AChartSeries.Marks.LinkPen.Visible := false;
|
|
|
|
if (AChartSeries is TPieSeries) then
|
|
|
|
case AWorkbookSeries.LabelPosition of
|
|
|
|
lpInside:
|
|
|
|
TPieSeries(AChartSeries).MarkPositions := pmpInside;
|
|
|
|
lpCenter:
|
|
|
|
TPieSeries(AChartSeries).MarkPositionCentered := true;
|
|
|
|
else
|
|
|
|
TPieSeries(AChartSeries).MarkPositions := pmpAround;
|
|
|
|
end
|
|
|
|
else
|
|
|
|
if (AChartSeries is TBasicPointSeries) then
|
|
|
|
case AWorkbookSeries.LabelPosition of
|
|
|
|
lpDefault:
|
|
|
|
TBasicPointSeriesOpener(AChartSeries).MarkPositions := lmpOutside;
|
|
|
|
lpOutside:
|
|
|
|
TBasicPointSeriesOpener(AChartSeries).MarkPositions := lmpOutside;
|
|
|
|
lpInside:
|
|
|
|
TBasicPointSeriesOpener(AChartSeries).MarkPositions := lmpInside;
|
|
|
|
lpCenter:
|
|
|
|
begin
|
|
|
|
TBasicPointSeriesOpener(AChartSeries).MarkPositions := lmpInside;
|
|
|
|
TBasicPointSeriesOpener(AChartSeries).MarkPositionCentered := true;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
|
|
|
UpdateChartPen(AWorkbookSeries.LabelBorder, AChartSeries.Marks.Frame);
|
2023-11-23 18:49:41 +00:00
|
|
|
UpdateChartBrush(AWorkbookSeries.Chart, AWorkbookSeries.LabelBackground, AChartSeries.Marks.LabelBrush);
|
2023-11-22 22:08:47 +00:00
|
|
|
end;
|
|
|
|
|
2023-11-21 15:18:51 +00:00
|
|
|
{@@ 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
|
2023-11-21 21:59:26 +00:00
|
|
|
AChartTitle.Text.Clear;
|
|
|
|
AChartTitle.Text.Add(AWorkbookTitle.Caption);
|
2023-11-21 15:18:51 +00:00
|
|
|
AChartTitle.Visible := AWorkbookTitle.Visible;
|
2023-11-21 21:59:26 +00:00
|
|
|
AChartTitle.WordWrap := true;
|
2023-11-21 15:18:51 +00:00
|
|
|
Convert_sFont_to_Font(AWorkbookTitle.Font, AChartTitle.Font);
|
|
|
|
UpdateChartPen(AWorkbookTitle.Border, AChartTitle.Frame);
|
2023-11-23 18:49:41 +00:00
|
|
|
UpdateChartBrush(AWorkbookTitle.Chart, AWorkbookTitle.Background, AChartTitle.Brush);
|
2023-11-21 15:18:51 +00:00
|
|
|
AChartTitle.Font.Orientation := round(AWorkbookTitle.RotationAngle * 10);
|
|
|
|
AChartTitle.Frame.Visible := (AChartTitle.Frame.Style <> psClear);
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
2023-11-23 00:08:33 +00:00
|
|
|
procedure TsWorkbookChartLink.UpdateLineSeries(AWorkbookSeries: TsLineSeries;
|
|
|
|
AChartSeries: TLineSeries);
|
|
|
|
const
|
|
|
|
POINTER_STYLES: array[TsChartSeriesSymbol] of TSeriesPointerstyle = (
|
|
|
|
psRectangle,
|
|
|
|
psDiamond,
|
|
|
|
psTriangle,
|
|
|
|
psDownTriangle,
|
|
|
|
psLeftTriangle,
|
|
|
|
psRightTriangle,
|
|
|
|
psCircle,
|
|
|
|
psStar,
|
|
|
|
psDiagCross,
|
|
|
|
psCross,
|
|
|
|
psFullStar
|
|
|
|
);
|
|
|
|
var
|
|
|
|
ppi: Integer;
|
|
|
|
begin
|
|
|
|
ppi := GetParentForm(FChart).PixelsPerInch;
|
|
|
|
|
|
|
|
UpdateChartPen(AWorkbookSeries.Line, AChartSeries.LinePen);
|
|
|
|
AChartSeries.ShowLines := AWorkbookSeries.Line.Style <> clsNoLine;
|
|
|
|
AChartSeries.ShowPoints := AWorkbookSeries.ShowSymbols;
|
|
|
|
if AChartSeries.ShowPoints then
|
|
|
|
begin
|
2023-11-23 18:49:41 +00:00
|
|
|
UpdateChartBrush(AWorkbookSeries.Chart, AWorkbookSeries.Fill, AChartSeries.Pointer.Brush);
|
2023-11-23 00:08:33 +00:00
|
|
|
AChartSeries.Pointer.Pen.Color := AChartSeries.LinePen.Color;
|
|
|
|
AChartSeries.Pointer.Style := POINTER_STYLES[AWorkbookSeries.Symbol];
|
|
|
|
AChartSeries.Pointer.HorizSize := mmToPx(AWorkbookSeries.SymbolWidth, ppi);
|
|
|
|
AChartSeries.Pointer.VertSize := mmToPx(AWorkbookSeries.SymbolHeight, ppi);
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
|
|
|
procedure TsWorkbookChartLink.UpdatePieSeries(AWorkbookSeries: TsPieSeries;
|
|
|
|
AChartSeries: TPieSeries);
|
|
|
|
begin
|
|
|
|
AChartSeries.StartAngle := AWorkbookSeries.StartAngle;
|
|
|
|
AChartSeries.Legend.Multiplicity := lmPoint;
|
|
|
|
AChartSeries.Legend.Format := '%2:s';
|
|
|
|
if AWorkbookSeries is TsRingSeries then
|
|
|
|
AChartSeries.InnerRadiusPercent := TsRingSeries(AWorkbookSeries).InnerRadiusPercent;
|
|
|
|
|
|
|
|
FChart.BottomAxis.Visible := false;
|
|
|
|
FChart.LeftAxis.Visible := false;
|
|
|
|
FChart.Legend.Inverted := false;
|
|
|
|
FChart.Frame.Visible := false;
|
|
|
|
end;
|
|
|
|
|
|
|
|
procedure TsWorkbookChartLink.UpdateScatterSeries(AWorkbookSeries: TsScatterSeries;
|
|
|
|
AChartSeries: TLineSeries);
|
|
|
|
var
|
|
|
|
ser: TFitSeries;
|
|
|
|
s: String;
|
|
|
|
begin
|
|
|
|
UpdateLineSeries(AWorkbookSeries, AChartSeries);
|
|
|
|
|
|
|
|
if AWorkbookSeries.Regression.RegressionType = rtNone then
|
|
|
|
exit;
|
|
|
|
|
|
|
|
// Create series and assign chartsource
|
|
|
|
ser := TFitSeries.Create(FChart);
|
|
|
|
ser.Source := AChartSeries.Source;
|
|
|
|
|
|
|
|
// Fit equation
|
|
|
|
case AWorkbookSeries.Regression.RegressionType of
|
|
|
|
rtLinear: ser.FitEquation := feLinear;
|
|
|
|
// rtLogarithmic: ser.FitEquation := feLogarithmic; // to do: implement this!
|
|
|
|
rtExponential: ser.FitEquation := feExp;
|
|
|
|
rtPower: ser.FitEquation := fePower;
|
|
|
|
rtPolynomial:
|
|
|
|
begin
|
|
|
|
ser.FitEquation := fePolynomial;
|
|
|
|
ser.ParamCount := AWorkbookSeries.Regression.PolynomialDegree + 1;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
|
|
|
// Take care of y intercept
|
|
|
|
if AWorkbookSeries.Regression.ForceYIntercept then
|
|
|
|
begin
|
|
|
|
str(AWorkbookSeries.Regression.YInterceptValue, s);
|
|
|
|
ser.FixedParams := s;
|
|
|
|
end;
|
|
|
|
|
|
|
|
// style of regression line
|
|
|
|
UpdateChartPen(AWorkbookSeries.Regression.Line, ser.Pen);
|
|
|
|
|
|
|
|
FChart.AddSeries(ser);
|
|
|
|
|
|
|
|
// Legend text
|
|
|
|
ser.Title := AWorkbookSeries.Regression.Title;
|
|
|
|
|
|
|
|
// Show fit curve in legend after series.
|
|
|
|
ser.Legend.Order := AChartseries.Legend.Order + 1;
|
|
|
|
|
|
|
|
// Regression equation
|
|
|
|
if AWorkbookSeries.Regression.DisplayEquation or AWorkbookSeries.Regression.DisplayRSquare then
|
|
|
|
begin
|
|
|
|
ser.ExecFit;
|
|
|
|
s := '';
|
|
|
|
if AWorkbookSeries.Regression.DisplayEquation then
|
|
|
|
s := s + ser.EquationText.
|
|
|
|
X(AWorkbookSeries.Regression.Equation.XName).
|
|
|
|
Y(AWorkbookSeries.Regression.Equation.YName).
|
|
|
|
NumFormat('%.3f'). // to do: convert from AWorkbookSeries.Regression.Equation.NumberFormat
|
|
|
|
DecimalSeparator('.').
|
|
|
|
TextFormat(tfHtml).
|
|
|
|
Get;
|
|
|
|
if AWorkbookSeries.Regression.DisplayRSquare then
|
|
|
|
s := s + LineEnding + 'R² = ' + FormatFloat('0.00', ser.FitStatistics.R2);
|
|
|
|
if s <> '' then
|
|
|
|
ser.Title := ser.Title + LineEnding + s;
|
|
|
|
// ser.Legend.Format := '%0:s' + LineEnding + '%2:s';
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
2010-05-01 18:10:38 +00:00
|
|
|
end.
|