fpspreadsheet: Radar series support in xlsx chart reader/writer. Fix some issues with radar series in ods.

git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@9222 8e941d3f-bd1b-0410-a28a-d453659cc2b4
This commit is contained in:
wp_xxyyzz 2024-02-07 17:36:41 +00:00
parent 45ec507997
commit 76c0221e9e
7 changed files with 196 additions and 112 deletions

View File

@ -51,11 +51,6 @@
<DebugInfoType Value="dsDwarf3"/>
</Debugging>
</Linking>
<Other>
<ConfigFile>
<WriteConfigFilePath Value=""/>
</ConfigFile>
</Other>
</CompilerOptions>
<Debugging>
<Exceptions>

View File

@ -12,8 +12,13 @@ var
book: TsWorkbook;
sheet: TsWorksheet;
ch: TsChart;
ser: TsChartSeries;
ser: TsRadarSeries;
fn, dir: String;
begin
dir := ExtractFilePath(ParamStr(0)) + 'files/';
ForceDirectories(dir);
fn := dir + FILE_NAME;
book := TsWorkbook.Create;
try
// worksheet
@ -51,25 +56,29 @@ begin
ser.SetLabelRange(3, 0, 10, 0);
ser.SetYRange(3, 1, 10, 1);
ser.Line.Color := scDarkRed;
ser.Fill.Color := scRed;
ser.Fill.Transparency := 0.35;
//ser.Fill.Style := cfsNoFill; // --> non-filled radar chrt
ser.ShowSymbols := true;
ser.Symbol := cssDiamond;
ser.SymbolFill.Style := cfsSolid;
ser.SymbolFill.Color := scYellow;
// in ods the symbol color is always equal to the line color
ser.SymbolWidth := 12; //3;
ser.SymbolHeight := 12; // 3;
// Add 2nd radar series ("Student 2")
ser := TsRadarSeries.Create(ch);
ser := TsFilledRadarSeries.Create(ch);
ser.SetTitleAddr(2, 2);
ser.SetLabelRange(3, 0, 10, 0);
ser.SetYRange(3, 2, 10, 2);
ser.Line.Color := scDarkBlue;
ser.Fill.Color := scBlue;
ser.Fill.Color := $FFCC99;
ser.Fill.Transparency := 0.35;
{
book.WriteToFile(FILE_NAME + '.xlsx', true); // Excel fails to open the file
WriteLn('Data saved with chart in ', FILE_NAME, '.xlsx');
}
book.WriteToFile(fn + '.xlsx', true);
WriteLn('Data saved with chart in ', fn + '.xlsx');
book.WriteToFile(FILE_NAME + '.ods', true);
WriteLn('Data saved with chart in ', FILE_NAME, '.ods');
book.WriteToFile(fn + '.ods', true);
WriteLn('Data saved with chart in ', fn + '.ods');
finally
book.Free;
end;

View File

@ -6,7 +6,7 @@ interface
uses
Classes, SysUtils,
LCLVersion, Forms, Controls, Graphics, Dialogs, ExtCtrls, StdCtrls,
LCLVersion, Forms, Controls, Graphics, Dialogs, ExtCtrls, StdCtrls, FileUtil,
TAGraph, TASources,
fpSpreadsheet, fpsTypes, fpsOpenDocument, xlsxOOXML,
fpSpreadsheetCtrls, fpSpreadsheetGrid, fpSpreadsheetChart;
@ -35,6 +35,7 @@ type
procedure FormCreate(Sender: TObject);
procedure sWorkbookSource1Error(Sender: TObject; const AMsg: String);
private
FDir: String;
sChartLink: TsWorkbookChartLink;
procedure LoadFile(AFileName: String);
@ -114,13 +115,27 @@ procedure TForm1.ComboBox1CloseUp(Sender: TObject);
begin
if ComboBox1.ItemIndex > -1 then
begin
Combobox1.Text := Combobox1.Items[Combobox1.ItemIndex];
Combobox1.Text := FDir + Combobox1.Items[Combobox1.ItemIndex];
LoadFile(Combobox1.Text);
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
var
L: TStrings;
i: Integer;
begin
FDir := ExpandFileName(Application.Location + '../../../other/chart/files/');
L := TStringList.Create;
try
FindAllFiles(L, FDir, '*.xlsx;*.ods', false);
for i := 0 to L.Count-1 do
L[i] := ExtractFileName(L[i]);
Combobox1.Items.Assign(L);
finally
L.Free;
end;
{$IF LCL_FullVersion >= 2020000}
ComboBox1.TextHint := 'Enter or select file name';
{$IFEND}

View File

@ -619,13 +619,12 @@ type
FSymbolWidth: Double; // in mm
FShowLines: Boolean;
FShowSymbols: Boolean;
FBorder: TsChartLine;
function GetSymbolFill: TsChartFill;
procedure SetSymbolFill(Value: TsChartFill);
FSymbolBorder: TsChartLine;
FSymbolFill: TsChartFill;
protected
property Symbol: TsChartSeriesSymbol read FSymbol write FSymbol;
property SymbolBorder: TsChartLine read FBorder write FBorder;
property SymbolFill: TsChartFill read GetSymbolFill write SetSymbolFill;
property SymbolBorder: TsChartLine read FSymbolBorder write FSymbolBorder;
property SymbolFill: TsChartFill read FSymbolFill write FSymbolFill;
property SymbolHeight: double read FSymbolHeight write FSymbolHeight;
property SymbolWidth: double read FSymbolWidth write FSymbolWidth;
property ShowLines: Boolean read FShowLines write FShowLines;
@ -666,18 +665,15 @@ type
end;
TsRadarSeries = class(TsLineSeries)
protected
function GetChartType: TsChartType; override;
end;
{
TsRingSeries = class(TsPieSeries)
private
FInnerRadiusPercent: Integer;
public
constructor Create(AChart: TsChart); override;
property InnerRadiusPercent: Integer read FInnerRadiusPercent write FInnerRadiusPercent;
end;
}
TsFilledRadarSeries = class(TsRadarSeries)
public
constructor Create(AChart: TsChart); override;
end;
TsCustomScatterSeries = class(TsCustomLineSeries)
public
constructor Create(AChart: TsChart); override;
@ -2490,27 +2486,22 @@ begin
FShowSymbols := false;
FShowLines := true;
FBorder := TsChartLine.Create;
FBorder.Style := clsSolid;
FBorder.Width := PtsToMM(DEFAULT_CHART_LINEWIDTH);
FBorder.Color := scBlack;
FSymbolBorder := TsChartLine.Create;
FSymbolBorder.Style := clsSolid;
FSymbolBorder.Width := PtsToMM(DEFAULT_CHART_LINEWIDTH);
FSymbolBorder.Color := scBlack;
FSymbolFill := TsChartFill.Create;
FSymbolFill.Style := cfsNoFill;
end;
destructor TsCustomLineSeries.Destroy;
begin
FBorder.Free;
FSymbolBorder.Free;
FSymbolFill.Free;
inherited;
end;
function TsCustomLineSeries.GetSymbolFill: TsChartFill;
begin
Result := FFill;
end;
procedure TsCustomLineSeries.SetSymbolFill(Value: TsChartFill);
begin
FFill := Value;
end;
{ TsPieSeries }
constructor TsPieSeries.Create(AChart: TsChart);
@ -2545,23 +2536,21 @@ end;
{ TsRadarSeries }
function TsRadarSeries.GetChartType: TsChartType;
begin
if Fill.Style <> cfsNoFill then
Result := ctFilledRadar
else
Result := ctRadar;
end;
(*
{ TsRingSeries }
constructor TsRingSeries.Create(AChart: TsChart);
constructor TsRadarSeries.Create(AChart: TsChart);
begin
inherited Create(AChart);
FChartType := ctRing;
FLine.Color := scBlack;
FInnerRadiusPercent := 50;
end; *)
FChartType := ctRadar;
FFill.Style := cfsNoFill; // to make the series default to ctRadar rather than ctFilledRadar
end;
{ TsFilledRadarSeries }
constructor TsFilledRadarSeries.Create(AChart: TsChart);
begin
inherited Create(AChart);
FChartType := ctFilledRadar;
Fill.Style := cfsSolid;
end;
{ TsTrendlineEquation }

View File

@ -1491,20 +1491,18 @@ begin
series := TsBarSeries.Create(AChart);
'chart:bubble':
series := TsBubbleSeries.Create(AChart);
// 'chart:circle':
// series := TsPieSeries.Create(AChart);
'chart:filled-radar':
series := TsRadarSeries.Create(AChart);
'chart:line':
series := TsLineSeries.Create(AChart);
'chart:radar':
series := TsRadarSeries.Create(AChart);
'chart:circle':
begin
series := TsPieSeries.Create(AChart);
if FChartType = ctRing then
TsPieSeries(series).InnerRadiusPercent := 50;
end;
'chart:line':
series := TsLineSeries.Create(AChart);
'chart:radar':
series := TsRadarSeries.Create(AChart);
'chart:filled-radar':
series := TsFilledRadarSeries.Create(AChart);
'chart:scatter':
series := TsScatterSeries.Create(AChart);
// 'chart:stock': --- has already been created
@ -1650,7 +1648,14 @@ begin
if ASeries.ChartType in [ctBar] then
ASeries.Line.Style := clsSolid;
GetChartLineProps(AStyleNode, AChart, ASeries.Line);
GetChartFillProps(AStyleNode, AChart, ASeries.Fill);
if ((ASeries is TsRadarSeries) and (ASeries.ChartType = ctRadar)) or (ASeries is TsCustomLineSeries) then
begin
// In ods, symbols and lines have the same color
TsRadarSeries(ASeries).SymbolFill.Style := cfsSolid;
TsRadarSeries(ASeries).SymbolFill.Color := ASeries.Line.Color;
TsRadarSeries(ASeries).SymbolBorder.Style := clsNoLine;
end else
GetChartFillProps(AStyleNode, AChart, ASeries.Fill);
end;
'style:text-properties':
GetChartTextProps(AStyleNode, ASeries.LabelFont);
@ -1739,10 +1744,10 @@ begin
TsOpenedCustomLineSeries(ASeries).Symbol := css;
break;
end;
s := GetAttrValue(AStyleNode, 'symbol-width');
s := GetAttrValue(AStyleNode, 'chart:symbol-width');
if (s <> '') and EvalLengthStr(s, value, rel) then
TsOpenedCustomLineSeries(ASeries).SymbolWidth := value;
s := GetAttrValue(AStyleNode, 'symbol-height');
s := GetAttrValue(AStyleNode, 'chart:symbol-height');
if (s <> '') and EvalLengthStr(s, value, rel) then
TsOpenedCustomLineSeries(ASeries).SymbolHeight := value;
end else
@ -2607,7 +2612,7 @@ begin
ciStepCenterY: interpolationStr := 'chart:interpolation="step-center-y" ';
end;
if not (AChart.GetChartType in [ctRadar, ctPie]) then
if not (AChart.GetChartType in [ctRadar, ctFilledRadar, ctPie]) then
rightAngledAxes := 'chart:right-angled-axes="true" ';
for i := 0 to AChart.Series.Count-1 do
@ -2784,7 +2789,7 @@ function TsSpreadOpenDocChartWriter.GetChartSeriesStyleAsXML(AChart: TsChart;
ASeriesIndex, AIndent, AStyleID: Integer): String;
var
series: TsChartSeries;
lineser: TsLineSeries = nil;
lineser: TsOpenedCustomLineSeries = nil;
indent: String;
numStyle: String;
forceNoLine: Boolean = false;
@ -2809,7 +2814,7 @@ begin
if ((series is TsLineSeries) and (series.ChartType <> ctFilledRadar)) or
(series is TsScatterSeries) then
begin
lineser := TsLineSeries(series);
lineser := TsOpenedCustomLineSeries(series);
if lineser.ShowSymbols then
chartProps := Format(
'chart:symbol-type="named-symbol" chart:symbol-name="%s" chart:symbol-width="%.1fmm" chart:symbol-height="%.1fmm" ',
@ -2870,9 +2875,10 @@ begin
// Graphic properties
lineProps := GetChartLineStyleGraphicPropsAsXML(AChart, series.Line, forceNoLine);
fillProps := GetChartFillStyleGraphicPropsAsXML(AChart, series.Fill);
if (series is TsLineSeries) and (series.ChartType <> ctFilledRadar) then
begin
lineSer := TsOpenedCustomLineSeries(series);
fillProps := GetChartFillStyleGraphicPropsAsXML(AChart, lineser.SymbolFill);
if lineSer.ShowSymbols then
graphProps := graphProps + fillProps;
if lineSer.ShowLines and (lineser.Line.Style <> clsNoLine) then
@ -2880,7 +2886,10 @@ begin
else
graphProps := graphProps + 'draw:stroke="none" ';
end else
begin
fillProps := GetChartFillStyleGraphicPropsAsXML(AChart, series.Fill);
graphProps := fillProps + lineProps;
end;
// Text properties
textProps := TsSpreadOpenDocWriter(Writer).WriteFontStyleXMLAsString(series.LabelFont);

View File

@ -102,7 +102,7 @@ type
procedure WriteChartAxisTitle(AStream: TStream; AIndent: Integer; Axis: TsChartAxis);
procedure WriteChartLegendNode(AStream: TStream; AIndent: Integer; ALegend: TsChartLegend);
procedure WriteChartPlotAreaNode(AStream: TStream; AIndent: Integer; AChart: TsChart);
procedure WriteChartRegression(AStream: TStream; AIndent: Integer; ASeries: TsChartSeries);
procedure WriteChartTrendline(AStream: TStream; AIndent: Integer; ASeries: TsChartSeries);
procedure WriteChartSeriesDatapointLabels(AStream: TStream; AIndent: Integer; ASeries: TsChartSeries);
procedure WriteChartSeriesDatapointStyles(AStream: TStream; AIndent: Integer; ASeries: TsChartSeries);
procedure WriteChartSeriesNode(AStream: TStream; AIndent: Integer; ASeries: TsChartSeries; ASeriesIndex: Integer);
@ -114,6 +114,7 @@ type
procedure WriteBarSeries(AStream: TStream; AIndent: Integer; ASeries: TsBarSeries; ASeriesIndex: Integer);
procedure WriteBubbleSeries(AStream: TStream; AIndent: Integer; ASeries: TsBubbleSeries; ASeriesIndex: Integer);
procedure WritePieSeries(AStream: TStream; AIndent: Integer; ASeries: TsPieSeries; ASeriesIndex: Integer);
procedure WriteRadarSeries(AStream: TStream; AIndent: Integer; ASeries: TsRadarSeries; ASeriesIndex: Integer);
procedure WriteScatterSeries(AStream: TStream; AIndent: Integer; ASeries: TsScatterSeries; ASeriesIndex: Integer);
procedure WriteChartLabels(AStream: TStream; AIndent: Integer; AFont: TsFont);
@ -1395,7 +1396,7 @@ begin
if TryStrToInt(s, n) then
with TsOpenedCustomLineSeries(ASeries) do
begin
SymbolWidth := PtsToMM(n div 2);
SymbolWidth := PtsToMM(n);
SymbolHeight := SymbolWidth;
end;
@ -1550,28 +1551,47 @@ end;
-------------------------------------------------------------------------------}
procedure TsSpreadOOXMLChartReader.ReadChartRadarSeries(ANode: TDOMNode; AChart: TsChart);
var
nodeName: String;
node: TDOMNode;
nodeName, s: String;
ser: TsRadarSeries;
radarStyle: String = '';
filled: Boolean = false;
begin
if ANode = nil then
exit;
while Assigned(ANode) do
// At first, we need the value of c:radarStyle because it determines the
// series class to be created.
node := ANode;
while Assigned(node) do
begin
nodeName := ANode.NodeName;
nodeName := node.NodeName;
case nodeName of
'c:radarStyle':
radarStyle := GetAttrValue(ANode, 'val');
'c:ser':
begin
ser := TsRadarSeries.Create(AChart);
ReadChartSeriesProps(ANode.FirstChild, ser);
if radarStyle <> 'filled' then
ser.Fill.Style := cfsNoFill;
s := GetAttrValue(node, 'val');
filled := s = 'filled';
end;
end;
ANode := ANode.NextSibling;
node := node.NextSibling;
end;
// Search the series node. Then create the series and read its properties
// from the subnodes.
node := ANode;
while Assigned(node) do
begin
nodeName := node.NodeName;
case nodeName of
'c:ser':
begin
if filled then
ser := TsFilledRadarSeries.Create(AChart)
else
ser := TsRadarSeries.Create(AChart);
ReadChartSeriesProps(node.FirstChild, ser);
end;
end;
node := node.NextSibling;
end;
end;
@ -3827,6 +3847,7 @@ var
markerStr: String;
chart: TsChart;
ser: TsOpenedCustomLineSeries;
symbolSizePts: Integer;
begin
indent := DupeString(' ', AIndent);
chart := ASeries.Chart;
@ -3835,7 +3856,7 @@ begin
if ser.ShowSymbols then
case ser.Symbol of
cssRect: markerStr := 'square';
cssDiamond: markerStr := 'diamong';
cssDiamond: markerStr := 'diamond';
cssTriangle: markerStr := 'triangle';
cssTriangleDown: markerStr := 'triangle'; // !!!!
cssTriangleLeft: markerStr := 'triangle'; // !!!!
@ -3852,13 +3873,16 @@ begin
else
markerStr := 'none';
symbolSizePts := round(mmToPts((ser.SymbolWidth + ser.SymbolHeight)/2));
if symbolSizePts > 72 then symbolSizePts := 72;
Result := Format(
indent + '<c:symbol val="%s"/>' + LE +
indent + '<c:size val="%.0f"/>' + LE +
indent + '<c:size val="%d"/>' + LE +
indent + '<c:spPr>' + LE +
GetChartFillAndLineXML(AIndent + 2, chart, ser.SymbolFill, ser.SymbolBorder) + LE +
indent + '</c:spPr>',
[ markerStr, mmToPts(ser.SymbolWidth + ser.SymbolHeight) ]
[ markerStr, symbolSizePts ]
);
end;
@ -3903,6 +3927,8 @@ begin
end;
ctPie, ctRing:
WritePieSeries(AStream, AIndent + 2, TsPieSeries(ser), i);
ctRadar, ctFilledRadar:
WriteRadarSeries(AStream, AIndent + 2, TsRadarSeries(ser), i);
ctScatter:
begin
WriteScatterSeries(AStream, AIndent + 2, TsScatterSeries(ser), i);
@ -3972,6 +3998,39 @@ begin
);
end;
procedure TsSpreadOOXMLChartWriter.WriteRadarSeries(AStream: TStream;
AIndent: Integer; ASeries: TsRadarSeries; ASeriesIndex: Integer);
var
indent: String;
chart: TsChart;
radarStyle: String;
begin
indent := DupeString(' ', AIndent);
chart := ASeries.Chart;
if ASeries.ChartType = ctFilledRadar then
radarStyle := 'filled'
else
radarStyle := 'marker';
AppendToStream(AStream,
indent + '<c:radarChart>' + LE +
indent + ' <c:radarStyle val="' + radarStyle + '"/>' + LE
);
WriteChartSeriesNode(AStream, AIndent + 4, ASeries, ASeriesIndex);
AppendToStream(AStream, Format(
indent + ' <c:axId val="%d"/>' + LE +
indent + ' <c:axId val="%d"/>' + LE +
indent + '</c:radarChart>' + LE,
[
FAxisID[chart.XAxis.Alignment], // <c:axId>
FAxisID[chart.YAxis.Alignment] // <c:axId>
]
));
end;
procedure TsSpreadOOXMLChartWriter.WriteScatterSeries(AStream: TStream;
AIndent: Integer; ASeries: TsScatterSeries; ASeriesIndex: Integer);
var
@ -4015,7 +4074,7 @@ end;
Writes the <c:trendline> node for the specified chart series if a trendline
is activated.
-------------------------------------------------------------------------------}
procedure TsSpreadOOXMLChartWriter.WriteChartRegression(AStream: TStream;
procedure TsSpreadOOXMLChartWriter.WriteChartTrendline(AStream: TStream;
AIndent: Integer; ASeries: TsChartSeries);
var
indent: String;
@ -4182,9 +4241,6 @@ var
xRng, yRng: TsChartRange;
forceNoLine: Boolean;
xValName, yValName, xRefName, yRefName: String;
explosionStr: String = '';
dps: TsChartDataPointStyle;
i: Integer;
begin
indent := DupeString(' ', AIndent);
chart := ASeries.Chart;
@ -4217,15 +4273,24 @@ begin
indent + ' </c:spPr>' + LE
);
end else
// Line & scatter series: symbol markers
// Line & scatter & radar series: symbol markers
if (ASeries is TsCustomLineSeries) then
begin
forceNoLine := not TsOpenedCustomLineSeries(ASeries).ShowLines;
AppendToStream(AStream,
indent + ' <c:spPr>' + LE +
GetChartLineXML(AIndent + 4, chart, ASeries.Line, forceNoLine) + LE +
indent + ' </c:spPr>' + LE
);
if (ASeries.ChartType = ctFilledRadar) then
AppendToStream(AStream,
indent + ' <c:spPr>' + LE +
GetChartFillAndLineXML(AIndent + 4, chart, ASeries.Fill, ASeries.Line) + LE +
indent + ' </c:spPr>' + LE
)
else
begin
forceNoLine := not TsOpenedCustomLineSeries(ASeries).ShowLines;
AppendToStream(AStream,
indent + ' <c:spPr>' + LE +
GetChartLineXML(AIndent + 4, chart, ASeries.Line, forceNoLine) + LE +
indent + ' </c:spPr>' + LE
);
end;
if TsOpenedCustomLineSeries(ASeries).ShowSymbols then
AppendToStream(AStream,
indent + ' <c:marker>' + LE +
@ -4242,7 +4307,7 @@ begin
// Trend line
if ASeries.SupportsTrendline then
WriteChartRegression(AStream, AIndent + 2, ASeries);
WriteChartTrendline(AStream, AIndent + 2, ASeries);
// Data point labels
WriteChartSeriesDatapointLabels(AStream, AIndent + 2, ASeries);

View File

@ -550,10 +550,11 @@ begin
if FIntegerX then
value := trunc(value);
end else
if FCyclicX then
value := AIndex / FPointsNumber * TWO_PI
else
value := AIndex;
// For polar series we rescale the x values to a full circle.
// And the angle begins at the 90° position.
if FCyclicX then
value := value / FPointsNumber * TWO_PI + pi/2;
FCurItem.SetX(i, value);
end;
@ -2651,8 +2652,8 @@ begin
UpdateChartBrush(AWorkbookSeries.Chart, openedWorkbookSeries.SymbolFill, seriesPointer.Brush);
UpdateChartPen(AWorkbookSeries.Chart, openedWorkbookSeries.SymbolBorder, seriesPointer.Pen);
seriesPointer.Style := POINTER_STYLES[openedWorkbookSeries.Symbol];
seriesPointer.HorizSize := mmToPx(openedWorkbookSeries.SymbolWidth, ppi);
seriesPointer.VertSize := mmToPx(openedWorkbookSeries.SymbolHeight, ppi);
seriesPointer.HorizSize := mmToPx(openedWorkbookSeries.SymbolWidth / 2, ppi);
seriesPointer.VertSize := mmToPx(openedWorkbookSeries.SymbolHeight / 2, ppi);
// Error bars
UpdateChartErrorBars(AWorkbookSeries, AChartSeries);
@ -2694,14 +2695,15 @@ begin
(AChartSeries.Source as TsWorkbookChartSource).CyclicX := true;
UpdateChartPen(AWorkbookSeries.Chart, AWorkbookSeries.Line, AChartSeries.LinePen);
UpdateChartBrush(AWorkbookSeries.Chart, AWorkbookSeries.Fill, AChartSeries.Brush);
if AWorkbookSeries.ChartType = ctFilledRadar then
UpdateChartBrush(AWorkbookSeries.Chart, AWorkbookSeries.Fill, AChartSeries.Brush);
if AWorkbookSeries.ShowSymbols then
begin
UpdateChartBrush(AWorkbookSeries.Chart, AWorkbookSeries.SymbolFill, AChartSeries.Pointer.Brush);
UpdateChartPen(AWorkbookSeries.Chart, AWorkbookSeries.SymbolBorder, AChartSeries.Pointer.Pen);
AChartSeries.Pointer.Style := POINTER_STYLES[AWorkbookSeries.Symbol];
AChartSeries.Pointer.HorizSize := mmToPx(AWorkbookSeries.SymbolWidth, ppi);
AChartSeries.Pointer.VertSize := mmToPx(AWorkbookSeries.SymbolHeight, ppi);
AChartSeries.Pointer.HorizSize := mmToPx(AWorkbookSeries.SymbolWidth / 2, ppi);
AChartSeries.Pointer.VertSize := mmToPx(AWorkbookSeries.SymbolHeight / 2, ppi);
end;
FChart.LeftAxis.Minors.Clear;