fpspreadsheet: Workbook chart link supports pie series offsets and data point styles.

git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@9214 8e941d3f-bd1b-0410-a28a-d453659cc2b4
This commit is contained in:
wp_xxyyzz 2024-02-05 22:51:41 +00:00
parent 1ebdbb15d8
commit 07ecb96d10
4 changed files with 135 additions and 48 deletions

View File

@ -63,10 +63,10 @@ begin
line := TsChartline.CreateSolid(scWhite, 0.8);
fill := TsChartFill.CreateHatchFill(ch.Hatches.AddLineHatch('ltHorz', chsSingle, $00C0FF, 1, 0.1, 0), scWhite);
ser.DataPointStyles.AddSolidFill(0, $C47244, line);
ser.DataPointStyles.AddSolidFill(1, $317DED, line, 10); // with explode offset, as percentage
ser.DataPointStyles.AddSolidFill(1, $317DED, line, 20); // with explode offset, as percentage
ser.DataPointStyles.AddSolidFill(2, $A5A5A5, line);
ser.DataPointStyles.AddFillAndLine(3, fill, line);
ser.DataPointStyles.AddSolidFill(4, $D69B5B, line, 20);
ser.DataPointStyles.AddSolidFill(4, $D69B5B, line);
line.Free;
fill.Free;

View File

@ -420,6 +420,7 @@ type
constructor Create(AChart: TsChart);
function AddFillAndLine(ADataPointIndex: Integer; AFill: TsChartFill; ALine: TsChartline; APieOffset: Integer = 0): Integer;
function AddSolidFill(ADataPointIndex: Integer; AColor: TsColor; ALine: TsChartLine = nil; APieOffset: Integer = 0): Integer;
function IndexOfDataPoint(ADataPointIndex: Integer): Integer;
property Items[AIndex: Integer]: TsChartDataPointStyle read GetItem write SetItem; default;
end;
@ -2010,6 +2011,14 @@ begin
Result := TsChartDataPointStyle(inherited Items[AIndex]);
end;
function TsChartDataPointStyleList.IndexOfDataPoint(ADataPointIndex: Integer): Integer;
begin
for Result := 0 to Count - 1 do
if Items[Result].DataPointIndex = ADataPointIndex then
exit;
Result := -1;
end;
procedure TsChartDataPointStyleList.SetItem(AIndex: Integer;
AValue: TsChartDataPointStyle);
begin

View File

@ -2733,8 +2733,7 @@ begin
if dataPointStyle = nil then
begin
// The series process by the caller has not individual style format.
// We must write a node nevertheless...
// No style information found. We write a node, nevertheless... (maybe can be dropped?)
Result := Format(
indent + '<style:style style:name="ch%d" style:family="chart">' + LE +
indent + ' <style:chart-properties/>' + LE +
@ -3744,7 +3743,7 @@ var
trendlineEquation: String = '';
trendline: TsChartTrendline = nil;
titleAddr: String;
i, j, idx, count: Integer;
i, idx, count: Integer;
nextStyleID, seriesStyleID, trendlineStyleID, trendlineEquStyleID: Integer;
xErrStyleID, yErrStyleID, dataStyleID: Integer;
datapointStyle: TsChartDataPointStyle;
@ -3960,7 +3959,7 @@ begin
for i := 0 to count - 1 do
begin
AppendToStream(AChartStream, Format(
indent + ' <chart:data-point chart:style.name="ch%d"/>' + LE,
indent + ' <chart:data-point chart:style-name="ch%d"/>' + LE,
[ dataStyleID + i ]
));
inc(nextStyleID);
@ -4028,17 +4027,7 @@ begin
begin
for i := 0 to count - 1 do
begin
idx := -1;
for j := 0 to series.DataPointStyles.Count-1 do
begin
dataPointStyle := series.DataPointstyles[j];
if (dataPointStyle <> nil) and (dataPointStyle.DataPointIndex = i) then
begin
idx := j;
break;
end;
end;
idx := series.DataPointStyles.IndexOfDatapoint(i);
AppendToStream(AStyleStream,
GetChartSeriesDataPointStyleAsXML(AChart, ASeriesIndex, idx, AStyleIndent, dataStyleID)
);

View File

@ -34,10 +34,6 @@ uses
// FPSpreadsheet Visual
fpSpreadsheetCtrls, fpSpreadsheetGrid, fpsVisualUtils;
{$if LCL_FullVersion >= 3990000}
{$define DATAPOINT_STYLES}
{$ifend}
type
{@@ Chart data source designed to work together with TChart from Lazarus
@ -62,6 +58,10 @@ type
FCyclicX: Boolean;
FIntegerX: Boolean; // only integers allowed for x values
FDataPointColors: array of TsColor;
FPieSeriesMode: boolean;
FPieOffsets: Array of Double;
FStyles: TChartStyles;
function GetRange(AIndex: TsXYLRange): String;
function GetTitle: String;
function GetWorkbook: TsWorkbook;
@ -86,7 +86,7 @@ type
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
function RangeIsEmpty(ARange: TsCellRange): Boolean;
procedure CheckPieSeriesMode(ASeries: TsChartSeries);
procedure Reset;
procedure SetColorRange(ARange: TsChartRange);
procedure SetLabelRange(ARange: TsChartRange);
@ -95,13 +95,17 @@ type
procedure SetTitleAddr(Addr: TsChartCellAddr);
procedure SetXErrorBarRange(APosRange, ANegRange: TsChartRange);
procedure SetYErrorBarRange(APosRange, ANegRange: TsChartRange);
procedure UseDataPointColors(ASeries: TsChartSeries);
property PointsNumber: Cardinal read FPointsNumber;
property Workbook: TsWorkbook read GetWorkbook;
public
// Interface to TsWorkbookSource
procedure ListenerNotification(AChangedItems: TsNotificationItems; AData: Pointer = nil);
procedure RemoveWorkbookSource;
public
// Special methods to be called by TsWorkbookChartLink
function RangeIsEmpty(ARange: TsCellRange): Boolean;
procedure UseDatapointColors(ASeries: TsChartSeries);
property Styles: TChartStyles read FStyles;
published
property WorkbookSource: TsWorkbookSource read FWorkbookSource write SetWorkbookSource;
property ColorRange: String index rngColor read GetRange write SetRange;
@ -180,9 +184,7 @@ type
procedure UpdateScatterSeries(AWorkbookSeries: TsScatterSeries; AChartSeries: TLineSeries);
procedure UpdateStockSeries(AWorkbookSeries: TsStockSeries; AChartSeries: TStockSeries);
{$ifdef DATAPOINT_STYLES}
procedure UseDatapointStyles(AWorkbookSeries: TsChartSeries; AChartSeries: TChartSeries);
{$endif}
procedure CreateChartStylesFromDatapoints(AWorkbookSeries: TsChartSeries; AChartStyles: TChartStyles);
public
constructor Create(AOwner: TComponent); override;
@ -377,6 +379,7 @@ end;
constructor TsWorkbookChartSource.Create(AOwner: TComponent);
begin
inherited;
FStyles := TChartStyles.Create(self);
FCurItemIndex := -1;
ClearRanges;
end;
@ -462,6 +465,9 @@ begin
FRangeStr[rngY] := '';
FRangeStr[rngLabel] := '';
FRangeStr[rngColor] := '';
FStyles.Styles.Clear;
SetLength(FPieOffsets, 0);
end;
@ -526,20 +532,30 @@ begin
exit;
end;
for i := 0 to XCount-1 do
{ In PieSeriesMode, the x values are not taken from the worksheet, but
have been made available in the separate array FPieOffsets. }
if FPieSeriesMode then
begin
if (FRanges[rngX, i] <> nil) then
begin
GetXYItem(rngX, i, AIndex, value, tmpLabel);
if FIntegerX then
value := trunc(value);
end else
if FCyclicX then
value := AIndex / FPointsNumber * TWO_PI
if (AIndex >= 0) and (AIndex < Length(FPieOffsets)) then
FCurItem.SetX(0, FPieOffsets[AIndex])
else
value := AIndex;
FCurItem.SetX(i, value);
end;
FCurItem.SetX(0, 0.0);
end
else
for i := 0 to XCount-1 do
begin
if (FRanges[rngX, i] <> nil) then
begin
GetXYItem(rngX, i, AIndex, value, tmpLabel);
if FIntegerX then
value := trunc(value);
end else
if FCyclicX then
value := AIndex / FPointsNumber * TWO_PI
else
value := AIndex;
FCurItem.SetX(i, value);
end;
for i := 0 to YCount-1 do
begin
@ -556,6 +572,7 @@ begin
FCurItem.Color := clTAColor; // = clDefault
if AIndex <= High(FDataPointColors) then
FCurItem.Color := FDataPointColors[AIndex];
if FRanges[rngColor] <> nil then
begin
GetXYItem(rngColor, 0, AIndex, dummyNumber, dummyString);
@ -997,14 +1014,44 @@ begin
SetRangeFromChart(rngY, YIndex, ARange);
end;
{@@ ----------------------------------------------------------------------------
In case of a pie series, both xlsx and ods files do not provide the
pie offsets as worksheet cell ranges but as attributes in the xml files.
Therefore, we store these offsets separately in an array, FPieOffsets.
-------------------------------------------------------------------------------}
procedure TsWorkbookChartSource.CheckPieSeriesMode(ASeries: TsChartSeries);
var
i, j: Integer;
datapointStyle: TsChartDataPointStyle;
begin
FPieSeriesMode := (ASeries is TsPieSeries);
if FPieSeriesMode then
begin
SetLength(FPieOffsets, ASeries.Count);
for i := 0 to ASeries.Count-1 do
begin
j := ASeries.DataPointStyles.IndexOfDataPoint(i);
FPieOffsets[i] := 0;
dataPointStyle := ASeries.DataPointStyles[j];
if dataPointStyle <> nil then
FPieOffsets[i] := dataPointStyle.PieOffset * 0.01;
end;
end else
SetLength(FPieOffsets, 0);
end;
{@@ ----------------------------------------------------------------------------
Extracts the fill color from the DataPointStyle items of the series. All the
other elements are ignored because TAChart does not support them.
But note: Some series types allow to use chartstyles for individual data point
formatting. In this case this method is not executed.
-------------------------------------------------------------------------------}
procedure TsWorkbookChartSource.UseDataPointColors(ASeries: TsChartSeries);
var
datapointStyle: TsChartDataPointStyle;
i: Integer;
style: TChartStyle;
i, j: Integer;
c: TsColor;
g: TsChartGradient;
begin
@ -1037,7 +1084,6 @@ begin
end;
end;
{@@ ----------------------------------------------------------------------------
Setter method for the WorkbookSource
-------------------------------------------------------------------------------}
@ -1242,12 +1288,18 @@ begin
src.SetColorRange(ASeries.FillColorRange);
src.SetTitleAddr(ASeries.TitleAddr);
{$ifdef DATAPOINT_STYLES}
UseDatapointStyles(ASeries, Result);
{$else}
// Copy individual data point colors to the chart series.
src.UseDataPointColors(ASeries);
{$endif}
// Send pie offsets to chart soruce...
src.CheckPieSeriesMode(ASeries);
// ... as well as datapoint styles/colors
CreateChartStylesFromDatapoints(ASeries, src.Styles);
{$if LCL_FullVersion >= 3990000}
if (Result is TPieSeries) then
TPieSeries(Result).Styles := src.Styles
else if (Result is TBubbleSeries) then
TBubbleSeries(Result).Styles := src.Styles
else if (Result is TBarSeries) then
TBarSeries(Result).Styles := src.Styles;
{$ifend}
if stackable then begin
calcSrc := TCalculatedChartSource.Create(self);
@ -2614,6 +2666,7 @@ begin
if AWorkbookSeries is TsRingSeries then
AChartSeries.InnerRadiusPercent := TsRingSeries(AWorkbookSeries).InnerRadiusPercent;
{$IFEND}
AChartSeries.Exploded := true;
FChart.BottomAxis.Visible := false;
FChart.LeftAxis.Visible := false;
@ -2682,6 +2735,41 @@ begin
UpdateChartSeriesTrendline(AWorkbookSeries, AChartSeries);
end;
procedure TsWorkbookChartLink.CreateChartStylesFromDatapoints(AWorkbookSeries: TsChartSeries;
AChartStyles: TChartStyles);
var
style: TChartStyle;
datapointStyle: TsChartDatapointStyle;
i: Integer;
begin
AChartStyles.Styles.Clear;
if AWorkbookSeries.DataPointStyles.Count = 0 then
exit;
if not ((AWorkbookSeries is TsPieSeries) or (AWorkbookSeries is TsBubbleSeries)) then
exit;
if (AWorkbookSeries is TsBarSeries) and (AWorkbookSeries.Chart.Series.Count > 1) then
exit; // TAChart cannot handle datapoint styles for layered bar series
for i := 0 to AWorkbookSeries.Count-1 do
begin
style := AChartStyles.Add;
datapointStyle := AWorkbookSeries.DataPointStyles[i];
if datapointstyle = nil then
begin
UpdateChartBrush(AWorkbookSeries.Chart, AWorkbookSeries.Fill, style.brush);
UpdateChartPen(AWorkbookSeries.Chart, AWorkbookSeries.Line, style.Pen);
end else
begin
UpdateChartBrush(AWorkbookSeries.Chart, datapointStyle.Background, style.Brush);
UpdateChartPen(AWorkbookSeries.Chart, datapointStyle.Border, style.Pen);
end;
end;
end;
(*
{$ifdef DATAPOINT_STYLES}
procedure TsWorkbookChartLink.UseDatapointStyles(AWorkbookSeries: TsChartSeries;
AChartSeries: TChartSeries);
@ -2708,7 +2796,8 @@ begin
style := styles.Add;
if dps = nil then
begin
UpdateChartBrush(AWorkbookSeries.Chart, AWorkbookSeries.FILL, style.brush);
UpdateChartBrush(AWorkbookSeries.Chart, AWorkbookSeries.Fill, style.brush);
UpdateChartPen(AWorkbookSeries.Chart, AWorkbookSeries.Line, style.Pen);
end else
begin
UpdateChartBrush(AWorkbookSeries.Chart, dps.Background, style.Brush);
@ -2724,7 +2813,7 @@ begin
TBarSeries(AChartSeries).Styles := styles;
end;
{$endif}
*)
{$ENDIF}
end.