diff --git a/components/fpspreadsheet/examples/other/chart/write_chart_demo.lpr b/components/fpspreadsheet/examples/other/chart/write_chart_demo.lpr
index 14a0fae72..b14fae548 100644
--- a/components/fpspreadsheet/examples/other/chart/write_chart_demo.lpr
+++ b/components/fpspreadsheet/examples/other/chart/write_chart_demo.lpr
@@ -83,7 +83,7 @@ begin
ch.Background.Style := fsSolidFill;
ch.Border.Style := clsSolid;
ch.PlotArea.Background.Style := fsSolidFill;
- ch.RotatedAxes := true;
+ //ch.RotatedAxes := true;
ch.StackMode := csmStackedPercentage;
//ch.Interpolation := ciCubicSpline;
diff --git a/components/fpspreadsheet/laz_fpspreadsheet.lpk b/components/fpspreadsheet/laz_fpspreadsheet.lpk
index 51533001b..073db68c6 100644
--- a/components/fpspreadsheet/laz_fpspreadsheet.lpk
+++ b/components/fpspreadsheet/laz_fpspreadsheet.lpk
@@ -33,7 +33,7 @@
This package is all you need if you don't want graphical components (such as grids and charts)."/>
-
+
@@ -305,6 +305,10 @@ This package is all you need if you don't want graphical components (such a
+
+
+
+
diff --git a/components/fpspreadsheet/source/common/fpsopendocument.pas b/components/fpspreadsheet/source/common/fpsopendocument.pas
index 20f8fbf6e..d9edd41be 100644
--- a/components/fpspreadsheet/source/common/fpsopendocument.pas
+++ b/components/fpspreadsheet/source/common/fpsopendocument.pas
@@ -236,6 +236,8 @@ type
FHasColFormats: Boolean;
FHasRowFormats: Boolean;
+ FChartWriter: TsBasicSpreadChartWriter;
+
// Routines to write parts of files
procedure WriteAutomaticStyles(AStream: TStream);
procedure WriteCellRow(AStream: TStream; ASheet: TsBasicWorksheet;
@@ -275,7 +277,7 @@ type
function WriteDefaultGraphicStyleXMLAsString: String; overload;
function WriteDocumentProtectionXMLAsString: String;
function WriteFontStyleXMLAsString(const AFormat: TsCellFormat): String; overload;
- function WriteFontStyleXMLAsString(AFont: TsFont): String; overload;
+// function WriteFontStyleXMLAsString(AFont: TsFont): String; overload;
function WriteHeaderFooterFontXMLAsString(AFont: TsHeaderFooterFont): String;
function WriteHorAlignmentStyleXMLAsString(const AFormat: TsCellFormat): String;
function WriteNumFormatStyleXMLAsString(const AFormat: TsCellFormat): String;
@@ -288,50 +290,14 @@ type
function WriteVertAlignmentStyleXMLAsString(const AFormat: TsCellFormat): String;
function WriteWordwrapStyleXMLAsString(const AFormat: TsCellFormat): String;
- { Chart support }
- function GetChartAxisStyleAsXML(Axis: TsChartAxis; AIndent, AStyleID: Integer): String;
- function GetChartBackgroundStyleAsXML(AChart: TsChart; AFill: TsChartFill;
- ABorder: TsChartLine; AIndent: Integer; AStyleID: Integer): String;
- function GetChartCaptionStyleAsXML(AChart: TsChart; ACaptionKind, AIndent, AStyleID: Integer): String;
- function GetChartLegendStyleAsXML(AChart: TsChart; AIndent, AStyleID: Integer): String;
- function GetChartLineStyleAsXML(AChart: TsChart; ALine: TsChartLine; AIndent, AStyleID: Integer): String;
- function GetChartPlotAreaStyleAsXML(AChart: TsChart; AIndent, AStyleID: Integer): String;
- function GetChartSeriesStyleAsXML(AChart: TsChart; ASeriesIndex, AIndent, AStyleID: integer): String;
-// function GetChartTitleStyleAsXML(AChart: TsChart; AStyleIndex, AIndent: Integer): String;
-
- function GetChartFillStyleGraphicPropsAsXML(AChart: TsChart; AFill: TsChartFill): String;
- function GetChartLineStyleGraphicPropsAsXML(AChart: TsChart; ALine: TsChartLine): String;
-
- procedure PrepareChartTable(AChart: TsChart; AWorksheet: TsBasicWorksheet);
-
- procedure WriteChart(AStream: TStream; AChart: TsChart);
- procedure WriteChartAxis(AChartStream, AStyleStream: TStream;
- AChartIndent, AStyleIndent: Integer; Axis: TsChartAxis; var AStyleID: Integer);
- procedure WriteChartBackground(AChartStream, AStyleStream: TStream;
- AChartIndent, AStyleIndent: Integer; AChart: TsChart; var AStyleID: Integer);
- procedure WriteChartLegend(AChartStream, AStyleStream: TStream;
- AChartIndent, AStyleIndent: Integer; AChart: TsChart; var AStyleID: Integer);
- procedure WriteChartNumberStyles(AStream: TStream; AIndent: Integer; AChart: TsChart);
- procedure WriteChartPlotArea(AChartStream, AStyleStream: TStream;
- AChartIndent, AStyleIndent: Integer; AChart: TsChart; var AStyleID: Integer);
- procedure WriteChartSeries(AChartStream, AStyleStream: TStream;
- AChartIndent, AStyleIndent: Integer; AChart: TsChart; ASeriesIndex: Integer; var AStyleID: Integer);
-// procedure WriteChartStyles(AStream: TStream; AChart: TsChart; AIndent: Integer);
- procedure WriteChartTable(AStream: TStream; AChart: TsChart; AIndent: Integer);
- procedure WriteChartTitle(AChartStream, AStyleStream: TStream;
- AChartIndent, AStyleIndent: Integer; AChart: TsChart; IsSubtitle: Boolean; var AStyleID: Integer);
-// procedure WriteChartTitle(AStream: TStream; AChart: TsChart; IsSubtitle: Boolean; AIndent: Integer);
-
- procedure WriteObjectStyles(AStream: TStream; AChart: TsChart);
-
protected
FPointSeparatorSettings: TFormatSettings;
// Streams with the contents of files
FSMeta, FSSettings, FSStyles, FSContent: TStream;
FSMimeType, FSMetaInfManifest: TStream;
- FSCharts: array of TStream;
- FSObjectStyles: array of TStream;
+// FSCharts: array of TStream;
+// FSObjectStyles: array of TStream;
{ Helpers }
procedure AddBuiltinNumFormats; override;
@@ -355,12 +321,10 @@ type
procedure ResetStreams;
{ Routines to write those files }
- procedure WriteCharts;
procedure WriteContent;
procedure WriteMetaInfManifest;
procedure WriteMeta;
procedure WriteMimetype;
- procedure WriteObjectStyles;
procedure WriteSettings;
procedure WriteStyles;
procedure WriteWorksheet(AStream: TStream; ASheetIndex: Integer);
@@ -389,6 +353,9 @@ type
{ General writing methods }
procedure WriteStringToFile(AString, AFileName: string);
procedure WriteToStream(AStream: TStream; AParams: TsStreamParams = []); override;
+
+ // Needed by the chart writer
+ function WriteFontStyleXMLAsString(AFont: TsFont): String;
end;
procedure InitOpenDocLimitations(out ALimitations: TsSpreadsheetFormatLimitations);
@@ -404,14 +371,13 @@ uses
{$ENDIF}
StrUtils, Variants, LazFileUtils, URIParser, LazUTF8,
{%H-}fpsPatches,
- fpsStrings, fpsStreams, fpsCrypto, fpsClasses, fpspreadsheet,
- fpsExprParser, fpsImages, fpsConditionalFormat;
+ fpsStrings, fpsStreams, fpsCrypto, fpsClasses, fpSpreadsheet,
+ fpsExprParser, fpsImages, fpsConditionalFormat, fpsOpenDocumentChart;
const
LE = LineEnding;
{ OpenDocument general XML constants }
- XML_HEADER = '';
{ OpenDocument Directory structure constants }
OPENDOC_PATH_CONTENT = 'content.xml';
@@ -537,16 +503,6 @@ const
'5Rating', '5Quarters', '5Boxes' // is5Rating, is5Quarters, is5Boxes
);
- CHART_TYPE_NAMES: array[TsChartType] of string = (
- '', 'bar', 'line', 'area', 'barLine', 'scatter', 'bubble'
- );
-
- CHART_SYMBOL_NAMES: array[TsChartSeriesSymbol] of String = (
- 'square', 'diamond', 'arrow-up', 'arrow-down', 'arrow-left',
- 'arrow-right', 'circle', 'star', 'x', 'plus', 'asterisk'
- ); // unsupported: bow-tie, hourglass, horizontal-bar, vertical-bar
-
-
function CFOperandToStr(v: variant; AWorksheet: TsWorksheet;
const AFormatSettings: TFormatSettings): String;
var
@@ -590,18 +546,6 @@ begin
Result := vkNone;
end;
-function ASCIIName(AName: String): String;
-var
- i: Integer;
-begin
- Result := '';
- for i := 1 to Length(AName) do
- if AName[i] in ['a'..'z', 'A'..'Z', '0'..'9'] then
- Result := Result + AName[i]
- else
- Result := Result + Format('_%.2x_', [ord(AName[i])]);
-end;
-
type
{ Table style items stored in TableStyleList of the reader }
@@ -5748,8 +5692,6 @@ end;
{ Creates the streams for the individual data files. Will be zipped into a
single xlsx file. }
procedure TsSpreadOpenDocWriter.CreateStreams;
-var
- i, n: Integer;
begin
FSMeta := CreateTempStream(FWorkbook, 'fpsM');
FSSettings := CreateTempStream(FWorkbook, 'fpsS');
@@ -5757,37 +5699,20 @@ begin
FSContent := CreateTempStream(FWorkbook, 'fpsC');
FSMimeType := CreateTempStream(FWorkbook, 'fpsMT');
FSMetaInfManifest := CreateTempStream(FWorkbook, 'fpsMIM');
-
- n := TsWorkbook(FWorkbook).GetChartCount;
- SetLength(FSCharts, n);
- SetLength(FSObjectStyles, n);
- for i := 0 to n - 1 do
- begin
- FSCharts[i] := CreateTempStream(FWorkbook, 'fpsCh');
- FSObjectStyles[i] := CreateTempStream(FWorkbook, 'fpsOS');
- end;
-
+ FChartWriter.CreateStreams;
// FSSheets will be created when needed.
end;
{ Destroys the temporary streams that were created by the writer }
procedure TsSpreadOpenDocWriter.DestroyStreams;
-var
- i: Integer;
begin
+ FChartWriter.DestroyStreams;
DestroyTempStream(FSMeta);
DestroyTempStream(FSSettings);
DestroyTempStream(FSStyles);
DestroyTempStream(FSContent);
DestroyTempStream(FSMimeType);
DestroyTempStream(FSMetaInfManifest);
- for i := 0 to High(FSCharts) do
- begin
- DestroyTempStream(FSCharts[i]);
- DestroyTempStream(FSObjectStyles[i]);
- end;
- Setlength(FSCharts, 0);
- SetLength(FSObjectStyles, 0);
end;
procedure TsSpreadOpenDocWriter.GetHeaderFooterImageName(
@@ -5887,8 +5812,7 @@ begin
WriteSettings();
WriteStyles();
WriteContent;
- WriteObjectStyles;
- WriteCharts;
+ FChartWriter.WriteCharts;
{ Now compress the files }
FZip := TZipper.Create;
@@ -5901,13 +5825,7 @@ begin
FZip.Entries.AddFileEntry(FSMimetype, OPENDOC_PATH_MIMETYPE);
FZip.Entries.AddFileEntry(FSMetaInfManifest, OPENDOC_PATH_METAINF_MANIFEST);
ZipPictures(FZip);
- for i := 0 to TsWorkbook(FWorkbook).GetChartCount-1 do
- begin
- FZip.Entries.AddFileEntry(
- FSCharts[i], Format(OPENDOC_PATH_CHART_CONTENT, [i+1]));
- FZip.Entries.AddFileEntry(
- FSObjectStyles[i], Format(OPENDOC_PATH_CHART_STYLES, [i+1]));
- end;
+ TsSpreadOpenDocChartWriter(FChartWriter).AddChartsToZip(FZip);
ResetStreams;
@@ -6097,8 +6015,6 @@ end;
{ Is called before zipping the individual file parts. Rewinds the streams. }
procedure TsSpreadOpenDocWriter.ResetStreams;
-var
- i: Integer;
begin
FSMeta.Position := 0;
FSSettings.Position := 0;
@@ -6106,11 +6022,7 @@ begin
FSContent.Position := 0;
FSMimeType.Position := 0;
FSMetaInfManifest.Position := 0;
- for i := 0 to High(FSCharts) do
- begin
- FSCharts[i].Position := 0;
- FSObjectStyles[i].Position := 0;
- end;
+ FChartWriter.ResetStreams;
end;
{ Writes the node "office:automatic-styles". Although this node occurs in both
@@ -6501,32 +6413,6 @@ begin
'');
end;
-procedure TsSpreadOpenDocWriter.WriteCharts;
-var
- i: Integer;
- chart: TsChart;
-begin
- for i := 0 to TsWorkbook(FWorkbook).GetChartCount - 1 do
- begin
- chart := TsWorkbook(FWorkbook).GetChartByIndex(i);
- WriteChart(FSCharts[i], chart);
- end;
-end;
-
-{ Writes the Object X/styles.xml stream which is needed for chart support
- (sometimes) and contains the dash pattern definitions. }
-procedure TsSpreadOpenDocWriter.WriteObjectStyles;
-var
- i: Integer;
- chart: TsChart;
-begin
- for i := 0 to TsWorkbook(FWorkbook).GetChartCount - 1 do
- begin
- chart := TsWorkbook(FWorkbook).GetChartByIndex(i);
- WriteObjectStyles(FSObjectStyles[i], chart);
- end;
-end;
-
procedure TsSpreadOpenDocWriter.WriteContent;
var
i: Integer;
@@ -6754,1283 +6640,6 @@ begin
end;
end;
-{ Writes the chart to the specified stream.
- All chart elements are formatted by means of styles. To simplify assignment
- of styles to elements we first write the elements and create the style of
- the currently written chart element on the fly. Since styles must be written
- to the steam first, we write the chart elements to a separate stream which
- is appended to the main stream afterwards. }
-procedure TsSpreadOpenDocWriter.WriteChart(AStream: TStream; AChart: TsChart);
-var
- chartStream: TMemoryStream;
- styleStream: TMemoryStream;
- styleID: Integer;
-begin
-// FChartStyleList.Clear;
-
- chartStream := TMemoryStream.Create;
- styleStream := TMemoryStream.Create;
- try
- WriteChartNumberStyles(styleStream, 4, AChart);
-
- styleID := 1;
- WriteChartBackground(chartStream, styleStream, 6, 4, AChart, styleID);
- WriteChartTitle(chartStream, styleStream, 6, 4, AChart, false, styleID); // Title
- WriteChartTitle(chartStream, styleStream, 6, 4, AChart, true, styleID); // Subtitle
- WriteChartLegend(chartStream, styleStream, 6, 4, AChart, styleID); // Legend
- WriteChartPlotArea(chartStream, styleStream, 6, 4, AChart, styleID); // Wall, axes, series
-
- // Here begins the main stream
- AppendToStream(AStream,
- XML_HEADER + LE);
-
- AppendToStream(AStream,
- '' + LE
- );
-
- // The file begins with the chart styles
- AppendToStream(AStream,
- ' ' + LE
- );
-
- // Copy the styles from the temporary style stream.
- styleStream.Position := 0;
- AStream.CopyFrom(styleStream, stylestream.Size);
-
- // Now the chart part follows, after closing the styles part
- AppendToStream(AStream,
- ' ' + LE +
- ' ' + LE +
- ' ' + LE
- );
-
- // Copy the chart elements from the temporary chart stream
- chartStream.Position := 0;
- AStream.CopyFrom(chartStream, chartStream.Size);
-
- // After the chart elements we have the data to be plotted
- WriteChartTable(AStream, AChart, 8);
-
- // Finally the footer.
- AppendToStream(AStream,
- ' ' + LE +
- ' ' + LE +
- ' ' + LE +
- '');
- finally
- chartStream.Free;
- styleStream.Free;
- end;
-end;
-
-procedure TsSpreadOpenDocWriter.WriteChartAxis(AChartStream, AStyleStream: TStream;
- AChartIndent, AStyleIndent: Integer; Axis: TsChartAxis;
- var AStyleID: Integer);
-type
- TAxisKind = 3..6;
-const
- AXIS_ID: array[TAxisKind] of string = ('x', 'y', 'x', 'y');
- AXIS_LEVEL: array[TAxisKind] of string = ('primary', 'primary', 'secondary', 'secondary');
-var
- indent: String;
- captionKind: Integer;
- chart: TsChart;
- series: TsChartSeries;
- sheet: TsWorksheet;
- refStr: String;
- r1, c1, r2, c2: Cardinal;
-begin
- if not Axis.Visible then
- exit;
-
- chart := Axis.Chart;
-
- if Axis = chart.XAxis then
- captionKind := 3
- else if Axis = chart.YAxis then
- captionKind := 4
- else if Axis = chart.X2Axis then
- captionKind := 5
- else if Axis = chart.Y2Axis then
- captionKind := 6
- else
- raise Exception.Create('[WriteChartAxis] Unknown axis');
-
- // Write axis
- indent := DupeString(' ', AChartIndent);
- AppendToStream(AChartStream, Format(
- indent + '' + LE,
- [ AStyleID, AXIS_ID[captionKind], AXIS_LEVEL[captionKind], AXIS_ID[captionKind] ]
- ));
-
- if (Axis = chart.XAxis) and (not chart.IsScatterChart) and (chart.Series.Count > 0) then
- begin
- series := chart.Series[0];
- sheet := TsWorkbook(FWorkbook).GetWorksheetByIndex(chart.SheetIndex);
- r1 := series.LabelRange.Row1;
- c1 := series.LabelRange.Col1;
- r2 := series.LabelRange.Row2;
- c2 := series.LabelRange.Col2;
- refStr := GetSheetCellRangeString_ODS(sheet.Name, sheet.Name, r1, c1, r2, c2, rfAllRel, false);
- AppendToStream(AChartStream, Format(
- indent + ' ' + LE,
- [ refStr ]
- ));
- end;
-
- // Write axis style
- AppendToStream(AStyleStream,
- GetChartAxisStyleAsXML(Axis, AStyleIndent, AStyleID)
- );
-
- // Next style
- inc(AStyleID);
-
- // Axis title
- if Axis.ShowCaption and (Axis.Caption <> '') then
- begin
- AppendToStream(AChartStream, Format(
- indent + ' ' + LE +
- indent + ' %s' + LE +
- indent + ' ' + LE,
- [ AStyleID, Axis.Caption ]
- ));
-
- // Axis title style
- AppendToStream(AStyleStream,
- GetChartCaptionStyleAsXML(chart, captionKind, AStyleIndent, AStyleID)
- );
-
- // Next style
- inc(AStyleID);
- end;
-
- // Major grid lines
- if Axis.MajorGridLines.Style <> clsNoLine then
- begin
- AppendToStream(AChartStream, Format(
- indent + ' ' + LE,
- [ AStyleID ]
- ));
-
- // Major grid lines style
- AppendToStream(AStyleStream,
- GetChartLineStyleAsXML(chart, Axis.MajorGridLines, AStyleIndent, AStyleID)
- );
-
- // Next style
- inc(AStyleID);
- end;
-
- // Minor grid lines
- if Axis.MinorGridLines.Style <> clsNoLine then
- begin
- AppendToStream(AChartStream, Format(
- indent + ' ' + LE,
- [ AStyleID ]
- ));
-
- // Minor grid lines style
- AppendToStream(AStyleStream,
- GetChartLineStyleAsXML(chart, Axis.MinorGridLines, AStyleIndent, AStyleID)
- );
-
- // Next style
- inc(AStyleID);
- end;
-
- // Close the xml node
- AppendToStream(AChartStream,
- indent + '' + LE
- );
-end;
-
-{ Writes the chart's background to the xml stream }
-procedure TsSpreadOpenDocWriter.WriteChartBackground(AChartStream, AStyleStream: TStream;
- AChartIndent, AStyleIndent: Integer; AChart: TsChart; var AStyleID: Integer);
-var
- indent: String;
- chartClass: String;
-begin
- chartClass := CHART_TYPE_NAMES[AChart.GetChartType];
- if chartClass <> '' then
- chartClass := 'chart:class="chart:' + chartClass + '"';
-
- indent := DupeString(' ', AChartIndent);
- AppendToStream(AChartStream, Format(
- indent + '' + LE, [
- AStyleID,
- chartClass,
- AChart.Width, AChart.Height // Width, Height are in mm
- ], FPointSeparatorSettings
- ));
-
- AppendToStream(AStyleStream,
- GetChartBackgroundStyleAsXML(AChart, AChart.Background, AChart.Border, AStyleIndent, AStyleID)
- );
-
- inc(AStyleID);
-end;
-
-{ Writes the chart's legend to the xml stream }
-procedure TsSpreadOpenDocWriter.WriteChartLegend(AChartStream, AStyleStream: TStream;
- AChartIndent, AStyleIndent: Integer; AChart: TsChart; var AStyleID: Integer);
-const
- LEGEND_POSITION: array[TsChartLegendPosition] of string = (
- 'end', 'top', 'bottom', 'start'
- );
-var
- indent: String;
- canOverlap: String = '';
-begin
- if (not AChart.Legend.Visible) then
- exit;
-
- if AChart.Legend.CanOverlapPlotArea then
- canOverlap := 'loext:overlay="true" ';
-
- // Write legend properties
- indent := DupeString(' ', AChartIndent);
- AppendToStream(AChartStream, Format(
- indent + '' + LE,
- [ AStyleID, LEGEND_POSITION[AChart.Legend.Position], canOverlap ]
- ));
-
- // Write legend style
- AppendToStream(AStyleStream,
- GetChartLegendStyleAsXML(AChart, AStyleIndent, AStyleID)
- );
-
- // Next style
- inc(AStyleID);
-end;
-
-procedure TsSpreadOpenDocWriter.WriteChartNumberStyles(AStream: TStream;
- AIndent: Integer; AChart: TsChart);
-var
- indent: String;
-begin
- indent := DupeString(' ', AIndent);
-
- AppendToStream(AStream,
- indent + '' + LE +
- indent + ' ' + LE +
- indent + '' + LE
- );
-
- if AChart.StackMode = csmStackedPercentage then
- AppendToStream(AStream,
- indent + '' + LE +
- indent + ' ' + LE +
- indent + ' %' + LE +
- indent + '' + LE
- );
-end;
-
-procedure TsSpreadOpenDocWriter.WriteChartPlotArea(AChartStream, AStyleStream: TStream;
- AChartIndent, AStyleIndent: Integer; AChart: TsChart; var AStyleID: Integer);
-var
- indent: String;
- i: Integer;
-begin
- indent := DupeString(' ', AChartIndent);
-
- // Plot area properties
- // (ods has a table:cell-range-address here but it is reconstructed by Calc)
- AppendToStream(AChartStream, Format(
- indent + '' + LE,
- [ AStyleID ]
- ));
- // Plot area style
- AppendToStream(AStyleStream,
- GetChartPlotAreaStyleAsXML(AChart, AStyleIndent, AStyleID)
- );
-
- // Next style
- inc(AStyleID);
-
- // Wall properties
- AppendToStream(AChartStream, Format(
- indent + ' ' + LE,
- [ AStyleID ]
- ));
- // Wall style
- AppendToStream(AStyleStream,
- GetChartBackgroundStyleAsXML(AChart, AChart.PlotArea.Background, AChart.PlotArea.Border, AStyleIndent, AStyleID)
- );
-
- // Next style
- inc(AStyleID);
-
- // Floor properties
- AppendToStream(AChartStream, Format(
- indent + ' ' + LE,
- [ AStyleID ]
- ));
- // Floor style
- AppendToStream(AStyleStream,
- GetChartBackgroundStyleAsXML(AChart, AChart.Floor.Background, AChart.Floor.Border, AStyleIndent, AStyleID)
- );
-
- // Next style
- inc(AStyleID);
-
- // primary x axis
- WriteChartAxis(AChartStream, AStyleStream, AChartIndent+2, AStyleIndent, AChart.XAxis, AStyleID);
-
- // primary y axis
- WriteChartAxis(AChartStream, AStyleStream, AChartIndent+2, AStyleIndent, AChart.YAxis, AStyleID);
-
- // secondary x axis
- WriteChartAxis(AChartStream, AStyleStream, AChartIndent+2, AStyleIndent, AChart.X2Axis, AStyleID);
-
- // secondary y axis
- WriteChartAxis(AChartStream, AStyleStream, AChartIndent+2, AStyleIndent, AChart.Y2Axis, AStyleID);
-
- // series
- for i := 0 to AChart.Series.Count-1 do
- WriteChartSeries(AChartStream, AStyleStream, AChartIndent+2, AStyleIndent, AChart, i, AStyleID);
-
- // close xml node
- AppendToStream(AChartStream,
- indent + '' + LE
- );
-end;
-
-procedure TsSpreadOpenDocWriter.WriteChartSeries(AChartStream, AStyleStream: TStream;
- AChartIndent, AStyleIndent: Integer; AChart: TsChart; ASeriesIndex: Integer;
- var AStyleID: Integer);
-var
- indent: String;
- sheet: TsWorksheet;
- series: TsChartSeries;
- valuesRange: String;
- domainRangeX: String = '';
- domainRangeY: String = '';
- rangeStr: String = '';
- titleAddr: String;
- count: Integer;
-begin
- indent := DupeString(' ', AChartIndent);
-
- series := AChart.Series[ASeriesIndex];
- sheet := TsWorkbook(FWorkbook).GetWorksheetByIndex(AChart.sheetIndex);
-
- // These are the x values of a scatter or bubble plot.
- if (series is TsScatterSeries) or (series is TsBubbleSeries) then
- begin
- domainRangeX := GetSheetCellRangeString_ODS(
- sheet.Name, sheet.Name,
- series.XRange.Row1, series.XRange.Col1,
- series.XRange.Row2, series.XRange.Col2,
- rfAllRel, false
- );
- end;
-
- if series is TsBubbleSeries then
- begin
- domainRangeY := GetSheetCellRangeString_ODS(
- sheet.Name, sheet.Name,
- series.YRange.Row1, series.YRange.Col1,
- series.YRange.Row2, series.YRange.Col2,
- rfAllRel, false
- );
- // These are the bubble radii
- valuesRange := GetSheetCellRangeString_ODS(
- sheet.Name, sheet.Name,
- TsBubbleSeries(series).BubbleRange.Row1, TsBubbleSeries(series).BubbleRange.Col1,
- TsBubbleSeries(series).BubbleRange.Row2, TsBubbleSeries(series).BubbleRange.Col2,
- rfAllRel, false
- );
- end else
- // These are the y values of the non-bubble series
- valuesRange := GetSheetCellRangeString_ODS(
- sheet.Name, sheet.Name,
- series.YRange.Row1, series.YRange.Col1,
- series.YRange.Row2, series.YRange.Col2,
- rfAllRel, false
- );
-
- // And these are the data point labels.
- titleAddr := GetSheetCellRangeString_ODS(
- sheet.Name, sheet.Name,
- series.TitleAddr.Row, series.TitleAddr.Col,
- series.TitleAddr.Row, series.TitleAddr.Col,
- rfAllRel, false
- );
- count := series.YRange.Row2 - series.YRange.Row1 + 1;
-
- // Store the series properties
- AppendToStream(AChartStream, Format(
- indent + '' + LE,
- [ AStyleID, valuesRange, titleAddr, CHART_TYPE_NAMES[series.ChartType] ]
- ));
- if domainRangeY <> '' then
- AppendToStream(AChartStream, Format(
- indent + '' + LE,
- [ domainRangeY ]
- ));
- if domainRangeX <> '' then
- AppendToStream(AChartStream, Format(
- indent + '' + LE,
- [ domainRangeX ]
- ));
-
- AppendToStream(AChartStream, Format(
- indent + ' ' + LE,
- [ count ]
- ));
- AppendToStream(AChartStream,
- indent + '' + LE
- );
-
- // Series style
- AppendToStream(AStyleStream,
- GetChartSeriesStyleAsXML(AChart, ASeriesIndex, AStyleIndent, AStyleID)
- );
-
- // Next style
- inc(AStyleID);
-end;
-
-function TsSpreadOpenDocWriter.GetChartAxisStyleAsXML(
- Axis: TsChartAxis; AIndent, AStyleID: Integer): String;
-var
- chart: TsChart;
- indent: String;
- angle: Integer;
- textProps: String = '';
- graphProps: String = '';
- chartProps: String = '';
- numStyle: String = 'N0';
-begin
- Result := '';
- if not Axis.Visible then
- exit;
-
- chart := Axis.Chart;
-
- if (Axis = chart.YAxis) and (chart.StackMode = csmStackedPercentage) then
- numStyle := 'N10010';
-
- if Axis.ShowLabels then
- chartProps := chartProps + 'chart:display-label="true" ';
-
- if Axis.Logarithmic then
- chartProps := chartProps + 'chart:logarithmic="true" ';
-
- if Axis.Inverted then
- chartProps := chartProps + 'chart:reverse-direction="true" ';
-
- angle := Axis.LabelRotation;
- chartProps := chartProps + Format('style:rotation-angle="%d" ', [angle]);
-
- graphProps := 'svg:stroke-color="' + ColorToHTMLColorStr(Axis.AxisLine.Color) + '" ';
-
- textProps := WriteFontStyleXMLAsString(Axis.LabelFont);
-
- indent := DupeString(' ', AIndent);
- Result := Format(
- indent + '' + LE +
- indent + ' ' + LE +
- indent + ' ' + LE +
- indent + ' ' + LE +
- indent + '' + LE,
- [ AStyleID, numStyle, chartProps, graphProps, textProps ]
- );
-end;
-
-function TsSpreadOpenDocWriter.GetChartBackgroundStyleAsXML(
- AChart: TsChart; AFill: TsChartFill; ABorder: TsChartLine;
- AIndent, AStyleID: Integer): String;
-var
- indent: String;
- fillStr: String = '';
- borderStr: String = '';
-begin
- fillStr := GetChartFillStyleGraphicPropsAsXML(AChart, AFill);
- borderStr := GetChartLineStyleGraphicPropsAsXML(AChart, ABorder);
- indent := DupeString(' ', AIndent);
- Result := Format(
- indent + '' + LE +
- indent + ' ' + LE +
- indent + '' + LE,
- [ AStyleID, fillStr, borderStr ]
- );
-end;
-
-{
-
-
-
-
- ACaptionKind = 1 ---> Title
- ACaptionKind = 2 ---> SubTitle
- ACaptionKind = 3 ---> x xis
- ACaptionKind = 4 ---> y axis
- ACaptionKind = 5 ---> x2 axis
- ACaptionKind = 6 ---> y2 axis }
-function TsSpreadOpenDocWriter.GetChartCaptionStyleAsXML(AChart: TsChart;
- ACaptionKind, AIndent, AStyleID: Integer): String;
-var
- title: TsChartText;
- axis: TsChartAxis;
- font: TsFont;
- indent: String;
- rotAngle: Integer;
- chartProps: String = '';
- textProps: String = '';
-begin
- Result := '';
-
- case ACaptionKind of
- 1, 2:
- begin
- if ACaptionKind = 1 then title := AChart.Title else title := AChart.Subtitle;
- font := title.Font;
- rotAngle := title.RotationAngle;
- end;
- 3, 4, 5, 6:
- begin
- case ACaptionKind of
- 3: axis := AChart.XAxis;
- 4: axis := AChart.YAxis;
- 5: axis := AChart.X2Axis;
- 6: axis := AChart.Y2Axis;
- end;
- font := axis.CaptionFont;
- rotAngle := axis.CaptionRotation;
- if AChart.RotatedAxes then
- begin
- if rotAngle = 0 then rotAngle := 90 else if rotAngle = 90 then rotAngle := 0;
- end;
- end;
- else
- raise Exception.Create('[GetChartCaptionStyleAsXML] Unknown caption.');
- end;
-
- chartProps := 'chart:auto-position="true" ';
- chartProps := chartProps + Format('style:rotation-angle="%d" ', [rotAngle]);
-
- textProps := WriteFontStyleXMLAsString(font);
-
- indent := DupeString(' ', AIndent);
- Result := Format(
- indent + '' + LE +
- indent + ' ' + LE +
- indent + ' ' + LE +
- indent + '' + LE,
- [ AStyleID, chartProps, textProps ]
- );
-end;
-
-function TsSpreadOpenDocWriter.GetChartFillStyleGraphicPropsAsXML(AChart: TsChart;
- AFill: TsChartFill): String;
-var
- fillStr: String;
- fillColorStr: String;
-begin
- if AFill.Style = fsNoFill then
- begin
- Result := 'draw:fill="none" ';
- exit;
- end;
-
- // To do: extend with hatched and gradient fills
- fillStr := 'draw:fill="solid" ';
- fillColorStr := 'draw:fill-color="' + ColorToHTMLColorStr(AFill.FgColor) + '" ';
-
- Result := fillStr + fillColorStr;
-end;
-
-{
-
-
-
-
-
-}
-function TsSpreadOpenDocWriter.GetChartLegendStyleAsXML(AChart: TsChart;
- AIndent, AStyleID: Integer): String;
-var
- indent: String;
- textProps: String = '';
- graphProps: String = '';
-begin
- Result := '';
-
- if not AChart.Legend.Visible then
- exit;
-
- graphProps := GetChartLineStyleGraphicPropsAsXML(AChart, AChart.Legend.Border) +
- GetChartFillStyleGraphicPropsAsXML(AChart, AChart.Legend.Background);
-
- textProps := WriteFontStyleXMLAsString(AChart.Legend.Font);
-
- indent := DupeString(' ', AIndent);
- Result := Format(
- indent + '' + LE +
- indent + ' ' + LE +
- indent + ' ' + LE +
- indent + ' ' + LE +
- indent + '' + LE,
- [ AStyleID, graphProps, textProps ]
- );
-end;
-
-{
-
- }
-function TsSpreadOpenDocWriter.GetChartLineStyleAsXML(AChart: TsChart;
- ALine: TsChartLine; AIndent, AStyleID: Integer): String;
-var
- ind: String;
- graphProps: String = '';
-begin
- ind := DupeString(' ', AIndent);
- graphProps := GetChartLineStyleGraphicPropsAsXML(AChart, ALine);
- Result := Format(
- ind + '' + LE +
- ind + ' ' + LE +
- ind + '' + LE,
- [ AStyleID, graphProps ]
- );
-end;
-
-{ Constructs the xml for a line style to be used in the }
-function TsSpreadOpenDocWriter.GetChartLineStyleGraphicPropsAsXML(AChart: TsChart;
- ALine: TsChartLine): String;
-var
- strokeStr: String = '';
- widthStr: String = '';
- colorStr: String = '';
- s: String;
- linestyle: TsChartLineStyle;
-begin
- if ALine.Style = clsNoLine then
- begin
- Result := 'draw:stroke="none" ';
- exit;
- end;
-
- strokeStr := 'draw:stroke="solid" ';
- if (ALine.Style <> clsSolid) then
- begin
- linestyle := AChart.GetLineStyle(ALine.Style);
- if linestyle <> nil then
- strokeStr := 'draw:stroke="dash" draw:stroke-dash="' + ASCIIName(linestyle.Name) + '" ';
- end;
-
- if ALine.Width > 0 then
- widthStr := Format('svg:stroke-width="%.1fmm" ', [ALine.Width], FPointSeparatorSettings);
- colorStr := Format('svg:stroke-color="%s" ', [ColorToHTMLColorStr(ALine.Color)]);
-
- Result := strokeStr + widthStr + colorStr;
-end;
-
-function TsSpreadOpenDocWriter.GetChartPlotAreaStyleAsXML(AChart: TsChart;
- AIndent, AStyleID: Integer): String;
-var
- indent: String;
- interpolationStr: String = '';
- verticalStr: String = '';
- stackModeStr: String = '';
-begin
- indent := DupeString(' ', AIndent);
-
- if AChart.RotatedAxes then
- verticalStr := 'chart:vertical="true" ';
-
- case AChart.StackMode of
- csmSideBySide: ;
- csmStacked: stackModeStr := 'chart:stacked="true" ';
- csmStackedPercentage: stackModeStr := 'chart:percentage="true" ';
- end;
-
- case AChart.Interpolation of
- ciLinear: ;
- ciCubicSpline: interpolationStr := 'chart:interpolation="cubic-spline" ';
- ciBSpline: interpolationStr := 'chart:interpolation="b-spline" ';
- ciStepStart: interpolationStr := 'chart:interpolation="step-start" ';
- ciStepEnd: interpolationStr := 'chart:interpolation="step-end" ';
- ciStepCenterX: interpolationStr := 'chart:interpolation="step-center-x" ';
- ciStepCenterY: interpolationStr := 'chart:interpolation="step-center-y" ';
- end;
-
- Result := Format(
- indent + ' ', [ AStyleID ]) + LE +
- indent + ' ' + LE +
- indent + ' ' + LE;
-end;
-
-{
-
-
-
- }
-function TsSpreadOpenDocWriter.GetChartSeriesStyleAsXML(AChart: TsChart;
- ASeriesIndex, AIndent, AStyleID: Integer): String;
-var
- series: TsChartSeries;
- lineser: TsLineSeries = nil;
- indent: String;
- chartProps: String = '';
- graphProps: String = '';
- textProps: String = '';
- lineProps: String = '';
- fillProps: String = '';
-begin
- Result := '';
-
- indent := DupeString(' ', AIndent);
- series := AChart.Series[ASeriesIndex];
-
- // Chart properties
- chartProps := 'chart:symbol-type="none" ';
- if (series is TsLineSeries) then
- begin
- lineser := TsLineSeries(series);
- if lineser.ShowSymbols then
- chartProps := Format(
- 'chart:symbol-type="named-symbol" chart:symbol-name="%s" chart:symbol-width="%.1fmm" chart.symbol-height="%.1fmm" ',
- [CHART_SYMBOL_NAMES[lineSer.Symbol], lineSer.SymbolWidth, lineSer.SymbolHeight ],
- FPointSeparatorSettings
- );
- end;
- chartProps := chartProps + 'chart:link-data-style-to-source="true" ';
-
- // Graphic properties
- lineProps := GetChartLineStyleGraphicPropsAsXML(AChart, series.Line);
- fillProps := GetChartFillStyleGraphicPropsAsXML(AChart, series.Fill);
- if (series is TsLineSeries) then
- begin
- if lineSer.ShowSymbols then
- graphProps := graphProps + fillProps;
- if lineSer.ShowLines and (lineser.Line.Style <> clsNoLine) then
- graphProps := graphProps + lineProps;
- end else
- graphProps := fillProps + lineProps;
-
- // Text properties
- textProps := 'fo:font-size="10pt" style:font-size-asian="10pt" style:font-size-complex="10pt" ';
-// textProps := WriteFontStyleXMLAsString(font); // <--- to be completed. this is for the series labels.
-
- Result := Format(
- indent + '' + LE +
- indent + ' ' + LE +
- indent + ' ' + LE +
- indent + ' ' + LE +
- indent + '' + LE,
- [ AStyleID, chartProps, graphProps, textProps ]
- );
-end;
-
-{ 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
- data can be written to the xml immediately.
- Independently of the layout in the original worksheet, data are arranged in
- columns of AWorksheet, starting at cell A1.
- - First column: Categories (or index in case of scatter chart)
- - Second column:
- in case of category charts: y values of the first series,
- in case of scatter series: x values of the first series
- - Third column:
- in case of category charts: y values of the second series
- in case of scatter series. y values of the first series
- - etc.
- The first row contains
- - nothing in case of the first column
- - cell range reference in ODS syntax for the cells in the original worksheet.
- The aux worksheet should be contained in a separate workbook to avoid
- interfering with the writing process.
- }
-procedure TsSpreadOpenDocWriter.PrepareChartTable(AChart: TsChart;
- AWorksheet: TsBasicWorksheet);
-var
- isScatterChart: Boolean;
- series: TsChartSeries;
- seriesSheet: TsWorksheet;
- auxSheet: TsWorksheet;
- refStr, txt: String;
- i, j: Integer;
- srcCell, destCell: PCell;
- destCol: Cardinal;
- r1, c1, r2, c2: Cardinal;
- nRows: Integer;
-begin
- if AChart.Series.Count = 0 then
- exit;
-
- auxSheet := TsWorksheet(AWorksheet);
- seriesSheet := TsWorkbook(FWorkbook).GetWorksheetByIndex(AChart.SheetIndex);
- isScatterChart := AChart.IsScatterChart;
-
- // Determine the number of rows in auxiliary output worksheet.
- nRows := 0;
- for i := 0 to AChart.Series.Count-1 do
- begin
- series := AChart.Series[i];
- j := series.GetXCount;
- if j > nRows then nRows := j;
- j := series.GetYCount;
- if j > nRows then nRows := j;
- end;
-
- // Write label column. If missing, write consecutive numbers 1, 2, 3, ...
- destCol := 0;
- for i := 0 to AChart.Series.Count-1 do
- begin
- series := AChart.Series[i];
-
- // Write the label column. Use consecutive numbers 1, 2, 3, ... if there
- // are no labels.
- if series.HasLabels then
- begin
- r1 := series.LabelRange.Row1;
- c1 := series.LabelRange.Col1;
- r2 := series.LabelRange.Row2;
- c2 := series.LabelRange.Col2;
- refStr := GetSheetCellRangeString_ODS(seriesSheet.Name, seriesSheet.Name, r1, c1, r2, c2, rfAllRel, false);
- end else
- refStr := '';
-
- auxSheet.WriteText(0, destCol, '');
-
- for j := 1 to nRows do
- begin
- if series.HasLabels then
- begin
- if series.LabelsInCol then
- srcCell := seriesSheet.FindCell(r1 + j - 1, c1)
- else
- srcCell := seriesSheet.FindCell(r1, c1 + j - 1);
- end else
- srcCell := nil;
- if srcCell <> nil then
- begin
- destCell := auxsheet.GetCell(j, destCol);
- seriesSheet.CopyValue(srcCell, destCell);
- end else
- destCell := auxSheet.WriteNumber(j, destCol, j);
- end;
- if (refStr <> '') then
- auxsheet.WriteComment(1, destCol, refStr);
-
- // In case of scatter plot write the x column. Use consecutive numbers 1, 2, 3, ...
- // if there are no x values.
- if isScatterChart then
- begin
- inc(destCol);
-
- if series.HasXValues then
- begin
- r1 := series.XRange.Row1;
- c1 := series.XRange.Col1;
- r2 := series.XRange.Row2;
- c2 := series.XRange.Col2;
- refStr := GetSheetCellRangeString_ODS(seriesSheet.Name, seriesSheet.Name, r1, c1, r2, c2, rfAllRel, false);
- if series.XValuesInCol then
- txt := 'Col ' + GetColString(c1)
- else
- txt := 'Row ' + GetRowString(r1);
- end else
- begin
- refStr := '';
- txt := '';
- end;
-
- auxSheet.WriteText(0, destCol, txt);
-
- for j := 1 to nRows do
- begin
- if series.HasXValues then
- begin
- if series.XValuesInCol then
- srcCell := seriesSheet.FindCell(r1 + j - 1, c1)
- else
- srcCell := seriesSheet.FindCell(r1, c1 + j - 1);
- end else
- srcCell := nil;
- if srcCell <> nil then
- begin
- destCell := auxsheet.GetCell(j, destCol);
- seriesSheet.CopyValue(srcCell, destCell);
- end else
- destCell := auxSheet.WriteNumber(j, destCol, j);
- end;
- if (refStr <> '') then
- auxsheet.WriteComment(1, destCol, refStr);
- end;
-
- // Write the y column
- if not series.HasYValues then
- Continue;
-
- inc(destCol);
-
- r1 := series.TitleAddr.Row; // Series title
- c1 := series.TitleAddr.Col;
- txt := seriesSheet.ReadAsText(r1, c1);
- auxsheet.WriteText(0, destCol, txt);
- refStr := GetSheetCellRangeString_ODS(seriesSheet.Name, seriesSheet.Name, r1, c1, r1, c1, rfAllRel, false);
- if (refStr <> '') then
- auxSheet.WriteComment(0, destCol, refStr); // Store title reference as comment for svg node
-
- r1 := series.YRange.Row1;
- c1 := series.YRange.Col1;
- r2 := series.YRange.Row2;
- c2 := series.YRange.Col2;
- refStr := GetSheetCellRangeString_ODS(seriesSheet.Name, seriesSheet.Name, r1, c1, r2, c2, rfAllRel, false);
- for j := 1 to series.GetYCount do
- begin
- if series.YValuesInCol then
- srcCell := seriesSheet.FindCell(r1 + j - 1, c1)
- else
- srcCell := seriesSheet.FindCell(r1, c1 + j - 1);
- if srcCell <> nil then
- begin
- destCell := auxSheet.GetCell(j, destCol);
- seriesSheet.CopyValue(srcCell, destCell);
- end else
- destCell := auxSheet.WriteNumber(j, destCol, j);
- end;
-
- if (refStr <> '') then
- auxSheet.WriteComment(1, destCol, refStr); // Store y range reference as comment for svg node
- end;
-end;
-
-{ Writes the chart's data table. NOTE: The chart gets its data from this table
- rather than from the worksheet! }
-procedure TsSpreadOpenDocWriter.WriteChartTable(AStream: TStream; AChart: TsChart;
- AIndent: Integer);
-var
- auxBook: TsWorkbook;
- auxSheet: TsWorksheet;
-
- procedure WriteAuxCell(AIndent: Integer; ACell: PCell);
- var
- ind: String;
- valueType: String;
- value: String;
- officeValue: String;
- draw: String;
- begin
- ind := DupeString(' ', AIndent);
- if (ACell = nil) or (ACell^.ContentType = cctEmpty) then
- begin
- AppendToStream(AStream,
- ind + '' + LE +
- ind + ' ' + LE +
- ind + '' + LE
- );
- exit;
- end;
-
- case ACell^.ContentType of
- cctUTF8String:
- begin
- valueType := 'string';
- value := auxSheet.ReadAsText(ACell);
- officeValue := '';
- end;
- cctNumber, cctDateTime, cctBool:
- begin
- valueType := 'float';
- value := Format('%g', [auxSheet.ReadAsNumber(ACell)], FPointSeparatorSettings);
- end;
- cctError:
- begin
- valueType := 'float';
- value := 'NaN';
- end;
- end;
-
- if ACell^.ContentType = cctUTF8String then
- officeValue := ''
- else
- officeValue := ' office:value="' + value + '"';
-
- if auxSheet.HasComment(ACell) then
- begin
- draw := auxSheet.ReadComment(ACell);
- if draw <> '' then
- draw := Format(
- ind + ' ' + LE +
- ind + ' %s' + LE +
- ind + ' ' + LE, [draw]);
- end else
- draw := '';
- AppendToStream(AStream, Format(
- ind + '' + LE +
- ind + ' %s' + LE +
- draw +
- ind + '' + LE,
- [ valueType, officevalue, value ]
- ));
- end;
-
-var
- ind: String;
- colCountStr: String;
- n: Integer;
- r, c: Cardinal;
-begin
- ind := DupeString(' ', AIndent);
- n := AChart.Series.Count;
- if n > 0 then
- begin
- if AChart.IsScatterChart then
- n := n * 2;
- colCountStr := Format('table:number-columns-repeated="%d"', [n]);
- end else
- colCountStr := '';
-
- AppendToStream(AStream, Format(
- ind + '' + LE +
- ind + ' ' + LE +
- ind + ' ' + LE +
- ind + ' ' + LE +
- ind + ' ' + LE +
- ind + ' ' + LE +
- ind + ' ' + LE, [ colCountStr ]
- ));
-
- auxBook := TsWorkbook.Create;
- try
- auxSheet := auxBook.AddWorksheet('chart');
- PrepareChartTable(AChart, auxSheet);
-
- // Header rows (containing the series names)
- AppendToStream(AStream,
- ind + ' ' + LE +
- ind + ' ' + LE );
- for c := 0 to auxSheet.GetLastColIndex do
- WriteAuxCell(AIndent + 6, auxSheet.FindCell(0, c));
- AppendToStream(AStream,
- ind + ' ' + LE +
- ind + ' ' + LE
- );
-
- // Write data rows
- AppendToStream(AStream,
- ind + ' ' + LE
- );
- for r := 1 to auxSheet.GetLastRowIndex do
- begin
- AppendToStream(AStream,
- ind + ' ' + LE
- );
- for c := 0 to auxSheet.GetlastColIndex do
- WriteAuxCell(AIndent + 6, auxSheet.FindCell(r, c));
- AppendToStream(AStream,
- ind + ' ' + LE
- );
- end;
- AppendToStream(AStream,
- ind + ' ' + LE +
- ind + '' + LE
- );
-
- //auxBook.WriteToFile('table.ods', true);
- finally
- auxBook.Free;
- end;
-end;
-
-{ Writes the chart's title (or subtitle, depending on the value of IsSubTitle)
- to the xml stream (chart stream) and the corresponding style to the stylestream. }
-procedure TsSpreadOpenDocWriter.WriteChartTitle(AChartStream, AStyleStream: TStream;
- AChartIndent, AStyleIndent: Integer; AChart: TsChart; IsSubtitle: Boolean;
- var AStyleID: Integer);
-var
- title: TsChartText;
- captionKind: Integer;
- elementName: String;
- indent: String;
-begin
- if IsSubTitle then
- begin
- title := AChart.SubTitle;
- elementName := 'subtitle';
- captionKind := 2;
- end else
- begin
- title := AChart.Title;
- elementName := 'title';
- captionKind := 1;
- end;
-
- if (not title.Visible) or (title.Caption = '') then
- exit;
-
- // Write title properties
- indent := DupeString(' ', AChartIndent);
- AppendToStream(AChartStream, Format(
- indent + '' + LE +
- indent + ' %s' + LE +
- indent + '' + LE,
- [ elementName, AStyleID, title.Caption, elementName ], FPointSeparatorSettings
- ));
-
- // Write title style
- AppendToStream(AStyleStream,
- GetChartCaptionStyleAsXML(AChart, captionKind, AStyleIndent, AStyleID)
- );
-
- // Next style
- inc(AStyleID);
-end;
-
-{ Writes the file "Object N/styles.xml" (N = 1, 2, ...) which is needed by the
- charts since it defines the line dash patterns. }
-procedure TsSpreadOpenDocWriter.WriteObjectStyles(AStream: TStream; AChart: TsChart);
-const
- LENGTH_UNIT: array[boolean] of string = ('mm', '%'); // relative to line width
- DECS: array[boolean] of Integer = (1, 0); // relative to line width
-var
- i: Integer;
- linestyle: TsChartLineStyle;
- seg1, seg2: String;
-begin
- AppendToStream(AStream,
- XML_HEADER + LE);
-
- AppendToStream(AStream,
- '' + LE
- );
-
- AppendToStream(AStream,
- ' ' + LE
- );
-
- for i := 0 to AChart.NumLineStyles-1 do
- begin
- lineStyle := AChart.GetLineStyle(i);
- if linestyle.Segment1.Count > 0 then
- seg1 := Format('draw:dots1="%d" draw:dots1-length="%.*f%s" ', [
- lineStyle.Segment1.Count,
- DECS[linestyle.RelativeToLineWidth], linestyle.Segment1.Length, LENGTH_UNIT[linestyle.RelativeToLineWidth]
- ], FPointSeparatorSettings
- )
- else
- seg1 := '';
-
- if linestyle.Segment2.Count > 0 then
- seg2 := Format('draw:dots2="%d" draw:dots2-length="%.*f%s" ', [
- lineStyle.Segment2.Count,
- DECS[linestyle.RelativeToLineWidth], linestyle.Segment2.Length, LENGTH_UNIT[linestyle.RelativeToLineWidth]
- ], FPointSeparatorSettings
- )
- else
- seg2 := '';
-
- if (seg1 <> '') or (seg2 <> '') then
- AppendToStream(AStream, Format(
- ' ' + LE, [
- ASCIIName(linestyle.Name), linestyle.Name,
- DECS[linestyle.RelativeToLineWidth], linestyle.Distance, LENGTH_UNIT[linestyle.RelativeToLineWidth],
- seg1, seg2
- ]));
- end;
-
- AppendToStream(AStream,
- ' ' + LE +
- ''
- );
-end;
-
{ Writes the "office:automatic" > "style:style" node for table-column style family
in content.xml for all column records. }
procedure TsSpreadOpenDocWriter.WriteColStyles(AStream: TStream);
@@ -9492,7 +8101,8 @@ constructor TsSpreadOpenDocWriter.Create(AWorkbook: TsBasicWorkbook);
begin
inherited Create(AWorkbook);
-// FChartStyleList := TsChartStyleList.Create;
+ FChartWriter := TsSpreadOpenDocChartWriter.Create(self);
+
FColumnStyleList := TFPList.Create;
FRowStyleList := TFPList.Create;
FRichTextFontList := TStringList.Create;
@@ -9518,6 +8128,8 @@ begin
FRichTextFontList.Free; // Do not destroy fonts, they are owned by Workbook
FHeaderFooterFontList.Free;
+ FChartWriter.Free;
+
inherited Destroy;
end;
diff --git a/components/fpspreadsheet/source/common/fpsopendocumentchart.pas b/components/fpspreadsheet/source/common/fpsopendocumentchart.pas
new file mode 100644
index 000000000..41cad6025
--- /dev/null
+++ b/components/fpspreadsheet/source/common/fpsopendocumentchart.pas
@@ -0,0 +1,1455 @@
+unit fpsOpenDocumentChart;
+
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+ Classes, SysUtils, StrUtils,
+ {$IF FPC_FULLVERSION >= 20701}
+ zipper,
+ {$ELSE}
+ fpszipper,
+ {$ENDIF}
+ fpsTypes, fpSpreadsheet, fpsChart, fpsUtils, fpsReaderWriter, fpsXMLCommon;
+
+type
+ TsSpreadOpenDocChartWriter = class(TsBasicSpreadChartWriter)
+ private
+ FSCharts: array of TStream;
+ FSObjectStyles: array of TStream;
+ FPointSeparatorSettings: TFormatSettings;
+ function GetChartAxisStyleAsXML(Axis: TsChartAxis; AIndent, AStyleID: Integer): String;
+ function GetChartBackgroundStyleAsXML(AChart: TsChart; AFill: TsChartFill;
+ ABorder: TsChartLine; AIndent: Integer; AStyleID: Integer): String;
+ function GetChartCaptionStyleAsXML(AChart: TsChart; ACaptionKind, AIndent, AStyleID: Integer): String;
+ function GetChartFillStyleGraphicPropsAsXML(AChart: TsChart; AFill: TsChartFill): String;
+ function GetChartLegendStyleAsXML(AChart: TsChart; AIndent, AStyleID: Integer): String;
+ function GetChartLineStyleAsXML(AChart: TsChart; ALine: TsChartLine; AIndent, AStyleID: Integer): String;
+ function GetChartLineStyleGraphicPropsAsXML(AChart: TsChart; ALine: TsChartLine): String;
+ function GetChartPlotAreaStyleAsXML(AChart: TsChart; AIndent, AStyleID: Integer): String;
+ function GetChartSeriesStyleAsXML(AChart: TsChart; ASeriesIndex, AIndent, AStyleID: integer): String;
+// function GetChartTitleStyleAsXML(AChart: TsChart; AStyleIndex, AIndent: Integer): String;
+ procedure PrepareChartTable(AChart: TsChart; AWorksheet: TsBasicWorksheet);
+
+ protected
+ procedure WriteChart(AStream: TStream; AChart: TsChart);
+ procedure WriteChartAxis(AChartStream, AStyleStream: TStream;
+ AChartIndent, AStyleIndent: Integer; Axis: TsChartAxis; var AStyleID: Integer);
+ procedure WriteChartBackground(AChartStream, AStyleStream: TStream;
+ AChartIndent, AStyleIndent: Integer; AChart: TsChart; var AStyleID: Integer);
+ procedure WriteChartLegend(AChartStream, AStyleStream: TStream;
+ AChartIndent, AStyleIndent: Integer; AChart: TsChart; var AStyleID: Integer);
+ procedure WriteChartNumberStyles(AStream: TStream;
+ AIndent: Integer; AChart: TsChart);
+ procedure WriteObjectStyles(AStream: TStream; AChart: TsChart);
+ procedure WriteChartPlotArea(AChartStream, AStyleStream: TStream;
+ AChartIndent, AStyleIndent: Integer; AChart: TsChart; var AStyleID: Integer);
+ procedure WriteChartSeries(AChartStream, AStyleStream: TStream;
+ AChartIndent, AStyleIndent: Integer; AChart: TsChart; ASeriesIndex: Integer;
+ var AStyleID: Integer);
+ procedure WriteChartTable(AStream: TStream; AChart: TsChart; AIndent: Integer);
+ procedure WriteChartTitle(AChartStream, AStyleStream: TStream;
+ AChartIndent, AStyleIndent: Integer; AChart: TsChart; IsSubtitle: Boolean;
+ var AStyleID: Integer);
+
+ public
+ constructor Create(AWriter: TsBasicSpreadWriter);
+ procedure AddChartsToZip(AZip: TZipper);
+ procedure CreateStreams; override;
+ procedure DestroyStreams; override;
+ procedure ResetStreams; override;
+ procedure WriteCharts; override;
+ end;
+
+implementation
+
+uses
+ fpsOpenDocument;
+
+const
+ OPENDOC_PATH_CHART_CONTENT = 'Object %d/content.xml';
+ OPENDOC_PATH_CHART_STYLES = 'Object %d/styles.xml';
+
+ CHART_TYPE_NAMES: array[TsChartType] of string = (
+ '', 'bar', 'line', 'area', 'barLine', 'scatter', 'bubble'
+ );
+
+ CHART_SYMBOL_NAMES: array[TsChartSeriesSymbol] of String = (
+ 'square', 'diamond', 'arrow-up', 'arrow-down', 'arrow-left',
+ 'arrow-right', 'circle', 'star', 'x', 'plus', 'asterisk'
+ ); // unsupported: bow-tie, hourglass, horizontal-bar, vertical-bar
+
+ LE = LineEnding;
+
+function ASCIIName(AName: String): String;
+var
+ i: Integer;
+begin
+ Result := '';
+ for i := 1 to Length(AName) do
+ if AName[i] in ['a'..'z', 'A'..'Z', '0'..'9'] then
+ Result := Result + AName[i]
+ else
+ Result := Result + Format('_%.2x_', [ord(AName[i])]);
+end;
+
+
+{------------------------------------------------------------------------------}
+{ TsSpreadOpenDocChartWriter }
+{------------------------------------------------------------------------------}
+
+constructor TsSpreadOpenDocChartWriter.Create(AWriter: TsBasicSpreadWriter);
+begin
+ inherited Create(AWriter);
+ FPointSeparatorSettings := SysUtils.DefaultFormatSettings;
+ FPointSeparatorSettings.DecimalSeparator:='.';
+end;
+
+procedure TsSpreadOpenDocChartWriter.AddChartsToZip(AZip: TZipper);
+var
+ i: Integer;
+begin
+ for i := 0 to TsWorkbook(Writer.Workbook).GetChartCount-1 do
+ begin
+ AZip.Entries.AddFileEntry(
+ FSCharts[i], Format(OPENDOC_PATH_CHART_CONTENT, [i+1]));
+ AZip.Entries.AddFileEntry(
+ FSObjectStyles[i], Format(OPENDOC_PATH_CHART_STYLES, [i+1]));
+ end;
+end;
+
+procedure TsSpreadOpenDocChartWriter.CreateStreams;
+var
+ i, n: Integer;
+begin
+ n := TsWorkbook(Writer.Workbook).GetChartCount;
+ SetLength(FSCharts, n);
+ SetLength(FSObjectStyles, n);
+ for i := 0 to n - 1 do
+ begin
+ FSCharts[i] := CreateTempStream(Writer.Workbook, 'fpsCh');
+ FSObjectStyles[i] := CreateTempStream(Writer.Workbook, 'fpsOS');
+ end;
+end;
+
+procedure TsSpreadOpenDocChartWriter.DestroyStreams;
+var
+ i: Integer;
+begin
+ for i := 0 to High(FSCharts) do
+ begin
+ DestroyTempStream(FSCharts[i]);
+ DestroyTempStream(FSObjectStyles[i]);
+ end;
+ Setlength(FSCharts, 0);
+ SetLength(FSObjectStyles, 0);
+end;
+
+function TsSpreadOpenDocChartWriter.GetChartAxisStyleAsXML(
+ Axis: TsChartAxis; AIndent, AStyleID: Integer): String;
+var
+ chart: TsChart;
+ indent: String;
+ angle: Integer;
+ textProps: String = '';
+ graphProps: String = '';
+ chartProps: String = '';
+ numStyle: String = 'N0';
+begin
+ Result := '';
+ if not Axis.Visible then
+ exit;
+
+ chart := Axis.Chart;
+
+ if (Axis = chart.YAxis) and (chart.StackMode = csmStackedPercentage) then
+ numStyle := 'N10010';
+
+ if Axis.ShowLabels then
+ chartProps := chartProps + 'chart:display-label="true" ';
+
+ if Axis.Logarithmic then
+ chartProps := chartProps + 'chart:logarithmic="true" ';
+
+ if Axis.Inverted then
+ chartProps := chartProps + 'chart:reverse-direction="true" ';
+
+ angle := Axis.LabelRotation;
+ chartProps := chartProps + Format('style:rotation-angle="%d" ', [angle]);
+
+ graphProps := 'svg:stroke-color="' + ColorToHTMLColorStr(Axis.AxisLine.Color) + '" ';
+
+ textProps := TsSpreadOpenDocWriter(Writer).WriteFontStyleXMLAsString(Axis.LabelFont);
+
+ indent := DupeString(' ', AIndent);
+ Result := Format(
+ indent + '' + LE +
+ indent + ' ' + LE +
+ indent + ' ' + LE +
+ indent + ' ' + LE +
+ indent + '' + LE,
+ [ AStyleID, numStyle, chartProps, graphProps, textProps ]
+ );
+end;
+
+function TsSpreadOpenDocChartWriter.GetChartBackgroundStyleAsXML(
+ AChart: TsChart; AFill: TsChartFill; ABorder: TsChartLine;
+ AIndent, AStyleID: Integer): String;
+var
+ indent: String;
+ fillStr: String = '';
+ borderStr: String = '';
+begin
+ fillStr := GetChartFillStyleGraphicPropsAsXML(AChart, AFill);
+ borderStr := GetChartLineStyleGraphicPropsAsXML(AChart, ABorder);
+ indent := DupeString(' ', AIndent);
+ Result := Format(
+ indent + '' + LE +
+ indent + ' ' + LE +
+ indent + '' + LE,
+ [ AStyleID, fillStr, borderStr ]
+ );
+end;
+
+{
+
+
+
+
+ ACaptionKind = 1 ---> Title
+ ACaptionKind = 2 ---> SubTitle
+ ACaptionKind = 3 ---> x xis
+ ACaptionKind = 4 ---> y axis
+ ACaptionKind = 5 ---> x2 axis
+ ACaptionKind = 6 ---> y2 axis }
+function TsSpreadOpenDocChartWriter.GetChartCaptionStyleAsXML(AChart: TsChart;
+ ACaptionKind, AIndent, AStyleID: Integer): String;
+var
+ title: TsChartText;
+ axis: TsChartAxis;
+ font: TsFont;
+ indent: String;
+ rotAngle: Integer;
+ chartProps: String = '';
+ textProps: String = '';
+begin
+ Result := '';
+
+ case ACaptionKind of
+ 1, 2:
+ begin
+ if ACaptionKind = 1 then title := AChart.Title else title := AChart.Subtitle;
+ font := title.Font;
+ rotAngle := title.RotationAngle;
+ end;
+ 3, 4, 5, 6:
+ begin
+ case ACaptionKind of
+ 3: axis := AChart.XAxis;
+ 4: axis := AChart.YAxis;
+ 5: axis := AChart.X2Axis;
+ 6: axis := AChart.Y2Axis;
+ end;
+ font := axis.CaptionFont;
+ rotAngle := axis.CaptionRotation;
+ if AChart.RotatedAxes then
+ begin
+ if rotAngle = 0 then rotAngle := 90 else if rotAngle = 90 then rotAngle := 0;
+ end;
+ end;
+ else
+ raise Exception.Create('[GetChartCaptionStyleAsXML] Unknown caption.');
+ end;
+
+ chartProps := 'chart:auto-position="true" ';
+ chartProps := chartProps + Format('style:rotation-angle="%d" ', [rotAngle]);
+
+ textProps := TsSpreadOpenDocWriter(Writer).WriteFontStyleXMLAsString(font);
+
+ indent := DupeString(' ', AIndent);
+ Result := Format(
+ indent + '' + LE +
+ indent + ' ' + LE +
+ indent + ' ' + LE +
+ indent + '' + LE,
+ [ AStyleID, chartProps, textProps ]
+ );
+end;
+
+function TsSpreadOpenDocChartWriter.GetChartFillStyleGraphicPropsAsXML(AChart: TsChart;
+ AFill: TsChartFill): String;
+var
+ fillStr: String;
+ fillColorStr: String;
+begin
+ if AFill.Style = fsNoFill then
+ begin
+ Result := 'draw:fill="none" ';
+ exit;
+ end;
+
+ // To do: extend with hatched and gradient fills
+ fillStr := 'draw:fill="solid" ';
+ fillColorStr := 'draw:fill-color="' + ColorToHTMLColorStr(AFill.FgColor) + '" ';
+
+ Result := fillStr + fillColorStr;
+end;
+
+{
+
+
+
+
+
+}
+function TsSpreadOpenDocChartWriter.GetChartLegendStyleAsXML(AChart: TsChart;
+ AIndent, AStyleID: Integer): String;
+var
+ indent: String;
+ textProps: String = '';
+ graphProps: String = '';
+begin
+ Result := '';
+
+ if not AChart.Legend.Visible then
+ exit;
+
+ graphProps := GetChartLineStyleGraphicPropsAsXML(AChart, AChart.Legend.Border) +
+ GetChartFillStyleGraphicPropsAsXML(AChart, AChart.Legend.Background);
+
+ textProps := TsSpreadOpenDocWriter(Writer).WriteFontStyleXMLAsString(AChart.Legend.Font);
+
+ indent := DupeString(' ', AIndent);
+ Result := Format(
+ indent + '' + LE +
+ indent + ' ' + LE +
+ indent + ' ' + LE +
+ indent + ' ' + LE +
+ indent + '' + LE,
+ [ AStyleID, graphProps, textProps ]
+ );
+end;
+
+{
+
+ }
+function TsSpreadOpenDocChartWriter.GetChartLineStyleAsXML(AChart: TsChart;
+ ALine: TsChartLine; AIndent, AStyleID: Integer): String;
+var
+ ind: String;
+ graphProps: String = '';
+begin
+ ind := DupeString(' ', AIndent);
+ graphProps := GetChartLineStyleGraphicPropsAsXML(AChart, ALine);
+ Result := Format(
+ ind + '' + LE +
+ ind + ' ' + LE +
+ ind + '' + LE,
+ [ AStyleID, graphProps ]
+ );
+end;
+
+{ Constructs the xml for a line style to be used in the }
+function TsSpreadOpenDocChartWriter.GetChartLineStyleGraphicPropsAsXML(
+ AChart: TsChart; ALine: TsChartLine): String;
+var
+ strokeStr: String = '';
+ widthStr: String = '';
+ colorStr: String = '';
+ s: String;
+ linestyle: TsChartLineStyle;
+begin
+ if ALine.Style = clsNoLine then
+ begin
+ Result := 'draw:stroke="none" ';
+ exit;
+ end;
+
+ strokeStr := 'draw:stroke="solid" ';
+ if (ALine.Style <> clsSolid) then
+ begin
+ linestyle := AChart.GetLineStyle(ALine.Style);
+ if linestyle <> nil then
+ strokeStr := 'draw:stroke="dash" draw:stroke-dash="' + ASCIIName(linestyle.Name) + '" ';
+ end;
+
+ if ALine.Width > 0 then
+ widthStr := Format('svg:stroke-width="%.1fmm" ', [ALine.Width], FPointSeparatorSettings);
+ colorStr := Format('svg:stroke-color="%s" ', [ColorToHTMLColorStr(ALine.Color)]);
+
+ Result := strokeStr + widthStr + colorStr;
+end;
+
+function TsSpreadOpenDocChartWriter.GetChartPlotAreaStyleAsXML(AChart: TsChart;
+ AIndent, AStyleID: Integer): String;
+var
+ indent: String;
+ interpolationStr: String = '';
+ verticalStr: String = '';
+ stackModeStr: String = '';
+begin
+ indent := DupeString(' ', AIndent);
+
+ if AChart.RotatedAxes then
+ verticalStr := 'chart:vertical="true" ';
+
+ case AChart.StackMode of
+ csmSideBySide: ;
+ csmStacked: stackModeStr := 'chart:stacked="true" ';
+ csmStackedPercentage: stackModeStr := 'chart:percentage="true" ';
+ end;
+
+ case AChart.Interpolation of
+ ciLinear: ;
+ ciCubicSpline: interpolationStr := 'chart:interpolation="cubic-spline" ';
+ ciBSpline: interpolationStr := 'chart:interpolation="b-spline" ';
+ ciStepStart: interpolationStr := 'chart:interpolation="step-start" ';
+ ciStepEnd: interpolationStr := 'chart:interpolation="step-end" ';
+ ciStepCenterX: interpolationStr := 'chart:interpolation="step-center-x" ';
+ ciStepCenterY: interpolationStr := 'chart:interpolation="step-center-y" ';
+ end;
+
+ Result := Format(
+ indent + ' ', [ AStyleID ]) + LE +
+ indent + ' ' + LE +
+ indent + ' ' + LE;
+end;
+
+{
+
+
+
+ }
+function TsSpreadOpenDocChartWriter.GetChartSeriesStyleAsXML(AChart: TsChart;
+ ASeriesIndex, AIndent, AStyleID: Integer): String;
+var
+ series: TsChartSeries;
+ lineser: TsLineSeries = nil;
+ indent: String;
+ chartProps: String = '';
+ graphProps: String = '';
+ textProps: String = '';
+ lineProps: String = '';
+ fillProps: String = '';
+begin
+ Result := '';
+
+ indent := DupeString(' ', AIndent);
+ series := AChart.Series[ASeriesIndex];
+
+ // Chart properties
+ chartProps := 'chart:symbol-type="none" ';
+ if (series is TsLineSeries) then
+ begin
+ lineser := TsLineSeries(series);
+ if lineser.ShowSymbols then
+ chartProps := Format(
+ 'chart:symbol-type="named-symbol" chart:symbol-name="%s" chart:symbol-width="%.1fmm" chart.symbol-height="%.1fmm" ',
+ [CHART_SYMBOL_NAMES[lineSer.Symbol], lineSer.SymbolWidth, lineSer.SymbolHeight ],
+ FPointSeparatorSettings
+ );
+ end;
+ chartProps := chartProps + 'chart:link-data-style-to-source="true" ';
+
+ // Graphic properties
+ lineProps := GetChartLineStyleGraphicPropsAsXML(AChart, series.Line);
+ fillProps := GetChartFillStyleGraphicPropsAsXML(AChart, series.Fill);
+ if (series is TsLineSeries) then
+ begin
+ if lineSer.ShowSymbols then
+ graphProps := graphProps + fillProps;
+ if lineSer.ShowLines and (lineser.Line.Style <> clsNoLine) then
+ graphProps := graphProps + lineProps;
+ end else
+ graphProps := fillProps + lineProps;
+
+ // Text properties
+ textProps := 'fo:font-size="10pt" style:font-size-asian="10pt" style:font-size-complex="10pt" ';
+// textProps := WriteFontStyleXMLAsString(font); // <--- to be completed. this is for the series labels.
+
+ Result := Format(
+ indent + '' + LE +
+ indent + ' ' + LE +
+ indent + ' ' + LE +
+ indent + ' ' + LE +
+ indent + '' + LE,
+ [ AStyleID, chartProps, graphProps, textProps ]
+ );
+end;
+
+{ 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
+ data can be written to the xml immediately.
+ Independently of the layout in the original worksheet, data are arranged in
+ columns of AWorksheet, starting at cell A1.
+ - First column: Categories (or index in case of scatter chart)
+ - Second column:
+ in case of category charts: y values of the first series,
+ in case of scatter series: x values of the first series
+ - Third column:
+ in case of category charts: y values of the second series
+ in case of scatter series. y values of the first series
+ - etc.
+ The first row contains
+ - nothing in case of the first column
+ - cell range reference in ODS syntax for the cells in the original worksheet.
+ The aux worksheet should be contained in a separate workbook to avoid
+ interfering with the writing process.
+ }
+procedure TsSpreadOpenDocChartWriter.PrepareChartTable(AChart: TsChart;
+ AWorksheet: TsBasicWorksheet);
+var
+ isScatterChart: Boolean;
+ series: TsChartSeries;
+ seriesSheet: TsWorksheet;
+ auxSheet: TsWorksheet;
+ refStr, txt: String;
+ i, j: Integer;
+ srcCell, destCell: PCell;
+ destCol: Cardinal;
+ r1, c1, r2, c2: Cardinal;
+ nRows: Integer;
+begin
+ if AChart.Series.Count = 0 then
+ exit;
+
+ auxSheet := TsWorksheet(AWorksheet);
+ seriesSheet := TsWorkbook(Writer.Workbook).GetWorksheetByIndex(AChart.SheetIndex);
+ isScatterChart := AChart.IsScatterChart;
+
+ // Determine the number of rows in auxiliary output worksheet.
+ nRows := 0;
+ for i := 0 to AChart.Series.Count-1 do
+ begin
+ series := AChart.Series[i];
+ j := series.GetXCount;
+ if j > nRows then nRows := j;
+ j := series.GetYCount;
+ if j > nRows then nRows := j;
+ end;
+
+ // Write label column. If missing, write consecutive numbers 1, 2, 3, ...
+ destCol := 0;
+ for i := 0 to AChart.Series.Count-1 do
+ begin
+ series := AChart.Series[i];
+
+ // Write the label column. Use consecutive numbers 1, 2, 3, ... if there
+ // are no labels.
+ if series.HasLabels then
+ begin
+ r1 := series.LabelRange.Row1;
+ c1 := series.LabelRange.Col1;
+ r2 := series.LabelRange.Row2;
+ c2 := series.LabelRange.Col2;
+ refStr := GetSheetCellRangeString_ODS(seriesSheet.Name, seriesSheet.Name, r1, c1, r2, c2, rfAllRel, false);
+ end else
+ refStr := '';
+
+ auxSheet.WriteText(0, destCol, '');
+
+ for j := 1 to nRows do
+ begin
+ if series.HasLabels then
+ begin
+ if series.LabelsInCol then
+ srcCell := seriesSheet.FindCell(r1 + j - 1, c1)
+ else
+ srcCell := seriesSheet.FindCell(r1, c1 + j - 1);
+ end else
+ srcCell := nil;
+ if srcCell <> nil then
+ begin
+ destCell := auxsheet.GetCell(j, destCol);
+ seriesSheet.CopyValue(srcCell, destCell);
+ end else
+ destCell := auxSheet.WriteNumber(j, destCol, j);
+ end;
+ if (refStr <> '') then
+ auxsheet.WriteComment(1, destCol, refStr);
+
+ // In case of scatter plot write the x column. Use consecutive numbers 1, 2, 3, ...
+ // if there are no x values.
+ if isScatterChart then
+ begin
+ inc(destCol);
+
+ if series.HasXValues then
+ begin
+ r1 := series.XRange.Row1;
+ c1 := series.XRange.Col1;
+ r2 := series.XRange.Row2;
+ c2 := series.XRange.Col2;
+ refStr := GetSheetCellRangeString_ODS(seriesSheet.Name, seriesSheet.Name, r1, c1, r2, c2, rfAllRel, false);
+ if series.XValuesInCol then
+ txt := 'Col ' + GetColString(c1)
+ else
+ txt := 'Row ' + GetRowString(r1);
+ end else
+ begin
+ refStr := '';
+ txt := '';
+ end;
+
+ auxSheet.WriteText(0, destCol, txt);
+
+ for j := 1 to nRows do
+ begin
+ if series.HasXValues then
+ begin
+ if series.XValuesInCol then
+ srcCell := seriesSheet.FindCell(r1 + j - 1, c1)
+ else
+ srcCell := seriesSheet.FindCell(r1, c1 + j - 1);
+ end else
+ srcCell := nil;
+ if srcCell <> nil then
+ begin
+ destCell := auxsheet.GetCell(j, destCol);
+ seriesSheet.CopyValue(srcCell, destCell);
+ end else
+ destCell := auxSheet.WriteNumber(j, destCol, j);
+ end;
+ if (refStr <> '') then
+ auxsheet.WriteComment(1, destCol, refStr);
+ end;
+
+ // Write the y column
+ if not series.HasYValues then
+ Continue;
+
+ inc(destCol);
+
+ r1 := series.TitleAddr.Row; // Series title
+ c1 := series.TitleAddr.Col;
+ txt := seriesSheet.ReadAsText(r1, c1);
+ auxsheet.WriteText(0, destCol, txt);
+ refStr := GetSheetCellRangeString_ODS(seriesSheet.Name, seriesSheet.Name, r1, c1, r1, c1, rfAllRel, false);
+ if (refStr <> '') then
+ auxSheet.WriteComment(0, destCol, refStr); // Store title reference as comment for svg node
+
+ r1 := series.YRange.Row1;
+ c1 := series.YRange.Col1;
+ r2 := series.YRange.Row2;
+ c2 := series.YRange.Col2;
+ refStr := GetSheetCellRangeString_ODS(seriesSheet.Name, seriesSheet.Name, r1, c1, r2, c2, rfAllRel, false);
+ for j := 1 to series.GetYCount do
+ begin
+ if series.YValuesInCol then
+ srcCell := seriesSheet.FindCell(r1 + j - 1, c1)
+ else
+ srcCell := seriesSheet.FindCell(r1, c1 + j - 1);
+ if srcCell <> nil then
+ begin
+ destCell := auxSheet.GetCell(j, destCol);
+ seriesSheet.CopyValue(srcCell, destCell);
+ end else
+ destCell := auxSheet.WriteNumber(j, destCol, j);
+ end;
+
+ if (refStr <> '') then
+ auxSheet.WriteComment(1, destCol, refStr); // Store y range reference as comment for svg node
+ end;
+end;
+
+procedure TsSpreadOpenDocChartWriter.ResetStreams;
+var
+ i: Integer;
+begin
+ for i := 0 to High(FSCharts) do
+ begin
+ FSCharts[i].Position := 0;
+ FSObjectStyles[i].Position := 0;
+ end;
+end;
+
+{ Writes the chart to the specified stream.
+ All chart elements are formatted by means of styles. To simplify assignment
+ of styles to elements we first write the elements and create the style of
+ the currently written chart element on the fly. Since styles must be written
+ to the steam first, we write the chart elements to a separate stream which
+ is appended to the main stream afterwards. }
+procedure TsSpreadOpenDocChartWriter.WriteChart(AStream: TStream; AChart: TsChart);
+var
+ chartStream: TMemoryStream;
+ styleStream: TMemoryStream;
+ styleID: Integer;
+begin
+// FChartStyleList.Clear;
+
+ chartStream := TMemoryStream.Create;
+ styleStream := TMemoryStream.Create;
+ try
+ WriteChartNumberStyles(styleStream, 4, AChart);
+
+ styleID := 1;
+ WriteChartBackground(chartStream, styleStream, 6, 4, AChart, styleID);
+ WriteChartTitle(chartStream, styleStream, 6, 4, AChart, false, styleID); // Title
+ WriteChartTitle(chartStream, styleStream, 6, 4, AChart, true, styleID); // Subtitle
+ WriteChartLegend(chartStream, styleStream, 6, 4, AChart, styleID); // Legend
+ WriteChartPlotArea(chartStream, styleStream, 6, 4, AChart, styleID); // Wall, axes, series
+
+ // Here begins the main stream
+ AppendToStream(AStream,
+ XML_HEADER + LE);
+
+ AppendToStream(AStream,
+ '' + LE
+ );
+
+ // The file begins with the chart styles
+ AppendToStream(AStream,
+ ' ' + LE
+ );
+
+ // Copy the styles from the temporary style stream.
+ styleStream.Position := 0;
+ AStream.CopyFrom(styleStream, stylestream.Size);
+
+ // Now the chart part follows, after closing the styles part
+ AppendToStream(AStream,
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE
+ );
+
+ // Copy the chart elements from the temporary chart stream
+ chartStream.Position := 0;
+ AStream.CopyFrom(chartStream, chartStream.Size);
+
+ // After the chart elements we have the data to be plotted
+ WriteChartTable(AStream, AChart, 8);
+
+ // Finally the footer.
+ AppendToStream(AStream,
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ '');
+ finally
+ chartStream.Free;
+ styleStream.Free;
+ end;
+end;
+
+procedure TsSpreadOpenDocChartWriter.WriteChartAxis(
+ AChartStream, AStyleStream: TStream;
+ AChartIndent, AStyleIndent: Integer; Axis: TsChartAxis;
+ var AStyleID: Integer);
+type
+ TAxisKind = 3..6;
+const
+ AXIS_ID: array[TAxisKind] of string = ('x', 'y', 'x', 'y');
+ AXIS_LEVEL: array[TAxisKind] of string = ('primary', 'primary', 'secondary', 'secondary');
+var
+ indent: String;
+ captionKind: Integer;
+ chart: TsChart;
+ series: TsChartSeries;
+ sheet: TsWorksheet;
+ refStr: String;
+ r1, c1, r2, c2: Cardinal;
+begin
+ if not Axis.Visible then
+ exit;
+
+ chart := Axis.Chart;
+
+ if Axis = chart.XAxis then
+ captionKind := 3
+ else if Axis = chart.YAxis then
+ captionKind := 4
+ else if Axis = chart.X2Axis then
+ captionKind := 5
+ else if Axis = chart.Y2Axis then
+ captionKind := 6
+ else
+ raise Exception.Create('[WriteChartAxis] Unknown axis');
+
+ // Write axis
+ indent := DupeString(' ', AChartIndent);
+ AppendToStream(AChartStream, Format(
+ indent + '' + LE,
+ [ AStyleID, AXIS_ID[captionKind], AXIS_LEVEL[captionKind], AXIS_ID[captionKind] ]
+ ));
+
+ if (Axis = chart.XAxis) and (not chart.IsScatterChart) and (chart.Series.Count > 0) then
+ begin
+ series := chart.Series[0];
+ sheet := TsWorkbook(Writer.Workbook).GetWorksheetByIndex(chart.SheetIndex);
+ r1 := series.LabelRange.Row1;
+ c1 := series.LabelRange.Col1;
+ r2 := series.LabelRange.Row2;
+ c2 := series.LabelRange.Col2;
+ refStr := GetSheetCellRangeString_ODS(sheet.Name, sheet.Name, r1, c1, r2, c2, rfAllRel, false);
+ AppendToStream(AChartStream, Format(
+ indent + ' ' + LE,
+ [ refStr ]
+ ));
+ end;
+
+ // Write axis style
+ AppendToStream(AStyleStream,
+ GetChartAxisStyleAsXML(Axis, AStyleIndent, AStyleID)
+ );
+
+ // Next style
+ inc(AStyleID);
+
+ // Axis title
+ if Axis.ShowCaption and (Axis.Caption <> '') then
+ begin
+ AppendToStream(AChartStream, Format(
+ indent + ' ' + LE +
+ indent + ' %s' + LE +
+ indent + ' ' + LE,
+ [ AStyleID, Axis.Caption ]
+ ));
+
+ // Axis title style
+ AppendToStream(AStyleStream,
+ GetChartCaptionStyleAsXML(chart, captionKind, AStyleIndent, AStyleID)
+ );
+
+ // Next style
+ inc(AStyleID);
+ end;
+
+ // Major grid lines
+ if Axis.MajorGridLines.Style <> clsNoLine then
+ begin
+ AppendToStream(AChartStream, Format(
+ indent + ' ' + LE,
+ [ AStyleID ]
+ ));
+
+ // Major grid lines style
+ AppendToStream(AStyleStream,
+ GetChartLineStyleAsXML(chart, Axis.MajorGridLines, AStyleIndent, AStyleID)
+ );
+
+ // Next style
+ inc(AStyleID);
+ end;
+
+ // Minor grid lines
+ if Axis.MinorGridLines.Style <> clsNoLine then
+ begin
+ AppendToStream(AChartStream, Format(
+ indent + ' ' + LE,
+ [ AStyleID ]
+ ));
+
+ // Minor grid lines style
+ AppendToStream(AStyleStream,
+ GetChartLineStyleAsXML(chart, Axis.MinorGridLines, AStyleIndent, AStyleID)
+ );
+
+ // Next style
+ inc(AStyleID);
+ end;
+
+ // Close the xml node
+ AppendToStream(AChartStream,
+ indent + '' + LE
+ );
+end;
+
+{ Writes the chart's background to the xml stream }
+procedure TsSpreadOpenDocChartWriter.WriteChartBackground(
+ AChartStream, AStyleStream: TStream;
+ AChartIndent, AStyleIndent: Integer; AChart: TsChart; var AStyleID: Integer);
+var
+ indent: String;
+ chartClass: String;
+begin
+ chartClass := CHART_TYPE_NAMES[AChart.GetChartType];
+ if chartClass <> '' then
+ chartClass := 'chart:class="chart:' + chartClass + '"';
+
+ indent := DupeString(' ', AChartIndent);
+ AppendToStream(AChartStream, Format(
+ indent + '' + LE, [
+ AStyleID,
+ chartClass,
+ AChart.Width, AChart.Height // Width, Height are in mm
+ ], FPointSeparatorSettings
+ ));
+
+ AppendToStream(AStyleStream,
+ GetChartBackgroundStyleAsXML(AChart, AChart.Background, AChart.Border, AStyleIndent, AStyleID)
+ );
+
+ inc(AStyleID);
+end;
+
+{ Writes the chart's legend to the xml stream }
+procedure TsSpreadOpenDocChartWriter.WriteChartLegend(AChartStream, AStyleStream: TStream;
+ AChartIndent, AStyleIndent: Integer; AChart: TsChart; var AStyleID: Integer);
+const
+ LEGEND_POSITION: array[TsChartLegendPosition] of string = (
+ 'end', 'top', 'bottom', 'start'
+ );
+var
+ indent: String;
+ canOverlap: String = '';
+begin
+ if (not AChart.Legend.Visible) then
+ exit;
+
+ if AChart.Legend.CanOverlapPlotArea then
+ canOverlap := 'loext:overlay="true" ';
+
+ // Write legend properties
+ indent := DupeString(' ', AChartIndent);
+ AppendToStream(AChartStream, Format(
+ indent + '' + LE,
+ [ AStyleID, LEGEND_POSITION[AChart.Legend.Position], canOverlap ]
+ ));
+
+ // Write legend style
+ AppendToStream(AStyleStream,
+ GetChartLegendStyleAsXML(AChart, AStyleIndent, AStyleID)
+ );
+
+ // Next style
+ inc(AStyleID);
+end;
+
+procedure TsSpreadOpenDocChartWriter.WriteChartNumberStyles(AStream: TStream;
+ AIndent: Integer; AChart: TsChart);
+var
+ indent: String;
+begin
+ indent := DupeString(' ', AIndent);
+
+ AppendToStream(AStream,
+ indent + '' + LE +
+ indent + ' ' + LE +
+ indent + '' + LE
+ );
+
+ if AChart.StackMode = csmStackedPercentage then
+ AppendToStream(AStream,
+ indent + '' + LE +
+ indent + ' ' + LE +
+ indent + ' %' + LE +
+ indent + '' + LE
+ );
+end;
+
+{ Writes the file "Object N/styles.xml" (N = 1, 2, ...) which is needed by the
+ charts since it defines the line dash patterns. }
+procedure TsSpreadOpenDocChartWriter.WriteObjectStyles(AStream: TStream;
+ AChart: TsChart);
+const
+ LENGTH_UNIT: array[boolean] of string = ('mm', '%'); // relative to line width
+ DECS: array[boolean] of Integer = (1, 0); // relative to line width
+var
+ i: Integer;
+ linestyle: TsChartLineStyle;
+ seg1, seg2: String;
+begin
+ AppendToStream(AStream,
+ XML_HEADER + LE);
+
+ AppendToStream(AStream,
+ '' + LE
+ );
+
+ AppendToStream(AStream,
+ ' ' + LE
+ );
+
+ for i := 0 to AChart.NumLineStyles-1 do
+ begin
+ lineStyle := AChart.GetLineStyle(i);
+ if linestyle.Segment1.Count > 0 then
+ seg1 := Format('draw:dots1="%d" draw:dots1-length="%.*f%s" ', [
+ lineStyle.Segment1.Count,
+ DECS[linestyle.RelativeToLineWidth], linestyle.Segment1.Length, LENGTH_UNIT[linestyle.RelativeToLineWidth]
+ ], FPointSeparatorSettings
+ )
+ else
+ seg1 := '';
+
+ if linestyle.Segment2.Count > 0 then
+ seg2 := Format('draw:dots2="%d" draw:dots2-length="%.*f%s" ', [
+ lineStyle.Segment2.Count,
+ DECS[linestyle.RelativeToLineWidth], linestyle.Segment2.Length, LENGTH_UNIT[linestyle.RelativeToLineWidth]
+ ], FPointSeparatorSettings
+ )
+ else
+ seg2 := '';
+
+ if (seg1 <> '') or (seg2 <> '') then
+ AppendToStream(AStream, Format(
+ ' ' + LE, [
+ ASCIIName(linestyle.Name), linestyle.Name,
+ DECS[linestyle.RelativeToLineWidth], linestyle.Distance, LENGTH_UNIT[linestyle.RelativeToLineWidth],
+ seg1, seg2
+ ]));
+ end;
+
+ AppendToStream(AStream,
+ ' ' + LE +
+ ''
+ );
+end;
+
+procedure TsSpreadOpenDocChartWriter.WriteChartPlotArea(AChartStream, AStyleStream: TStream;
+ AChartIndent, AStyleIndent: Integer; AChart: TsChart; var AStyleID: Integer);
+var
+ indent: String;
+ i: Integer;
+begin
+ indent := DupeString(' ', AChartIndent);
+
+ // Plot area properties
+ // (ods has a table:cell-range-address here but it is reconstructed by Calc)
+ AppendToStream(AChartStream, Format(
+ indent + '' + LE,
+ [ AStyleID ]
+ ));
+ // Plot area style
+ AppendToStream(AStyleStream,
+ GetChartPlotAreaStyleAsXML(AChart, AStyleIndent, AStyleID)
+ );
+
+ // Next style
+ inc(AStyleID);
+
+ // Wall properties
+ AppendToStream(AChartStream, Format(
+ indent + ' ' + LE,
+ [ AStyleID ]
+ ));
+ // Wall style
+ AppendToStream(AStyleStream,
+ GetChartBackgroundStyleAsXML(AChart, AChart.PlotArea.Background, AChart.PlotArea.Border, AStyleIndent, AStyleID)
+ );
+
+ // Next style
+ inc(AStyleID);
+
+ // Floor properties
+ AppendToStream(AChartStream, Format(
+ indent + ' ' + LE,
+ [ AStyleID ]
+ ));
+ // Floor style
+ AppendToStream(AStyleStream,
+ GetChartBackgroundStyleAsXML(AChart, AChart.Floor.Background, AChart.Floor.Border, AStyleIndent, AStyleID)
+ );
+
+ // Next style
+ inc(AStyleID);
+
+ // primary x axis
+ WriteChartAxis(AChartStream, AStyleStream, AChartIndent+2, AStyleIndent, AChart.XAxis, AStyleID);
+
+ // primary y axis
+ WriteChartAxis(AChartStream, AStyleStream, AChartIndent+2, AStyleIndent, AChart.YAxis, AStyleID);
+
+ // secondary x axis
+ WriteChartAxis(AChartStream, AStyleStream, AChartIndent+2, AStyleIndent, AChart.X2Axis, AStyleID);
+
+ // secondary y axis
+ WriteChartAxis(AChartStream, AStyleStream, AChartIndent+2, AStyleIndent, AChart.Y2Axis, AStyleID);
+
+ // series
+ for i := 0 to AChart.Series.Count-1 do
+ WriteChartSeries(AChartStream, AStyleStream, AChartIndent+2, AStyleIndent, AChart, i, AStyleID);
+
+ // close xml node
+ AppendToStream(AChartStream,
+ indent + '' + LE
+ );
+end;
+
+procedure TsSpreadOpenDocChartWriter.WriteChartSeries(
+ AChartStream, AStyleStream: TStream;
+ AChartIndent, AStyleIndent: Integer; AChart: TsChart; ASeriesIndex: Integer;
+ var AStyleID: Integer);
+var
+ indent: String;
+ sheet: TsWorksheet;
+ series: TsChartSeries;
+ valuesRange: String;
+ domainRangeX: String = '';
+ domainRangeY: String = '';
+ rangeStr: String = '';
+ titleAddr: String;
+ count: Integer;
+begin
+ indent := DupeString(' ', AChartIndent);
+
+ series := AChart.Series[ASeriesIndex];
+ sheet := TsWorkbook(Writer.Workbook).GetWorksheetByIndex(AChart.sheetIndex);
+
+ // These are the x values of a scatter or bubble plot.
+ if (series is TsScatterSeries) or (series is TsBubbleSeries) then
+ begin
+ domainRangeX := GetSheetCellRangeString_ODS(
+ sheet.Name, sheet.Name,
+ series.XRange.Row1, series.XRange.Col1,
+ series.XRange.Row2, series.XRange.Col2,
+ rfAllRel, false
+ );
+ end;
+
+ if series is TsBubbleSeries then
+ begin
+ domainRangeY := GetSheetCellRangeString_ODS(
+ sheet.Name, sheet.Name,
+ series.YRange.Row1, series.YRange.Col1,
+ series.YRange.Row2, series.YRange.Col2,
+ rfAllRel, false
+ );
+ // These are the bubble radii
+ valuesRange := GetSheetCellRangeString_ODS(
+ sheet.Name, sheet.Name,
+ TsBubbleSeries(series).BubbleRange.Row1, TsBubbleSeries(series).BubbleRange.Col1,
+ TsBubbleSeries(series).BubbleRange.Row2, TsBubbleSeries(series).BubbleRange.Col2,
+ rfAllRel, false
+ );
+ end else
+ // These are the y values of the non-bubble series
+ valuesRange := GetSheetCellRangeString_ODS(
+ sheet.Name, sheet.Name,
+ series.YRange.Row1, series.YRange.Col1,
+ series.YRange.Row2, series.YRange.Col2,
+ rfAllRel, false
+ );
+
+ // And these are the data point labels.
+ titleAddr := GetSheetCellRangeString_ODS(
+ sheet.Name, sheet.Name,
+ series.TitleAddr.Row, series.TitleAddr.Col,
+ series.TitleAddr.Row, series.TitleAddr.Col,
+ rfAllRel, false
+ );
+ count := series.YRange.Row2 - series.YRange.Row1 + 1;
+
+ // Store the series properties
+ AppendToStream(AChartStream, Format(
+ indent + '' + LE,
+ [ AStyleID, valuesRange, titleAddr, CHART_TYPE_NAMES[series.ChartType] ]
+ ));
+ if domainRangeY <> '' then
+ AppendToStream(AChartStream, Format(
+ indent + '' + LE,
+ [ domainRangeY ]
+ ));
+ if domainRangeX <> '' then
+ AppendToStream(AChartStream, Format(
+ indent + '' + LE,
+ [ domainRangeX ]
+ ));
+
+ AppendToStream(AChartStream, Format(
+ indent + ' ' + LE,
+ [ count ]
+ ));
+ AppendToStream(AChartStream,
+ indent + '' + LE
+ );
+
+ // Series style
+ AppendToStream(AStyleStream,
+ GetChartSeriesStyleAsXML(AChart, ASeriesIndex, AStyleIndent, AStyleID)
+ );
+
+ // Next style
+ inc(AStyleID);
+end;
+
+procedure TsSpreadOpenDocChartWriter.WriteCharts;
+var
+ i: Integer;
+ chart: TsChart;
+begin
+ for i := 0 to TsWorkbook(Writer.Workbook).GetChartCount - 1 do
+ begin
+ chart := TsWorkbook(Writer.Workbook).GetChartByIndex(i);
+ WriteChart(FSCharts[i], chart);
+ end;
+end;
+
+{ Writes the chart's data table. NOTE: The chart gets its data from this table
+ rather than from the worksheet! }
+procedure TsSpreadOpenDocChartWriter.WriteChartTable(AStream: TStream;
+ AChart: TsChart; AIndent: Integer);
+var
+ auxBook: TsWorkbook;
+ auxSheet: TsWorksheet;
+
+ procedure WriteAuxCell(AIndent: Integer; ACell: PCell);
+ var
+ ind: String;
+ valueType: String;
+ value: String;
+ officeValue: String;
+ draw: String;
+ begin
+ ind := DupeString(' ', AIndent);
+ if (ACell = nil) or (ACell^.ContentType = cctEmpty) then
+ begin
+ AppendToStream(AStream,
+ ind + '' + LE +
+ ind + ' ' + LE +
+ ind + '' + LE
+ );
+ exit;
+ end;
+
+ case ACell^.ContentType of
+ cctUTF8String:
+ begin
+ valueType := 'string';
+ value := auxSheet.ReadAsText(ACell);
+ officeValue := '';
+ end;
+ cctNumber, cctDateTime, cctBool:
+ begin
+ valueType := 'float';
+ value := Format('%g', [auxSheet.ReadAsNumber(ACell)], FPointSeparatorSettings);
+ end;
+ cctError:
+ begin
+ valueType := 'float';
+ value := 'NaN';
+ end;
+ end;
+
+ if ACell^.ContentType = cctUTF8String then
+ officeValue := ''
+ else
+ officeValue := ' office:value="' + value + '"';
+
+ if auxSheet.HasComment(ACell) then
+ begin
+ draw := auxSheet.ReadComment(ACell);
+ if draw <> '' then
+ draw := Format(
+ ind + ' ' + LE +
+ ind + ' %s' + LE +
+ ind + ' ' + LE, [draw]);
+ end else
+ draw := '';
+ AppendToStream(AStream, Format(
+ ind + '' + LE +
+ ind + ' %s' + LE +
+ draw +
+ ind + '' + LE,
+ [ valueType, officevalue, value ]
+ ));
+ end;
+
+var
+ ind: String;
+ colCountStr: String;
+ n: Integer;
+ r, c: Cardinal;
+begin
+ ind := DupeString(' ', AIndent);
+ n := AChart.Series.Count;
+ if n > 0 then
+ begin
+ if AChart.IsScatterChart then
+ n := n * 2;
+ colCountStr := Format('table:number-columns-repeated="%d"', [n]);
+ end else
+ colCountStr := '';
+
+ AppendToStream(AStream, Format(
+ ind + '' + LE +
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+ ind + ' ' + LE, [ colCountStr ]
+ ));
+
+ auxBook := TsWorkbook.Create;
+ try
+ auxSheet := auxBook.AddWorksheet('chart');
+ PrepareChartTable(AChart, auxSheet);
+
+ // Header rows (containing the series names)
+ AppendToStream(AStream,
+ ind + ' ' + LE +
+ ind + ' ' + LE );
+ for c := 0 to auxSheet.GetLastColIndex do
+ WriteAuxCell(AIndent + 6, auxSheet.FindCell(0, c));
+ AppendToStream(AStream,
+ ind + ' ' + LE +
+ ind + ' ' + LE
+ );
+
+ // Write data rows
+ AppendToStream(AStream,
+ ind + ' ' + LE
+ );
+ for r := 1 to auxSheet.GetLastRowIndex do
+ begin
+ AppendToStream(AStream,
+ ind + ' ' + LE
+ );
+ for c := 0 to auxSheet.GetlastColIndex do
+ WriteAuxCell(AIndent + 6, auxSheet.FindCell(r, c));
+ AppendToStream(AStream,
+ ind + ' ' + LE
+ );
+ end;
+ AppendToStream(AStream,
+ ind + ' ' + LE +
+ ind + '' + LE
+ );
+
+ //auxBook.WriteToFile('table.ods', true);
+ finally
+ auxBook.Free;
+ end;
+end;
+
+{ Writes the chart's title (or subtitle, depending on the value of IsSubTitle)
+ to the xml stream (chart stream) and the corresponding style to the stylestream. }
+procedure TsSpreadOpenDocChartWriter.WriteChartTitle(AChartStream, AStyleStream: TStream;
+ AChartIndent, AStyleIndent: Integer; AChart: TsChart; IsSubtitle: Boolean;
+ var AStyleID: Integer);
+var
+ title: TsChartText;
+ captionKind: Integer;
+ elementName: String;
+ indent: String;
+begin
+ if IsSubTitle then
+ begin
+ title := AChart.SubTitle;
+ elementName := 'subtitle';
+ captionKind := 2;
+ end else
+ begin
+ title := AChart.Title;
+ elementName := 'title';
+ captionKind := 1;
+ end;
+
+ if (not title.Visible) or (title.Caption = '') then
+ exit;
+
+ // Write title properties
+ indent := DupeString(' ', AChartIndent);
+ AppendToStream(AChartStream, Format(
+ indent + '' + LE +
+ indent + ' %s' + LE +
+ indent + '' + LE,
+ [ elementName, AStyleID, title.Caption, elementName ], FPointSeparatorSettings
+ ));
+
+ // Write title style
+ AppendToStream(AStyleStream,
+ GetChartCaptionStyleAsXML(AChart, captionKind, AStyleIndent, AStyleID)
+ );
+
+ // Next style
+ inc(AStyleID);
+end;
+
+
+end.
+
diff --git a/components/fpspreadsheet/source/common/fpsreaderwriter.pas b/components/fpspreadsheet/source/common/fpsreaderwriter.pas
index 493bcf042..e195d290e 100644
--- a/components/fpspreadsheet/source/common/fpsreaderwriter.pas
+++ b/components/fpspreadsheet/source/common/fpsreaderwriter.pas
@@ -197,6 +197,19 @@ type
property NumFormatList: TStringList read FNumFormatList;
end;
+ {@@ Helper class for the spreadsheet writer to keep processing of charts
+ out of the main writer unit. }
+ TsBasicSpreadChartWriter = class
+ protected
+ FWriter: TsBasicSpreadWriter;
+ public
+ constructor Create(AWriter: TsBasicSpreadWriter); virtual;
+ procedure CreateStreams; virtual; abstract;
+ procedure DestroyStreams; virtual; abstract;
+ procedure ResetStreams; virtual; abstract;
+ procedure WriteCharts; virtual; abstract;
+ property Writer: TsBasicSpreadWriter read FWriter;
+ end;
type
TsSpreadFileAccess = (faRead, faWrite);
@@ -912,6 +925,21 @@ begin
raise Exception.Create(rsUnsupportedWriteFormat);
end;
+
+{------------------------------------------------------------------------------}
+{ TsBasicSpreadChartWriter }
+{------------------------------------------------------------------------------}
+
+{@@ Constructor of the ChartWriter. It gets the main writer as parameter. }
+constructor TsBasicSpreadChartWriter.Create(AWriter: TsBasicSpreadWriter);
+begin
+ FWriter := AWriter;
+end;
+
+
+{------------------------------------------------------------------------------}
+{ TsSpreadFormatRegistry }
+{------------------------------------------------------------------------------}
type
TsSpreadFormatData = class
private
diff --git a/components/fpspreadsheet/source/common/fpsxmlcommon.pas b/components/fpspreadsheet/source/common/fpsxmlcommon.pas
index 40adb6cd7..e461c65fb 100644
--- a/components/fpspreadsheet/source/common/fpsxmlcommon.pas
+++ b/components/fpspreadsheet/source/common/fpsxmlcommon.pas
@@ -18,6 +18,9 @@ uses
{$ENDIF}
fpstypes, fpsreaderwriter;
+const
+ XML_HEADER = '';
+
type
TsSpreadXMLReader = class(TsCustomSpreadReader)
protected