fpspreadsheet: Chart link supports series marks, and pie series. Add related missing properties to ods reader/writer.

git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@9038 8e941d3f-bd1b-0410-a28a-d453659cc2b4
This commit is contained in:
wp_xxyyzz
2023-11-22 22:08:47 +00:00
parent 71f5985598
commit 6946f27d34
4 changed files with 227 additions and 15 deletions

View File

@ -35,7 +35,9 @@ implementation
{$R *.lfm}
const
FILE_NAME = '../../../other/chart/bars.ods';
// FILE_NAME = '../../../other/chart/bars.ods';
// FILE_NAME = '../../../other/chart/area.ods';
FILE_NAME = '../../../other/chart/pie.ods';
{ TForm1 }

View File

@ -288,7 +288,7 @@ type
end;
TsChartAxisLink = (alPrimary, alSecondary);
TsChartDataLabel = (cdlValue, cdlPercentage, cdlValueAndPercentage, cdlCategory, cdlSeriesName, cdlSymbol);
TsChartDataLabel = (cdlValue, cdlPercentage, cdlCategory, cdlSeriesName, cdlSymbol);
TsChartDataLabels = set of TsChartDataLabel;
TsChartLabelPosition = (lpDefault, lpOutside, lpInside, lpCenter);
@ -299,6 +299,8 @@ type
FYRange: TsChartRange;
FFillColorRange: TsChartRange;
FLineColorRange: TsChartRange;
FLabelBackground: TsChartFill;
FLabelBorder: TsChartLine;
FLabelRange: TsChartRange;
FLabelFont: TsFont;
FLabelPosition: TsChartLabelPosition;
@ -340,6 +342,8 @@ type
property Count: Integer read GetCount;
property DataLabels: TsChartDataLabels read FDataLabels write FDataLabels;
property FillColorRange: TsChartRange read FFillColorRange write FFillColorRange;
property LabelBackground: TsChartFill read FLabelBackground write FLabelBackground;
property LabelBorder: TsChartLine read FLabelBorder write FLabelBorder;
property LabelFont: TsFont read FLabelFont write FLabelFont;
property LabelFormat: String read FLabelFormat write FLabelFormat; // Number format in Excel notation, e.g. '0.00'
property LabelPosition: TsChartLabelPosition read FLabelPosition write FLabelPosition;
@ -405,8 +409,11 @@ type
end;
TsPieSeries = class(TsChartSeries)
private
FStartAngle: Integer; // degrees
public
constructor Create(AChart: TsChart); override;
property StartAngle: Integer read FStartAngle write FStartAngle;
end;
TsRadarSeries = class(TsLineSeries)
@ -415,8 +422,11 @@ type
end;
TsRingSeries = class(TsChartSeries)
private
FInnerRadiusPercent: Integer;
public
constructor Create(AChart: TsChart); override;
property InnerRadiusPercent: Integer read FInnerRadiusPercent write FInnerRadiusPercent;
end;
TsRegressionType = (rtNone, rtLinear, rtLogarithmic, rtExponential, rtPower, rtPolynomial);
@ -1070,6 +1080,16 @@ begin
FLabelFont := TsFont.Create;
FLabelFont := TsFont.Create;
FLabelFont.Size := 9;
FLabelBorder := TsChartLine.Create;
FLabelBorder.Color := scBlack;
FLabelBorder.Style := clsNoLine;
FLabelBackground := TsChartFill.Create;
FLabelBackground.Color := scWhite;
FLabelBackground.Style := cfsNoFill;
FLabelSeparator := ' ';
end;
destructor TsChartSeries.Destroy;
@ -1353,6 +1373,7 @@ constructor TsPieSeries.Create(AChart: TsChart);
begin
inherited Create(AChart);
FChartType := ctPie;
FStartAngle := 90;
FLine.Color := scBlack;
end;
@ -1373,6 +1394,7 @@ begin
inherited Create(AChart);
FChartType := ctRing;
FLine.Color := scBlack;
FInnerRadiusPercent := 50;
end;

View File

@ -23,6 +23,7 @@ type
FChartFiles: TStrings;
FPointSeparatorSettings: TFormatSettings;
FNumberFormatList: TStrings;
FPieSeriesStartAngle: Integer;
function FindStyleNode(AStyleNodes: TDOMNode; AStyleName: String): TDOMNode;
procedure GetChartFillProps(ANode: TDOMNode; AChart: TsChart; AFill: TsChartFill);
procedure GetChartLineProps(ANode: TDOMNode; AChart: TsChart; ALine: TsChartLine);
@ -316,6 +317,8 @@ begin
FChartFiles := TStringList.Create;
FNumberFormatList := TsChartNumberFormatList.Create;
FNumberFormatList.NameValueSeparator := ':';
FPieSeriesStartAngle := 999;
end;
destructor TsSpreadOpenDocChartReader.Destroy;
@ -709,6 +712,8 @@ procedure TsSpreadOpenDocChartReader.ReadChartBackgroundStyle(AStyleNode: TDOMNo
var
nodeName: String;
begin
AElement.Border.Style := clsNoLine;
nodeName := AStyleNode.NodeName;
AStyleNode := AStyleNode.FirstChild;
while AStyleNode <> nil do begin
@ -905,7 +910,10 @@ begin
AChart.StackMode := csmStacked;
s := GetAttrValue(AStyleNode, 'chart:percentage');
if s = 'true' then
Achart.StackMode := csmStackedPercentage;
AChart.StackMode := csmStackedPercentage;
s := GetAttrValue(AStyleNode, 'chart:angle-offset');
if s <> '' then
FPieSeriesStartAngle := StrToInt(s);
end;
end;
AStyleNode := AStyleNode.NextSibling;
@ -1185,6 +1193,9 @@ begin
s := GetAttrValue(ANode, 'chart:style-name');
styleNode := FindStyleNode(AStyleNode, s);
ReadChartSeriesStyle(styleNode, AChart, series);
if (series is TsPieSeries) and (FPieSeriesStartAngle <> 999) then
TsPieSeries(series).StartAngle := FPieSeriesStartAngle;
end;
procedure TsSpreadOpenDocChartReader.ReadChartSeriesStyle(AStyleNode: TDOMNode;
@ -1195,6 +1206,8 @@ var
css: TsChartSeriesSymbol;
value: Double;
rel: Boolean;
dataLabels: TsChartDataLabels = [];
childNode1, childNode2, childNode3: TDOMNode;
begin
nodeName := AStyleNode.NodeName;
s := GetAttrValue(AStyleNode, 'style:data-style-name');
@ -1217,6 +1230,72 @@ begin
TsSpreadOpenDocReader(Reader).ReadFont(AStyleNode, ASeries.LabelFont);
'style:chart-properties':
begin
s := GetAttrValue(AStyleNode, 'chart:label-position');
case s of
'outside': ASeries.LabelPosition := lpOutside;
'inside': ASeries.LabelPosition := lpInside;
'center': ASeries.LabelPosition := lpCenter;
end;
s := GetAttrValue(AStyleNode, 'loext:label-stroke-color');
if s <> '' then
ASeries.LabelBorder.Color := HTMLColorStrToColor(s);
s := GetAttrValue(AStyleNode, 'loext:label-stroke');
case s of
'none': ASeries.LabelBorder.Style := clsNoLine;
else ASeries.LabelBorder.Style := clsSolid;
end;
s := GetAttrValue(AStyleNode, 'chart:data-label-number');
if s <> '' then
Include(datalabels, cdlValue);
s := GetAttrValue(AStyleNode, 'chart:data-label-number="percentage"');
if s <> '' then
Include(datalabels, cdlPercentage);
s := GetAttrValue(AStyleNode, 'chart:data-label-number="value-and-percentage"');
if s <> '' then
dataLabels := dataLabels + [cdlValue, cdlPercentage];
s := GetAttrValue(AStyleNode, 'chart:data-label-text');
if s = 'true' then
Include(dataLabels, cdlCategory);
s := GetAttrValue(AStyleNode, 'chart:data-label-series');
if s = 'true' then
Include(dataLabels, cdlSeriesName);
s := GetAttrValue(AStyleNode, 'chart:data-label-symbol');
if s = 'true' then
Include(dataLabels, cdlSymbol);
ASeries.DataLabels := dataLabels;
childNode1 := AStyleNode.FirstChild;
while childNode1 <> nil do
begin
nodeName := childNode1.NodeName;
if nodeName = 'chart:label-separator' then
begin
childNode2 := childNode1.FirstChild;
while childNode2 <> nil do
begin
nodeName := childNode2.NodeName;
if nodeName = 'text:p' then
begin
childNode3 := childNode2.FirstChild;
while childNode3 <> nil do
begin
nodeName := childNode3.NodeName;
if nodeName = 'text:line-break' then
begin
ASeries.LabelSeparator := LineEnding;
break;
end;
childNode3 := childNode3.NextSibling;
end;
end;
childNode2 := childNode2.NextSibling;
end;
end;
childNode1 := childNode1.NextSibling;
end;
if (ASeries is TsLineSeries) then
begin
s := GetAttrValue(AStyleNode, 'chart:symbol-name');
@ -1914,6 +1993,7 @@ var
verticalStr: String = '';
stackModeStr: String = '';
rightAngledAxes: String = '';
startAngleStr: String = '';
begin
indent := DupeString(' ', AIndent);
@ -1926,6 +2006,9 @@ begin
csmStackedPercentage: stackModeStr := 'chart:percentage="true" ';
end;
if (AChart.Series.Count > 0) and (AChart.Series[0] is TsPieSeries) then
startAngleStr := Format('chart:angle-offset="%.0f" ', [TsPieSeries(AChart.Series[0]).StartAngle]);
case AChart.Interpolation of
ciLinear: ;
ciCubicSpline: interpolationStr := 'chart:interpolation="cubic-spline" ';
@ -1945,6 +2028,7 @@ begin
interpolationStr +
verticalStr +
stackModeStr +
startAngleStr +
'chart:symbol-type="automatic" ' +
'chart:include-hidden-cells="false" ' +
'chart:auto-position="true" ' +
@ -2122,6 +2206,12 @@ begin
' </chart:label-separator>' + LE;
end;
if series.LabelBorder.Style <> clsNoLine then
begin
chartProps := chartProps + 'loext:label-stroke="solid" ';
chartProps := chartProps + 'loext:label-stroke-color="' + ColorToHTMLColorStr(series.LabelBorder.Color) + '"'
end;
if labelSeparator <> '' then
chartProps := ' <style:chart-properties ' + chartProps + '>' + LE + labelSeparator + ' </style:chart-properties>'
else

View File

@ -17,14 +17,17 @@ unit fpspreadsheetchart;
interface
uses
Classes, SysUtils, LResources, Forms, Controls, Graphics, Dialogs,
// TChart
TATypes, TATextElements, TAChartUtils, TACustomSource, TACustomSeries, TASeries,
TAChartAxisUtils, TAChartAxis, TALegend, TAGraph,
// FPSpreadsheet Visual
fpSpreadsheetCtrls, fpSpreadsheetGrid, fpsVisualUtils,
// RTL/FCL
Classes, SysUtils,
// LCL
LCLVersion, Forms, Controls, Graphics, Dialogs,
// TAChart
TATypes, TATextElements, TAChartUtils, TACustomSource, TACustomSeries,
TASeries, TARadialSeries, TAChartAxisUtils, TAChartAxis, TALegend, TAGraph,
// FPSpreadsheet
fpsTypes, fpSpreadsheet, fpsUtils, fpsChart;
fpsTypes, fpSpreadsheet, fpsUtils, fpsChart,
// FPSpreadsheet Visual
fpSpreadsheetCtrls, fpSpreadsheetGrid, fpsVisualUtils;
type
@ -108,6 +111,7 @@ type
procedure AddSeries(ASeries: TsChartSeries);
procedure FixAreaSeries(AWorkbookChart: TsChart);
procedure ClearChart;
procedure ConstructSeriesMarks(AWorkbookSeries: TsChartSeries; AChartSeries: TChartSeries);
function GetWorkbookChart: TsChart;
procedure UpdateChartAxis(AWorkbookAxis: TsChartAxis);
procedure UpdateChartAxisLabels(AWorkbookChart: TsChart);
@ -116,6 +120,8 @@ type
procedure UpdateChartBrush(AWorkbookFill: TsChartFill; ABrush: TBrush);
procedure UpdateChartLegend(AWorkbookLegend: TsChartLegend; ALegend: TChartLegend);
procedure UpdateChartPen(AWorkbookLine: TsChartLine; APen: TPen);
procedure UpdateChartPieSeries(AWorkbookSeries: TsChartSeries; APieSeries: TPieSeries);
procedure UpdateChartSeriesMarks(AWorkbookSeries: TsChartSeries; AChartSeries: TChartSeries);
procedure UpdateChartTitle(AWorkbookTitle: TsChartText; AChartTitle: TChartTitle);
public
@ -141,6 +147,9 @@ implementation
uses
Math;
type
TBasicPointSeriesOpener = class(TBasicPointSeries);
function mmToPx(mm: Double; ppi: Integer): Integer;
begin
Result := round(mmToIn(mm * ppi));
@ -677,11 +686,19 @@ begin
UpdateChartPen(ASeries.Line, TAreaSeries(ser).AreaContourPen);
TAreaSeries(ser).AreaLinesPen.Style := psClear;
end;
ctPie, ctRing:
begin
ser := TPieSeries.Create(FChart);
UpdateChartPieSeries(ASeries, TPieSeries(ser));
end;
end;
src.SetTitleAddr(ASeries.TitleAddr);
ser.Source := src;
ser.Title := src.Title;
ser.Transparency := round(ASeries.Fill.Transparency);
UpdateChartSeriesMarks(ASeries, ser);
FChart.AddSeries(ser);
end;
@ -729,6 +746,37 @@ begin
FChart.Foot.Text.Clear;
end;
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;
// Fix area series zero level not being clipped at chart's plotrect.
procedure TsWorkbookChartLink.FixAreaSeries(AWorkbookChart: TsChart);
var
@ -736,6 +784,7 @@ var
ser: TAreaSeries;
ext: TDoubleRect;
begin
{$IF LCL_FullVersion < 3990000}
if AWorkbookChart.GetChartType <> ctArea then
exit;
@ -750,6 +799,7 @@ begin
ser.ZeroLevel := ext.b.y;
ser.UseZeroLevel := true;
end;
{$ENDIF}
end;
function TsWorkbookChartLink.GetWorkbookChart: TsChart;
@ -888,8 +938,7 @@ begin
// Labels
Convert_sFont_to_Font(AWorkbookAxis.LabelFont, axis.Marks.LabelFont);
// if not AWorkbookAxis.CategoryRange.IsEmpty then --- fix me
// axis.Marks.Style := smsLabel;
axis.Marks.LabelFont.Orientation := round(AWorkbookAxis.LabelRotation * 10);
// Axis line
UpdateChartPen(AWorkbookAxis.AxisLine, axis.AxisPen);
@ -900,8 +949,8 @@ begin
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);
axis.TickColor := axis.Grid.Color;
axis.TickWidth := axis.Grid.Width;
axis.TickColor := axis.AxisPen.Color;
axis.TickWidth := axis.AxisPen.Width;
// Minor axis grid
if AWorkbookAxis.MinorGridLines.Style <> clsNoLine then
@ -912,7 +961,7 @@ begin
minorAxis.Intervals.Count := AWorkbookAxis.MinorCount;
minorAxis.TickLength := IfThen(catOutside in AWorkbookAxis.MinorTicks, 2, 0);
minorAxis.TickInnerLength := IfThen(catInside in AWorkbookAxis.MinorTicks, 2, 0);
minorAxis.TickColor := minorAxis.Grid.Color;
minorAxis.TickColor := axis.AxisPen.Color;
minorAxis.TickWidth := minorAxis.Grid.Width;
end;
@ -1060,6 +1109,55 @@ begin
end;
end;
procedure TsWorkbookChartLink.UpdateChartPieSeries(AWorkbookSeries: TsChartSeries;
APieSeries: TPieSeries);
begin
APieSeries.StartAngle := TsPieSeries(AWorkbookSeries).StartAngle;
APieSeries.Legend.Multiplicity := lmPoint;
APieSeries.Legend.Format := '%2:s';
if AWorkbookSeries is TsRingSeries then
APieSeries.InnerRadiusPercent := TsRingSeries(AWorkbookSeries).InnerRadiusPercent;
FChart.BottomAxis.Visible := false;
FChart.LeftAxis.Visible := false;
FChart.Legend.Inverted := false;
FChart.Frame.Visible := false;
end;
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);
UpdateChartBrush(AWorkbookSeries.LabelBackground, AChartSeries.Marks.LabelBrush);
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;