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;
FNumberFormatList: TStrings;
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 GetChartRegressionEquationStyleAsXML(AChart: TsChart;
AEquation: TsRegressionEquation; AIndent, AStyleID: Integer): String;
function GetChartRegressionStyleAsXML(AChart: TsChart; ASeriesIndex, AIndent, AStyleID: Integer): String;
function GetChartSeriesStyleAsXML(AChart: TsChart; ASeriesIndex, AIndent, AStyleID: integer): String;
// function GetChartTitleStyleAsXML(AChart: TsChart; AStyleIndex, AIndent: Integer): String;
function GetNumberFormatID(ANumFormat: String): String;
procedure ListAllNumberFormats(AChart: TsChart);
procedure PrepareChartTable(AChart: TsChart; AWorksheet: TsBasicWorksheet);
protected
// Object X/styles.xml
procedure WriteObjectStyles(AStream: TStream; AChart: TsChart);
procedure WriteObjectGradientStyles(AStream: TStream; AChart: TsChart; AIndent: Integer);
procedure WriteObjectLineStyles(AStream: TStream; AChart: TsChart; AIndent: Integer);
// Object X/content.xml
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 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); override;
destructor Destroy; override;
procedure AddChartsToZip(AZip: TZipper);
procedure AddToMetaInfManifest(AStream: TStream);
procedure CreateStreams; override;
procedure DestroyStreams; override;
procedure ResetStreams; override;
procedure WriteCharts; override;
end;
implementation
uses
fpsOpenDocument;
type
TAxisKind = 3..6;
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',
'radar', 'filled-radar', 'circle', 'ring'
);
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
GRADIENT_STYLES: array[TsChartGradientStyle] of string = (
'linear', 'axial', 'radial', 'ellipsoid', 'square', 'rectangular'
);
LABEL_POSITION: array[TsChartLabelPosition] of string = (
'', 'outside', 'inside', 'center');
LEGEND_POSITION: array[TsChartLegendPosition] of string = (
'end', 'top', 'bottom', 'start'
);
AXIS_ID: array[TAxisKind] of string = ('x', 'y', 'x', 'y');
AXIS_LEVEL: array[TAxisKind] of string = ('primary', 'primary', 'secondary', 'secondary');
FALSE_TRUE: array[boolean] of string = ('false', 'true');
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;
{------------------------------------------------------------------------------}
{ internal number formats }
{------------------------------------------------------------------------------}
type
TsChartNumberFormatList = class(TStringList)
public
constructor Create;
function Add(const ANumFormat: String): Integer; override;
end;
constructor TsChartNumberFormatList.Create;
begin
inherited;
Add(''); // default number format
end;
// Adds a new format, but make sure to avoid duplicates.
function TsChartNumberFormatList.Add(const ANumFormat: String): Integer;
begin
if (ANumFormat = '') and (Count > 0) then
Result := 0
else
begin
Result := IndexOf(ANumFormat);
if Result = -1 then
Result := inherited Add(ANumFormat);
end;
end;
{------------------------------------------------------------------------------}
{ TsSpreadOpenDocChartWriter }
{------------------------------------------------------------------------------}
constructor TsSpreadOpenDocChartWriter.Create(AWriter: TsBasicSpreadWriter);
begin
inherited Create(AWriter);
FPointSeparatorSettings := SysUtils.DefaultFormatSettings;
FPointSeparatorSettings.DecimalSeparator:='.';
FNumberFormatList := TsChartNumberFormatList.Create;
end;
destructor TsSpreadOpenDocChartWriter.Destroy;
begin
FNumberFormatList.Free;
inherited;
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;
{ Writes the chart entries needed in the META-INF/manifest.xml file }
procedure TsSpreadOpenDocChartWriter.AddToMetaInfManifest(AStream: TStream);
var
i: Integer;
begin
for i:=0 to TsWorkbook(Writer.Workbook).GetChartCount-1 do
begin
AppendToStream(AStream, Format(
' ' + LE,
[i+1]
));
AppendToStream(AStream, Format(
' ' + LE,
[i+1]
));
AppendToStream(AStream, Format(
' ' + LE,
[i+1]
));
// Object X/meta.xml and ObjectReplacement/Object X are not necessarily needed.
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;
idx: Integer;
textProps: String = '';
graphProps: String = '';
chartProps: String = '';
numStyle: String = 'N0';
begin
Result := '';
if not Axis.Visible then
exit;
chart := Axis.Chart;
// Get number format, use percent format for stacked percentage axis
if (Axis = chart.YAxis) and (chart.StackMode = csmStackedPercentage) then
numStyle := GetNumberFormatID(Axis.LabelFormatPercent)
else
numStyle := GetNumberFormatID(Axis.LabelFormat);
// Show axis labels
if Axis.ShowLabels then
chartProps := chartProps + 'chart:display-label="true" ';
// Logarithmic axis
if Axis.Logarithmic then
chartProps := chartProps + 'chart:logarithmic="true" ';
// Axis scaling: minimum, maximum, tick intervals
if not Axis.AutomaticMin then
chartProps := chartProps + Format('chart:minimum="%g" ', [Axis.Min], FPointSeparatorSettings);
if not Axis.AutomaticMax then
chartProps := chartProps + Format('chart:maximum="%g" ', [Axis.Max], FPointSeparatorSettings);
if not Axis.AutomaticMajorInterval then
chartProps := chartProps + Format('chart:interval-major="%g" ', [Axis.MajorInterval], FPointSeparatorSettings);
if not Axis.AutomaticMinorSteps then
chartProps := chartProps + Format('chart:interval-minor-divisor="%d" ', [Axis.MinorSteps]);
// Position of the axis
case Axis.Position of
capStart: chartProps := chartProps + 'chart:axis-position="start" ';
capEnd: chartProps := chartProps + 'chart:axis-position="end" ';
capValue: chartProps := chartProps + Format('chart:axis-position="%g" ', [Axis.PositionValue], FPointSeparatorSettings);
end;
// Tick marks
if (chart.GetChartType in [ctRadar, ctFilledRadar]) and (Axis = chart.YAxis) then
begin
// Radar series needs a "false" to hide the tick-marks
chartProps := chartProps + Format('chart:tick-marks-major-inner="%s" ', [FALSE_TRUE[catInside in Axis.MajorTicks]]);
chartProps := chartProps + Format('chart:tick-marks-major-outer="%s" ', [FALSE_TRUE[catOutside in Axis.MajorTicks]]);
chartProps := chartProps + Format('chart:tick-marks-minor-inner="%s" ', [FALSE_TRUE[catInside in Axis.MinorTicks]]);
chartProps := chartProps + Format('chart:tick-marks-minor-outer="%s" ', [FALSE_TRUE[catOutside in Axis.MinorTicks]]);
end else
begin
// The other series hide the tick-marks by default.
if (catInside in Axis.MajorTicks) then
chartProps := chartProps + 'chart:tick-marks-major-inner="true" ';
if (catOutside in Axis.MajorTicks) then
chartProps := chartProps + 'chart:tick-marks-major-outer="true" ';
if (catInside in Axis.MinorTicks) then
chartProps := chartProps + 'chart:tick-marks-minor-inner="true" ';
if (catOutside in Axis.MinorTicks) then
chartProps := chartProps + 'chart:tick-marks-minor-outer="true" ';
end;
// Inverted axis direction
if Axis.Inverted then
chartProps := chartProps + 'chart:reverse-direction="true" ';
// Rotated axis labels
angle := Axis.LabelRotation;
chartProps := chartProps + Format('style:rotation-angle="%d" ', [angle]);
// Label orientation
graphProps := 'svg:stroke-color="' + ColorToHTMLColorStr(Axis.AxisLine.Color) + '" ';
// Label font
textProps := TsSpreadOpenDocWriter(Writer).WriteFontStyleXMLAsString(Axis.LabelFont);
// Putting it all together...
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;
fillOpacity: String = '';
gradient: TsChartGradient;
gradientStr: String;
begin
case AFill.Style of
cfsNoFill:
Result := 'draw:fill="none" ';
cfsSolid:
begin
fillStr := 'draw:fill="solid" ';
fillColorStr := 'draw:fill-color="' + ColorToHTMLColorStr(AFill.FgColor) + '" ';
if AFill.Transparency > 0 then
fillOpacity := Format('draw:opacity="%.0f%%" ', [(1.0 - AFill.Transparency)*100], FPointSeparatorSettings);
Result := fillStr + fillColorStr + fillOpacity;
end;
cfsGradient:
begin
gradient := AChart.Gradients[AFill.Gradient];
Result := Format(
'draw:fill="gradient" ' +
'draw:fill-gradient-name="%s" ' +
'draw:gradient-step-count="0" ',
[ ASCIIName(gradient.Name) ]
);
end;
end;
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 = '';
rightAngledAxes: 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;
if not (AChart.GetChartType in [ctRadar, ctPie]) then
rightAngledAxes := 'chart:right-angled-axes="true" ';
Result := Format(
indent + ' ', [ AStyleID ]) + LE +
indent + ' ' + LE +
indent + ' ' + LE;
end;
function TsSpreadOpenDocChartWriter.GetChartRegressionEquationStyleAsXML(
AChart: TsChart; AEquation: TsRegressionEquation; AIndent, AStyleID: Integer): String;
var
indent: String;
idx: Integer;
numStyle: String = 'N0';
chartprops: String = '';
lineprops: String = '';
fillprops: String = '';
textprops: String = '';
begin
Result := '';
indent := DupeString(' ', AIndent);
numStyle := GetNumberFormatID(AEquation.NumberFormat);
if not AEquation.DefaultXName then
chartprops := chartprops + Format('loext:regression-x-name="%s" ', [AEquation.XName]);
if not AEquation.DefaultYName then
chartprops := chartprops + Format('loext:regression-y-name="%s" ', [AEquation.YName]) ;
if not AEquation.DefaultBorder then
lineProps := GetChartLineStyleGraphicPropsAsXML(AChart, AEquation.Border);
if not AEquation.DefaultFill then
fillProps := GetChartFillStyleGraphicPropsAsXML(AChart, AEquation.Fill);
if not AEquation.DefaultFont then
textprops := TsSpreadOpenDocWriter(Writer).WriteFontStyleXMLAsString(AEquation.Font);
Result := Format(
indent + '' + LE +
indent + ' ' + LE +
indent + ' ' + LE +
indent + ' ' + LE +
indent + '' + LE,
[ AStyleID, numStyle, chartprops, fillprops + lineprops, textprops ]
);
end;
function TsSpreadOpenDocChartWriter.GetChartRegressionStyleAsXML(AChart: TsChart;
ASeriesIndex, AIndent, AStyleID: Integer): String;
const
REGRESSION_TYPE: array [TsRegressionType] of string = (
'', 'linear', 'logarithmic', 'exponential', 'power', 'polynomial');
var
series: TsScatterSeries;
indent: String;
chartProps: String = '';
graphProps: String = '';
textProps: String = '';
lineProps: String = '';
fillProps: String = '';
labelSeparator: String = '';
begin
Result := '';
series := AChart.Series[ASeriesIndex] as TsScatterSeries;
if series.Regression.RegressionType = rtNone then
exit;
indent := DupeString(' ', AIndent);
chartprops := Format(
'chart:regression-name="%s" ' +
'chart:regression-type="%s" ' +
'chart:regression-extrapolate-forward="%g" ' +
'chart:regression-extrapolate-backward="%g" ' +
'chart:regression-force-intercept="%s" ' +
'chart:regression-intercept-value="%g" ' +
'chart:regression-max-degree="%d" ',
[ series.Regression.Title,
REGRESSION_TYPE[series.Regression.RegressionType] ,
series.Regression.ExtrapolateForwardBy,
series.Regression.ExtrapolateBackwardBy,
FALSE_TRUE[series.Regression.ForceYIntercept],
series.Regression.YInterceptValue,
series.Regression.PolynomialDegree
], FPointSeparatorSettings
);
graphprops := GetChartLineStyleGraphicPropsAsXML(AChart, series.Regression.Line);
Result := Format(
indent + ' ' + LE +
indent + ' ' + LE +
indent + ' ' + LE +
indent + '' + LE,
[ AStyleID, chartprops, graphprops ]
);
end;
{
}
function TsSpreadOpenDocChartWriter.GetChartSeriesStyleAsXML(AChart: TsChart;
ASeriesIndex, AIndent, AStyleID: Integer): String;
var
series: TsChartSeries;
lineser: TsLineSeries = nil;
indent: String;
numStyle: String;
chartProps: String = '';
graphProps: String = '';
textProps: String = '';
lineProps: String = '';
fillProps: String = '';
labelSeparator: String = '';
begin
Result := '';
indent := DupeString(' ', AIndent);
series := AChart.Series[ASeriesIndex];
// Number format
numStyle := GetNumberFormatID(series.LabelFormat);
// Chart properties
chartProps := 'chart:symbol-type="none" ';
if (series is TsLineSeries) and (series.ChartType <> ctFilledRadar) 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" ',
[SYMBOL_NAMES[lineSer.Symbol], lineSer.SymbolWidth, lineSer.SymbolHeight ],
FPointSeparatorSettings
);
end;
chartProps := chartProps + Format('chart:link-data-style-to-source="%s" ', [FALSE_TRUE[numStyle = 'N0']]);
if ([cdlValue, cdlPercentage] * series.DataLabels = [cdlValue]) then
chartProps := chartProps + 'chart:data-label-number="value" '
else
if ([cdlValue, cdlPercentage] * series.DataLabels = [cdlPercentage]) then
chartProps := chartProps + 'chart:data-label-number="percentage" '
else
if ([cdlValue, cdlPercentage] * series.DataLabels = [cdlValue, cdlPercentage]) then
chartProps := chartProps + 'chart:data-label-number="value-and-percentage" ';
if (cdlCategory in series.DataLabels) then
chartProps := chartProps + 'chart:data-label-text="true" ';
if (cdlSeriesName in series.DataLabels) then
chartProps := chartProps + 'chart:data-label-series="true" ';
if (cdlSymbol in series.DataLabels) then
chartProps := chartProps + 'chart:data-label-symbol="true" ';
if series.LabelPosition <> lpDefault then
chartProps := chartProps + 'chart:label-position="' + LABEL_POSITION[series.LabelPosition] + '" ';
if series.LabelSeparator = ' ' then
labelSeparator := ''
else
begin
labelSeparator := series.LabelSeparator;
if pos('\n', labelSeparator) > 0 then
labelSeparator := StringReplace(labelSeparator, '\n', '', [rfReplaceAll, rfIgnoreCase]);
labelSeparator :=
' ' + LE +
' ' + labelSeparator + '' + LE +
' ' + LE;
end;
if labelSeparator <> '' then
chartProps := ' ' + LE + labelSeparator + ' '
else
chartProps := ' ';
// Graphic properties
lineProps := GetChartLineStyleGraphicPropsAsXML(AChart, series.Line);
fillProps := GetChartFillStyleGraphicPropsAsXML(AChart, series.Fill);
if (series is TsLineSeries) and (series.ChartType <> ctFilledRadar) then
begin
if lineSer.ShowSymbols then
graphProps := graphProps + fillProps;
if lineSer.ShowLines and (lineser.Line.Style <> clsNoLine) then
graphProps := graphProps + lineProps
else
graphProps := graphProps + 'draw:stroke="none" ';
end else
graphProps := fillProps + lineProps;
// Text properties
textProps := TsSpreadOpenDocWriter(Writer).WriteFontStyleXMLAsString(series.LabelFont);
Result := Format(
indent + '' + LE +
indent + chartProps + LE +
indent + ' ' + LE +
indent + ' ' + LE +
indent + '' + LE,
[ AStyleID, numstyle, graphProps, textProps ]
);
end;
function TsSpreadOpenDocChartWriter.GetNumberFormatID(ANumFormat: String): String;
var
idx: Integer;
begin
idx := FNumberFormatList.IndexOf(ANumFormat);
if idx > -1 then
Result := Format('N%d', [idx])
else
Result := 'N0';
end;
procedure TsSpreadOpenDocChartWriter.ListAllNumberFormats(AChart: TsChart);
var
i: Integer;
series: TsChartSeries;
regression: TsChartRegression;
begin
FNumberFormatList.Clear;
// Formats of axis labels
FNumberFormatList.Add(AChart.XAxis.LabelFormat);
FNumberFormatList.Add(AChart.YAxis.LabelFormat);
FNumberFormatList.Add(AChart.X2Axis.LabelFormat);
FNumberFormatList.Add(AChart.Y2Axis.LabelFormat);
if AChart.StackMode = csmStackedPercentage then
begin
FNumberFormatList.Add(AChart.YAxis.LabelFormatPercent);
FNumberFormatList.Add(AChart.Y2Axis.LabelFormatPercent);
end;
// Formats of series labels
for i := 0 to AChart.Series.Count-1 do
begin
series := AChart.Series[i];
FNumberFormatList.Add(series.LabelFormat);
// Format of fit equation
if (series is TsScatterSeries) then begin
regression := TsScatterSeries(series).Regression;
if (regression.RegressionType <> rtNone) and
(regression.DisplayEquation or regression.DisplayRSquare) then
begin
FNumberFormatList.Add(regression.Equation.NumberFormat);
end;
end;
end;
end;
{ Extracts the cells needed by the given chart from the chart's worksheet and
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
chartStream := TMemoryStream.Create;
styleStream := TMemoryStream.Create;
try
ListAllNumberFormats(AChart);
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);
// wp: writing this makes no change except the series fills not being applied
// 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);
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, for each gradient used by the chart, a node to the Object/styles xml file }
procedure TsSpreadOpenDocChartWriter.WriteObjectGradientStyles(AStream: TStream;
AChart: TsChart; AIndent: Integer);
var
i: Integer;
gradient: TsChartGradient;
style: String;
indent: String;
begin
indent := DupeString(' ', AIndent);
for i := 0 to AChart.Gradients.Count-1 do
begin
gradient := AChart.Gradients[i];
style := indent + Format(
'' + LE;
AppendToStream(AStream, style);
end;
end;
procedure TsSpreadOpenDocChartWriter.WriteObjectLineStyles(AStream: TStream;
AChart: TsChart; AIndent: Integer);
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;
indent: String;
begin
indent := DupeString(' ', AIndent);
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, indent + Format(
'' + LE, [
ASCIIName(linestyle.Name), linestyle.Name,
DECS[linestyle.RelativeToLineWidth], linestyle.Distance, LENGTH_UNIT[linestyle.RelativeToLineWidth],
seg1, seg2
], FPointSeparatorSettings
));
end;
end;
{ Writes the chart's legend to the xml stream }
procedure TsSpreadOpenDocChartWriter.WriteChartLegend(AChartStream, AStyleStream: TStream;
AChartIndent, AStyleIndent: Integer; AChart: TsChart; var AStyleID: Integer);
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;
numFmtName: String;
numFmtStr: String;
numFmtXML: String;
i: Integer;
parser: TsSpreadOpenDocNumFormatParser;
begin
indent := DupeString(' ', AIndent);
for i := 0 to FNumberFormatList.Count-1 do begin
numFmtName := Format('N%d', [i]);
numFmtStr := FNumberFormatList[i];
parser := TsSpreadOpenDocNumFormatParser.Create(numFmtStr, FWriter.Workbook.FormatSettings);
try
numFmtXML := parser.BuildXMLAsString(numFmtName);
if numFmtXML <> '' then
AppendToStream(AStream, indent + numFmtXML);
finally
parser.Free;
end;
end;
{
AppendToStream(AStream,
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, or gradients. }
procedure TsSpreadOpenDocChartWriter.WriteObjectStyles(AStream: TStream;
AChart: TsChart);
begin
AppendToStream(AStream,
XML_HEADER + LE);
AppendToStream(AStream,
'' + LE
);
AppendToStream(AStream,
' ' + LE
);
WriteObjectLineStyles(AStream, AChart, 4);
WriteObjectGradientStyles(AStream, AChart, 4);
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 = '';
fillColorRange: String = '';
chartClass: String = '';
regressionEquation: String = '';
needRegressionStyle: Boolean = false;
needRegressionEquationStyle: Boolean = false;
regression: TsChartRegression = nil;
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
);
// Fill colors for bars, line series symbols, bubbles
if (series.FillColorRange.Row1 <> series.FillColorRange.Row2) or
(series.FillColorRange.Col1 <> series.FillColorRange.Col2)
then
fillColorRange := GetSheetCellRangeString_ODS(
sheet.Name, sheet.Name,
series.FillColorRange.Row1, series.FillColorRange.Col1,
series.FillColorRange.Row2, series.FillColorRange.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;
if series is TsRingSeries then
chartClass := 'circle'
else
chartClass := CHART_TYPE_NAMES[series.ChartType];
// Store the series properties
AppendToStream(AChartStream, Format(
indent + '' + LE, // series title
[ AStyleID, chartClass, valuesRange, titleAddr, chartClass ]
));
if domainRangeY <> '' then
AppendToStream(AChartStream, Format(
indent + '' + LE,
[ domainRangeY ]
));
if domainRangeX <> '' then
AppendToStream(AChartStream, Format(
indent + '' + LE,
[ domainRangeX ]
));
if fillColorRange <> '' then
AppendToStream(AChartStream, Format(
indent + '' + LE,
[ fillColorRange ]
));
{ --- not working...
if borderColorRange <> '' then
AppendToStream(AChartStream, Format(
indent + '' + LE,
[ borderColorRange ]
));
}
// Regression
if (series is TsScatterSeries) then
begin
regression := TsScatterSeries(series).Regression;
if regression.RegressionType <> rtNone then
begin
if regression.DisplayEquation or regression.DisplayRSquare then
begin
if (not regression.Equation.DefaultXName) or (not regression.Equation.DefaultYName) or
(not regression.Equation.DefaultBorder) or (not regression.Equation.DefaultFill) or
(not regression.Equation.DefaultFont) or (not regression.Equation.DefaultNumberFormat) or
(not regression.Equation.DefaultPosition) then
begin
regressionEquation := regressionEquation + Format('chart:style-name="ch%d" ', [AStyleID + 2]);
needRegressionEquationStyle := true;
end;
end;
if regression.DisplayEquation then
regressionEquation := regressionEquation + 'chart:display-equation="true" ';
if regression.DisplayRSquare then
regressionEquation := regressionEquation + 'chart:display-r-square="true" ';
if regressionEquation <> '' then
begin
if not regression.Equation.DefaultPosition then
regressionEquation := regressionEquation + Format(
'svg:x="%.2fmm" svg:y="%.2fmm" ',
[ regression.Equation.Left, regression.Equation.Top ],
FPointSeparatorSettings
);
AppendToStream(AChartStream, Format(
indent + ' ' + LE +
indent + ' ' + LE +
indent + ' ' + LE,
[ AStyleID + 1, regressionEquation ]
))
end else
AppendToStream(AChartStream, Format(
indent + ' ',
[ AStyleID + 1 ]
));
needRegressionStyle := true;
end;
end;
AppendToStream(AChartStream, Format(
indent + ' ' + LE,
[ count ]
));
AppendToStream(AChartStream,
indent + '' + LE
);
// Series style
AppendToStream(AStyleStream,
GetChartSeriesStyleAsXML(AChart, ASeriesIndex, AStyleIndent, AStyleID)
);
// Regression style
if needRegressionStyle then
begin
inc(AStyleID);
AppendToStream(AStyleStream,
GetChartRegressionStyleAsXML(AChart, ASeriesIndex, AStyleIndent, AStyleID)
);
// Style of regression equation
if needRegressionEquationStyle then
begin
inc(AStyleID);
AppendToStream(AStyleStream,
GetChartRegressionEquationStyleAsXML(AChart, regression.Equation, AStyleIndent, AStyleID)
);
end;
end;
// 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);
WriteObjectStyles(FSObjectStyles[i], chart);
end;
end;
(* wp:
DO NOT DELETE THIS - IT WAS A PAINT TO GET THIS, AND MAYBE IT WILL BE NEEDED
LATER.
AT THE MOMENT THIS IS NOT NEEDED, IN FACT, IT IS EVEN DETRIMENTAL:
WITH THIS CODE INCLUDED, SERIES FILL ARE IGNORED AND TITLES ARE NOT CORRECT.
{ 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.