lazarus-ccr/components/fpspreadsheet/source/common/fpsopendocumentchart.pas
2023-10-29 12:10:50 +00:00

1456 lines
48 KiB
ObjectPascal

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 + '<style:style style:name="ch%d" style:family="chart" style:data-style-name="%s">' + LE +
indent + ' <style:chart-properties %s/>' + LE +
indent + ' <style:graphic-properties %s/>' + LE +
indent + ' <style:text-properties %s/>' + LE +
indent + '</style:style>' + 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 + '<style:style style:name="ch%d" style:family="chart">' + LE +
indent + ' <style:graphic-properties %s%s />' + LE +
indent + '</style:style>' + LE,
[ AStyleID, fillStr, borderStr ]
);
end;
{ <style:style style:name="ch7" style:family="chart">
<style:chart-properties chart:auto-position="true" style:rotation-angle="0"/>
<style:text-properties fo:font-size="9pt" style:font-size-asian="9pt" style:font-size-complex="9pt"/>
</style:style>
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 + '<style:style style:name="ch%d" style:family="chart">' + LE +
indent + ' <style:chart-properties %s/>' + LE +
indent + ' <style:text-properties %s/>' + LE +
indent + '</style:style>' + 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;
{
<style:style style:name="ch4" style:family="chart">
<style:chart-properties chart:auto-position="true"/>
<style:graphic-properties svg:stroke-color="#b3b3b3" draw:fill="none"
draw:fill-color="#e6e6e6"/>
<style:text-properties fo:font-family="Consolas"
style:font-style-name="Standard" style:font-family-generic="modern"
style:font-pitch="fixed" fo:font-size="12pt"
style:font-size-asian="10pt" style:font-size-complex="10pt"/>
</style:style>
}
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 + '<style:style style:name="ch%d" style:family="chart">' + LE +
indent + ' <style:chart-properties />' + LE +
indent + ' <style:graphic-properties %s/>' + LE +
indent + ' <style:text-properties %s/>' + LE +
indent + '</style:style>' + LE,
[ AStyleID, graphProps, textProps ]
);
end;
{ <style:style style:name="ch12" style:family="chart">
<style:graphic-properties draw:stroke="dash" draw:stroke-dash="Dot"
svg:stroke-color="#ff0000"/>
</style:style> }
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 + '<style:style style:name="ch%d" style:family="chart">' + LE +
ind + ' <style:graphic-properties %s/>' + LE +
ind + '</style:style>' + LE,
[ AStyleID, graphProps ]
);
end;
{ Constructs the xml for a line style to be used in the <style:graphic-properties> }
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 + ' <style:style style:name="ch%d" style:family="chart">', [ AStyleID ]) + LE +
indent + ' <style:chart-properties ' +
interpolationStr +
verticalStr +
stackModeStr +
'chart:symbol-type="automatic" ' +
'chart:include-hidden-cells="false" ' +
'chart:auto-position="true" ' +
'chart:auto-size="true" ' +
'chart:treat-empty-cells="leave-gap" ' +
'chart:right-angled-axes="true"/>' + LE +
indent + ' </style:style>' + LE;
end;
{ <style:style style:name="ch1400" style:family="chart" style:data-style-name="N0">
<style:chart-properties
chart:symbol-type="named-symbol"
chart:symbol-name="arrow-down"
chart:symbol-width="0.25cm"
chart:symbol-height="0.25cm"
chart:link-data-style-to-source="true"/>
<style:graphic-properties
svg:stroke-width="0.08cm"
svg:stroke-color="#ffd320"
draw:fill-color="#ffd320"
dr3d:edge-rounding="5%"/>
<style:text-properties fo:font-size="10pt" style:font-size-asian="10pt" style:font-size-complex="10pt"/>
</style:style> }
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 + '<style:style style:name="ch%d" style:family="chart" style:data-style-name="N0">' + LE +
indent + ' <style:chart-properties %s/>' + LE +
indent + ' <style:graphic-properties %s/>' + LE +
indent + ' <style:text-properties %s/>' + LE +
indent + '</style:style>' + 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,
'<office:document-content ' + LE +
' xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0"' + LE +
' xmlns:ooo="http://openoffice.org/2004/office"' + LE +
' xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0"' + LE +
' xmlns:xlink="http://www.w3.org/1999/xlink"' + LE +
' xmlns:dc="http://purl.org/dc/elements/1.1/"' + LE +
' xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0"' + LE +
' xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0"' + LE +
' xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0"' + LE +
' xmlns:rpt="http://openoffice.org/2005/report"' + LE +
' xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0"' + LE +
' xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0"' + LE +
' xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0"' + LE +
' xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0"' + LE +
' xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0"' + LE +
' xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0"' + LE +
' xmlns:ooow="http://openoffice.org/2004/writer"' + LE +
' xmlns:oooc="http://openoffice.org/2004/calc"' + LE +
' xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2"' + LE +
' xmlns:xforms="http://www.w3.org/2002/xforms"' + LE +
' xmlns:tableooo="http://openoffice.org/2009/table"' + LE +
' xmlns:calcext="urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0"' + LE +
' xmlns:drawooo="http://openoffice.org/2010/draw"' + LE +
' xmlns:xhtml="http://www.w3.org/1999/xhtml"' + LE +
' xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0"' + LE +
' xmlns:field="urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0"' + LE +
' xmlns:math="http://www.w3.org/1998/Math/MathML"' + LE +
' xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0"' + LE +
' xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0"' + LE +
' xmlns:formx="urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0"' + LE +
' xmlns:dom="http://www.w3.org/2001/xml-events"' + LE +
' xmlns:xsd="http://www.w3.org/2001/XMLSchema"' + LE +
' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"' + LE +
' xmlns:grddl="http://www.w3.org/2003/g/data-view#"' + LE +
' xmlns:css3t="http://www.w3.org/TR/css3-text/"' + LE +
' xmlns:chartooo="http://openoffice.org/2010/chart" office:version="1.3">' + LE
);
// The file begins with the chart styles
AppendToStream(AStream,
' <office:automatic-styles>' + 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,
' </office:automatic-styles>' + LE +
' <office:body>' + LE +
' <office:chart>' + 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,
' </chart:chart>' + LE +
' </office:chart>' + LE +
' </office:body>' + LE +
'</office:document-content>');
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 + '<chart:axis chart:style-name="ch%d" chart:dimension="%s" chart:name="%s-%s" chartooo:axis-type="auto">' + 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 + ' <chart:categories table:cell-range-address="%s"/>' + 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 + ' <chart:title chart:style-name="ch%d">' + LE +
indent + ' <text:p>%s</text:p>' + LE +
indent + ' </chart:title>' + 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 + ' <chart:grid chart:style-name="ch%d" chart:class="major"/>' + 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 + ' <chart:grid chart:style-name="ch%d" chart:class="minor"/>' + 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 + '</chart:axis>' + 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 + '<chart:chart chart:style-name="ch%d" %s' + LE +
indent + ' svg:width="%.3fmm" svg:height="%.3fmm" ' + LE +
indent + ' xlink:href=".." xlink:type="simple">' + 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 + '<chart:legend chart:style-name="ch%d" chart:legend-position="%s" style:legend-expansion="high" %s/>' + 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 + '<number:number-style style:name="N0">' + LE +
indent + ' <number:number number:min-integer-digits="1"/>' + LE +
indent + '</number:number-style>' + LE
);
if AChart.StackMode = csmStackedPercentage then
AppendToStream(AStream,
indent + '<number:percentage-style style:name="N10010">' + LE +
indent + ' <number:number number:decimal-places="0" number:min-decimal-places="0" number:min-integer-digits="1"/>' + LE +
indent + ' <number:text>%</number:text>' + LE +
indent + '</number:percentage-style>' + 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,
'<office:document-styles ' + LE +
' xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0"' + LE +
' xmlns:ooo="http://openoffice.org/2004/office"' + LE +
' xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0"' + LE +
' xmlns:xlink="http://www.w3.org/1999/xlink"' + LE +
' xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0"' + LE +
' xmlns:dc="http://purl.org/dc/elements/1.1/"' + LE +
' xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0"' + LE +
' xmlns:rpt="http://openoffice.org/2005/report"' + LE +
' xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0"' + LE +
' xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0"' + LE +
' xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0"' + LE +
' xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0"' + LE +
' xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0"' + LE +
' xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0"' + LE +
' xmlns:ooow="http://openoffice.org/2004/writer"' + LE +
' xmlns:oooc="http://openoffice.org/2004/calc"' + LE +
' xmlns:css3t="http://www.w3.org/TR/css3-text/"' + LE +
' xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2"' + LE +
' xmlns:tableooo="http://openoffice.org/2009/table"' + LE +
' xmlns:calcext="urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0"' + LE +
' xmlns:drawooo="http://openoffice.org/2010/draw"' + LE +
' xmlns:xhtml="http://www.w3.org/1999/xhtml"' + LE +
' xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0"' + LE +
' xmlns:grddl="http://www.w3.org/2003/g/data-view#"' + LE +
' xmlns:field="urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0"' + LE +
' xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0"' + LE +
' xmlns:dom="http://www.w3.org/2001/xml-events"' + LE +
' xmlns:chartooo="http://openoffice.org/2010/chart" office:version="1.3">' + LE
);
AppendToStream(AStream,
' <office:styles>' + 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(
' <draw:stroke-dash draw:name="%s" draw:display-name="%s" draw:style="round" draw:distance="%.*f%s" %s%s/>' + LE, [
ASCIIName(linestyle.Name), linestyle.Name,
DECS[linestyle.RelativeToLineWidth], linestyle.Distance, LENGTH_UNIT[linestyle.RelativeToLineWidth],
seg1, seg2
]));
end;
AppendToStream(AStream,
' </office:styles>' + LE +
'</office:document-styles>'
);
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 + '<chart:plot-area chart:style-name="ch%d" chart:data-source-has-labels="both">' + LE,
[ AStyleID ]
));
// Plot area style
AppendToStream(AStyleStream,
GetChartPlotAreaStyleAsXML(AChart, AStyleIndent, AStyleID)
);
// Next style
inc(AStyleID);
// Wall properties
AppendToStream(AChartStream, Format(
indent + ' <chart:wall chart:style-name="ch%d"/>' + 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 + ' <chart:floor chart:style-name="ch%d"/>' + 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 + '</chart:plot-area>' + 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 + '<chart:series chart:style-name="ch%d" ' +
'chart:values-cell-range-address="%s" ' + // y values
'chart:label-cell-address="%s" ' + // series title
'chart:class="chart:%s">' + LE,
[ AStyleID, valuesRange, titleAddr, CHART_TYPE_NAMES[series.ChartType] ]
));
if domainRangeY <> '' then
AppendToStream(AChartStream, Format(
indent + '<chart:domain table:cell-range-address="%s"/>' + LE,
[ domainRangeY ]
));
if domainRangeX <> '' then
AppendToStream(AChartStream, Format(
indent + '<chart:domain table:cell-range-address="%s"/>' + LE,
[ domainRangeX ]
));
AppendToStream(AChartStream, Format(
indent + ' <chart:data-point chart:repeated="%d"/>' + LE,
[ count ]
));
AppendToStream(AChartStream,
indent + '</chart:series>' + 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 + '<table:table-cell>' + LE +
ind + ' <text:p/>' + LE +
ind + '</table:table-cell>' + 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 + ' <draw:g>' + LE +
ind + ' <svg:desc>%s</svg:desc>' + LE +
ind + ' </draw:g>' + LE, [draw]);
end else
draw := '';
AppendToStream(AStream, Format(
ind + '<table:table-cell office:value-type="%s"%s>' + LE +
ind + ' <text:p>%s</text:p>' + LE +
draw +
ind + '</table:table-cell>' + 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 + '<table:table table:name="local-table">' + LE +
ind + ' <table:table-header-columns>' + LE +
ind + ' <table:table-column/>' + LE +
ind + ' </table:table-header-columns>' + LE +
ind + ' <table:table-columns>' + LE +
ind + ' <table:table-column %s/>' + LE +
ind + ' </table:table-columns>' + LE, [ colCountStr ]
));
auxBook := TsWorkbook.Create;
try
auxSheet := auxBook.AddWorksheet('chart');
PrepareChartTable(AChart, auxSheet);
// Header rows (containing the series names)
AppendToStream(AStream,
ind + ' <table:table-header-rows>' + LE +
ind + ' <table:table-row>' + LE );
for c := 0 to auxSheet.GetLastColIndex do
WriteAuxCell(AIndent + 6, auxSheet.FindCell(0, c));
AppendToStream(AStream,
ind + ' </table:table-row>' + LE +
ind + ' </table-header-rows>' + LE
);
// Write data rows
AppendToStream(AStream,
ind + ' <table:table-rows>' + LE
);
for r := 1 to auxSheet.GetLastRowIndex do
begin
AppendToStream(AStream,
ind + ' <table:table-row>' + LE
);
for c := 0 to auxSheet.GetlastColIndex do
WriteAuxCell(AIndent + 6, auxSheet.FindCell(r, c));
AppendToStream(AStream,
ind + ' </table:table-row>' + LE
);
end;
AppendToStream(AStream,
ind + ' </table:table-rows>' + LE +
ind + '</table:table>' + 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 + '<chart:%s chart:style-name="ch%d">' + LE +
indent + ' <text:p>%s</text:p>' + LE +
indent + '</chart:%s>' + LE,
[ elementName, AStyleID, title.Caption, elementName ], FPointSeparatorSettings
));
// Write title style
AppendToStream(AStyleStream,
GetChartCaptionStyleAsXML(AChart, captionKind, AStyleIndent, AStyleID)
);
// Next style
inc(AStyleID);
end;
end.