fpspreadsheet: Support number formatting in charts.

git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@9008 8e941d3f-bd1b-0410-a28a-d453659cc2b4
This commit is contained in:
wp_xxyyzz
2023-11-03 22:36:00 +00:00
parent c4d733bd08
commit b66f052dc4
4 changed files with 135 additions and 12 deletions

View File

@ -49,6 +49,7 @@ begin
ser.LabelSeparator := '\n'; // this is the symbol for a line-break ser.LabelSeparator := '\n'; // this is the symbol for a line-break
ser.LabelPosition := lpOutside; ser.LabelPosition := lpOutside;
ser.Line.Color := scWhite; ser.Line.Color := scWhite;
ser.LabelFormat := '#,##0';
//ser.SetFillColorRange(4, 2, 8, 2); //ser.SetFillColorRange(4, 2, 8, 2);
b.WriteToFile('world-population.xlsx', true); // Excel fails to open the file b.WriteToFile('world-population.xlsx', true); // Excel fails to open the file

View File

@ -61,6 +61,7 @@ begin
ser.Regression.Equation.Border.Color := scRed; ser.Regression.Equation.Border.Color := scRed;
ser.Regression.Equation.Fill.Style := fsSolidFill; ser.Regression.Equation.Fill.Style := fsSolidFill;
ser.Regression.Equation.Fill.FgColor := scSilver; ser.Regression.Equation.Fill.FgColor := scSilver;
ser.Regression.Equation.NumberFormat := '0.000';
//ser.Regression.Equation.Top := 5; //ser.Regression.Equation.Top := 5;
//ser.Regression.Equation.Left := 5; //ser.Regression.Equation.Left := 5;

View File

@ -132,6 +132,7 @@ type
FCaptionRotation: Integer; FCaptionRotation: Integer;
FLabelFont: TsFont; FLabelFont: TsFont;
FLabelFormat: String; FLabelFormat: String;
FLabelFormatPercent: String;
FLabelRotation: Integer; FLabelRotation: Integer;
FLogarithmic: Boolean; FLogarithmic: Boolean;
FMajorInterval: Double; FMajorInterval: Double;
@ -158,6 +159,7 @@ type
property Inverted: Boolean read FInverted write FInverted; property Inverted: Boolean read FInverted write FInverted;
property LabelFont: TsFont read FLabelFont write FLabelFont; property LabelFont: TsFont read FLabelFont write FLabelFont;
property LabelFormat: String read FLabelFormat write FLabelFormat; property LabelFormat: String read FLabelFormat write FLabelFormat;
property LabelFormatPercent: String read FLabelFormatPercent write FLabelFormatPercent;
property LabelRotation: Integer read FLabelRotation write FLabelRotation; property LabelRotation: Integer read FLabelRotation write FLabelRotation;
property Logarithmic: Boolean read FLogarithmic write FLogarithmic; property Logarithmic: Boolean read FLogarithmic write FLogarithmic;
property MajorGridLines: TsChartLine read FMajorGridLines write FMajorGridLines; property MajorGridLines: TsChartLine read FMajorGridLines write FMajorGridLines;
@ -593,6 +595,8 @@ begin
FLabelFont.Style := []; FLabelFont.Style := [];
FLabelFont.Color := scBlack; FLabelFont.Color := scBlack;
FLabelFormatPercent := '0%';
FCaptionRotation := 0; FCaptionRotation := 0;
FLabelRotation := 0; FLabelRotation := 0;

View File

@ -18,7 +18,9 @@ type
private private
FSCharts: array of TStream; FSCharts: array of TStream;
FSObjectStyles: array of TStream; FSObjectStyles: array of TStream;
FNumberFormatList: TStrings;
FPointSeparatorSettings: TFormatSettings; FPointSeparatorSettings: TFormatSettings;
function GetChartAxisStyleAsXML(Axis: TsChartAxis; AIndent, AStyleID: Integer): String; function GetChartAxisStyleAsXML(Axis: TsChartAxis; AIndent, AStyleID: Integer): String;
function GetChartBackgroundStyleAsXML(AChart: TsChart; AFill: TsChartFill; function GetChartBackgroundStyleAsXML(AChart: TsChart; AFill: TsChartFill;
ABorder: TsChartLine; AIndent: Integer; AStyleID: Integer): String; ABorder: TsChartLine; AIndent: Integer; AStyleID: Integer): String;
@ -38,6 +40,9 @@ type
function GetChartRegressionStyleAsXML(AChart: TsChart; ASeriesIndex, AIndent, AStyleID: Integer): String; function GetChartRegressionStyleAsXML(AChart: TsChart; ASeriesIndex, AIndent, AStyleID: Integer): String;
function GetChartSeriesStyleAsXML(AChart: TsChart; ASeriesIndex, AIndent, AStyleID: integer): String; function GetChartSeriesStyleAsXML(AChart: TsChart; ASeriesIndex, AIndent, AStyleID: integer): String;
// function GetChartTitleStyleAsXML(AChart: TsChart; AStyleIndex, AIndent: Integer): String; // function GetChartTitleStyleAsXML(AChart: TsChart; AStyleIndex, AIndent: Integer): String;
function GetNumberFormatID(ANumFormat: String): String;
procedure ListAllNumberFormats(AChart: TsChart);
procedure PrepareChartTable(AChart: TsChart; AWorksheet: TsBasicWorksheet); procedure PrepareChartTable(AChart: TsChart; AWorksheet: TsBasicWorksheet);
protected protected
@ -63,6 +68,7 @@ type
public public
constructor Create(AWriter: TsBasicSpreadWriter); override; constructor Create(AWriter: TsBasicSpreadWriter); override;
destructor Destroy; override;
procedure AddChartsToZip(AZip: TZipper); procedure AddChartsToZip(AZip: TZipper);
procedure AddToMetaInfManifest(AStream: TStream); procedure AddToMetaInfManifest(AStream: TStream);
procedure CreateStreams; override; procedure CreateStreams; override;
@ -119,6 +125,36 @@ begin
Result := Result + Format('_%.2x_', [ord(AName[i])]); Result := Result + Format('_%.2x_', [ord(AName[i])]);
end; end;
{------------------------------------------------------------------------------}
{ internal number formats }
{------------------------------------------------------------------------------}
type
TsChartNumberFormatList = class(TStringList)
public
constructor Create;
function Add(const ANumFormat: String): Integer; override;
end;
constructor TsChartNumberFormatList.Create;
begin
inherited;
Add(''); // default number format
end;
// Adds a new format, but make sure to avoid duplicates.
function TsChartNumberFormatList.Add(const ANumFormat: String): Integer;
begin
if (ANumFormat = '') and (Count > 0) then
Result := 0
else
begin
Result := IndexOf(ANumFormat);
if Result = -1 then
Result := inherited Add(ANumFormat);
end;
end;
{------------------------------------------------------------------------------} {------------------------------------------------------------------------------}
{ TsSpreadOpenDocChartWriter } { TsSpreadOpenDocChartWriter }
@ -127,8 +163,17 @@ end;
constructor TsSpreadOpenDocChartWriter.Create(AWriter: TsBasicSpreadWriter); constructor TsSpreadOpenDocChartWriter.Create(AWriter: TsBasicSpreadWriter);
begin begin
inherited Create(AWriter); inherited Create(AWriter);
FPointSeparatorSettings := SysUtils.DefaultFormatSettings; FPointSeparatorSettings := SysUtils.DefaultFormatSettings;
FPointSeparatorSettings.DecimalSeparator:='.'; FPointSeparatorSettings.DecimalSeparator:='.';
FNumberFormatList := TsChartNumberFormatList.Create;
end;
destructor TsSpreadOpenDocChartWriter.Destroy;
begin
FNumberFormatList.Free;
inherited;
end; end;
procedure TsSpreadOpenDocChartWriter.AddChartsToZip(AZip: TZipper); procedure TsSpreadOpenDocChartWriter.AddChartsToZip(AZip: TZipper);
@ -201,6 +246,7 @@ var
chart: TsChart; chart: TsChart;
indent: String; indent: String;
angle: Integer; angle: Integer;
idx: Integer;
textProps: String = ''; textProps: String = '';
graphProps: String = ''; graphProps: String = '';
chartProps: String = ''; chartProps: String = '';
@ -212,9 +258,11 @@ begin
chart := Axis.Chart; chart := Axis.Chart;
// Special number format for numerical axis labels // Get number format, use percent format for stacked percentage axis
if (Axis = chart.YAxis) and (chart.StackMode = csmStackedPercentage) then if (Axis = chart.YAxis) and (chart.StackMode = csmStackedPercentage) then
numStyle := 'N10010'; numStyle := GetNumberFormatID(Axis.LabelFormatPercent)
else
numStyle := GetNumberFormatID(Axis.LabelFormat);
// Show axis labels // Show axis labels
if Axis.ShowLabels then if Axis.ShowLabels then
@ -537,6 +585,7 @@ function TsSpreadOpenDocChartWriter.GetChartRegressionEquationStyleAsXML(
AChart: TsChart; AEquation: TsRegressionEquation; AIndent, AStyleID: Integer): String; AChart: TsChart; AEquation: TsRegressionEquation; AIndent, AStyleID: Integer): String;
var var
indent: String; indent: String;
idx: Integer;
numStyle: String = 'N0'; numStyle: String = 'N0';
chartprops: String = ''; chartprops: String = '';
lineprops: String = ''; lineprops: String = '';
@ -547,9 +596,7 @@ begin
indent := DupeString(' ', AIndent); indent := DupeString(' ', AIndent);
// TO DO: Create chart number style list and find the current style there! numStyle := GetNumberFormatID(AEquation.NumberFormat);
if not AEquation.DefaultNumberFormat then
numStyle := 'N0';
if not AEquation.DefaultXName then if not AEquation.DefaultXName then
chartprops := chartprops + Format('loext:regression-x-name="%s" ', [AEquation.XName]); chartprops := chartprops + Format('loext:regression-x-name="%s" ', [AEquation.XName]);
@ -647,6 +694,7 @@ var
series: TsChartSeries; series: TsChartSeries;
lineser: TsLineSeries = nil; lineser: TsLineSeries = nil;
indent: String; indent: String;
numStyle: String;
chartProps: String = ''; chartProps: String = '';
graphProps: String = ''; graphProps: String = '';
textProps: String = ''; textProps: String = '';
@ -659,8 +707,12 @@ begin
indent := DupeString(' ', AIndent); indent := DupeString(' ', AIndent);
series := AChart.Series[ASeriesIndex]; series := AChart.Series[ASeriesIndex];
// Number format
numStyle := GetNumberFormatID(series.LabelFormat);
// Chart properties // Chart properties
chartProps := 'chart:symbol-type="none" '; chartProps := 'chart:symbol-type="none" ';
if (series is TsLineSeries) and (series.ChartType <> ctFilledRadar) then if (series is TsLineSeries) and (series.ChartType <> ctFilledRadar) then
begin begin
lineser := TsLineSeries(series); lineser := TsLineSeries(series);
@ -672,9 +724,7 @@ begin
); );
end; end;
chartProps := chartProps + 'chart:link-data-style-to-source="true" '; chartProps := chartProps + Format('chart:link-data-style-to-source="%s" ', [FALSE_TRUE[numStyle = 'N0']]);
// to do: link-data-style-to-source must go to "false" in case of a specific
// numeric label format. But that requires a number-style in the Object/style.xml
if ([cdlValue, cdlPercentage] * series.DataLabels = [cdlValue]) then if ([cdlValue, cdlPercentage] * series.DataLabels = [cdlValue]) then
chartProps := chartProps + 'chart:data-label-number="value" ' chartProps := chartProps + 'chart:data-label-number="value" '
@ -729,15 +779,63 @@ begin
textProps := TsSpreadOpenDocWriter(Writer).WriteFontStyleXMLAsString(series.LabelFont); textProps := TsSpreadOpenDocWriter(Writer).WriteFontStyleXMLAsString(series.LabelFont);
Result := Format( Result := Format(
indent + '<style:style style:name="ch%d" style:family="chart" style:data-style-name="N0">' + LE + indent + '<style:style style:name="ch%d" style:family="chart" style:data-style-name="%s">' + LE +
indent + chartProps + LE + indent + chartProps + LE +
indent + ' <style:graphic-properties %s/>' + LE + indent + ' <style:graphic-properties %s/>' + LE +
indent + ' <style:text-properties %s/>' + LE + indent + ' <style:text-properties %s/>' + LE +
indent + '</style:style>' + LE, indent + '</style:style>' + LE,
[ AStyleID, graphProps, textProps ] [ AStyleID, numstyle, graphProps, textProps ]
); );
end; end;
function TsSpreadOpenDocChartWriter.GetNumberFormatID(ANumFormat: String): String;
var
idx: Integer;
begin
idx := FNumberFormatList.IndexOf(ANumFormat);
if idx > -1 then
Result := Format('N%d', [idx])
else
Result := 'N0';
end;
procedure TsSpreadOpenDocChartWriter.ListAllNumberFormats(AChart: TsChart);
var
i: Integer;
series: TsChartSeries;
regression: TsChartRegression;
begin
FNumberFormatList.Clear;
// Formats of axis labels
FNumberFormatList.Add(AChart.XAxis.LabelFormat);
FNumberFormatList.Add(AChart.YAxis.LabelFormat);
FNumberFormatList.Add(AChart.X2Axis.LabelFormat);
FNumberFormatList.Add(AChart.Y2Axis.LabelFormat);
if AChart.StackMode = csmStackedPercentage then
begin
FNumberFormatList.Add(AChart.YAxis.LabelFormatPercent);
FNumberFormatList.Add(AChart.Y2Axis.LabelFormatPercent);
end;
// Formats of series labels
for i := 0 to AChart.Series.Count-1 do
begin
series := AChart.Series[i];
FNumberFormatList.Add(series.LabelFormat);
// Format of fit equation
if (series is TsScatterSeries) then begin
regression := TsScatterSeries(series).Regression;
if (regression.RegressionType <> rtNone) and
(regression.DisplayEquation or regression.DisplayRSquare) then
begin
FNumberFormatList.Add(regression.Equation.NumberFormat);
end;
end;
end;
end;
{ Extracts the cells needed by the given chart from the chart's worksheet and { Extracts the cells needed by the given chart from the chart's worksheet and
copies their values into a temporary worksheet, AWorksheet, so that these copies their values into a temporary worksheet, AWorksheet, so that these
data can be written to the xml immediately. data can be written to the xml immediately.
@ -936,11 +1034,10 @@ var
styleStream: TMemoryStream; styleStream: TMemoryStream;
styleID: Integer; styleID: Integer;
begin begin
// FChartStyleList.Clear;
chartStream := TMemoryStream.Create; chartStream := TMemoryStream.Create;
styleStream := TMemoryStream.Create; styleStream := TMemoryStream.Create;
try try
ListAllNumberFormats(AChart);
WriteChartNumberStyles(styleStream, 4, AChart); WriteChartNumberStyles(styleStream, 4, AChart);
styleID := 1; styleID := 1;
@ -1210,9 +1307,28 @@ procedure TsSpreadOpenDocChartWriter.WriteChartNumberStyles(AStream: TStream;
AIndent: Integer; AChart: TsChart); AIndent: Integer; AChart: TsChart);
var var
indent: String; indent: String;
numFmtName: String;
numFmtStr: String;
numFmtXML: String;
i: Integer;
parser: TsSpreadOpenDocNumFormatParser;
begin begin
indent := DupeString(' ', AIndent); indent := DupeString(' ', AIndent);
for i := 0 to FNumberFormatList.Count-1 do begin
numFmtName := Format('N%d', [i]);
numFmtStr := FNumberFormatList[i];
parser := TsSpreadOpenDocNumFormatParser.Create(numFmtStr, FWriter.Workbook.FormatSettings);
try
numFmtXML := parser.BuildXMLAsString(numFmtName);
if numFmtXML <> '' then
AppendToStream(AStream, indent + numFmtXML);
finally
parser.Free;
end;
end;
{
AppendToStream(AStream, AppendToStream(AStream,
indent + '<number:number-style style:name="N0">' + LE + indent + '<number:number-style style:name="N0">' + LE +
indent + ' <number:number number:min-integer-digits="1"/>' + LE + indent + ' <number:number number:min-integer-digits="1"/>' + LE +
@ -1226,6 +1342,7 @@ begin
indent + ' <number:text>%</number:text>' + LE + indent + ' <number:text>%</number:text>' + LE +
indent + '</number:percentage-style>' + LE indent + '</number:percentage-style>' + LE
); );
}
end; end;
{ Writes the file "Object N/styles.xml" (N = 1, 2, ...) which is needed by the { Writes the file "Object N/styles.xml" (N = 1, 2, ...) which is needed by the