diff --git a/components/fpspreadsheet/laz_fpspreadsheet.lpk b/components/fpspreadsheet/laz_fpspreadsheet.lpk
index d8d0ef99b..a9d3f21da 100644
--- a/components/fpspreadsheet/laz_fpspreadsheet.lpk
+++ b/components/fpspreadsheet/laz_fpspreadsheet.lpk
@@ -33,7 +33,7 @@
This package is all you need if you don't want graphical components (such as grids and charts)."/>
-
+
@@ -297,6 +297,14 @@ This package is all you need if you don't want graphical components (such a
+
+
+
+
+
+
+
+
diff --git a/components/fpspreadsheet/source/common/fpschart.pas b/components/fpspreadsheet/source/common/fpschart.pas
new file mode 100644
index 000000000..a3ec0d3b9
--- /dev/null
+++ b/components/fpspreadsheet/source/common/fpschart.pas
@@ -0,0 +1,776 @@
+unit fpschart;
+
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+ Classes, SysUtils, Contnrs, fpsTypes, fpsUtils;
+
+const
+ clsNoLine = -2;
+ clsSolid = -1;
+
+{@@ Pre-defined chart line styles given as indexes into the chart's LineStyles
+ list. Get their value in the constructor of TsChart. Default here to -1
+ while is the code for a solid line, just in case that something goes wrong }
+var
+ clsFineDot: Integer = -1;
+ clsDot: Integer = -1;
+ clsDash: Integer = -1;
+ clsDashDot: Integer = -1;
+ clsLongDash: Integer = -1;
+ clsLongDashDot: Integer = -1;
+ clsLongDashDotDot: Integer = -1;
+
+type
+ TsChart = class;
+
+ TsChartFill = record
+ Style: TsFillStyle;
+ FgColor: TsColor;
+ BgColor: TsColor;
+ end;
+
+ TsChartLineSegment = record
+ Length: Double; // mm or % of linewidth
+ Count: Integer;
+ end;
+
+ TsChartLineStyle = class
+ Name: String;
+ Segment1: TsChartLineSegment;
+ Segment2: TsChartLineSegment;
+ Distance: Double; // mm or % of linewidth
+ RelativeToLineWidth: Boolean;
+ function GetID: String;
+ end;
+
+ TsChartLineStyleList = class(TFPObjectList)
+ private
+ function GetItem(AIndex: Integer): TsChartLineStyle;
+ procedure SetItem(AIndex: Integer; AValue: TsChartLineStyle);
+ public
+ function Add(AName: String;
+ ASeg1Length: Double; ASeg1Count: Integer;
+ ASeg2Length: Double; ASeg2Count: Integer;
+ ADistance: Double; ARelativeToLineWidth: Boolean): Integer;
+ property Items[AIndex: Integer]: TsChartLineStyle read GetItem write SetItem; default;
+ end;
+
+ TsChartLine = record
+ Style: Integer; // index into chart's LineStyle list or predefined clsSolid/clsNoLine
+ Width: Double; // mm
+ Color: TsColor; // in hex: $00bbggrr, r=red, g=green, b=blue
+ Transparency: Double; // in percent
+ end;
+
+ TsChartElement = class
+ private
+ FChart: TsChart;
+ FVisible: Boolean;
+ public
+ constructor Create(AChart: TsChart);
+ property Chart: TsChart read FChart;
+ property Visible: Boolean read FVisible write FVisible;
+ end;
+
+ TsChartFillElement = class(TsChartElement)
+ private
+ FBackground: TsChartFill;
+ FBorder: TsChartLine;
+ public
+ constructor Create(AChart: TsChart);
+ property Background: TsChartFill read FBackground write FBackground;
+ property Border: TsChartLine read FBorder write FBorder;
+ end;
+
+ TsChartText = class(TsChartFillElement)
+ private
+ FCaption: String;
+ FShowCaption: Boolean;
+ FFont: TsFont;
+ public
+ constructor Create(AChart: TsChart);
+ destructor Destroy; override;
+ property Caption: String read FCaption write FCaption;
+ property Font: TsFont read FFont write FFont;
+ property ShowCaption: Boolean read FShowCaption write FShowCaption;
+ end;
+
+ TsChartAxisPosition = (capStart, capEnd, capValue);
+ TsChartType = (ctEmpty, ctBar, ctLine, ctArea, ctBarLine, ctScatter);
+
+ TsChartAxis = class(TsChartText)
+ private
+ FAutomaticMax: Boolean;
+ FAutomaticMin: Boolean;
+ FAutomaticMajorInterval: Boolean;
+ FAutomaticMinorSteps: Boolean;
+ FAxisLine: TsChartLine;
+ FMajorGridLines: TsChartLine;
+ FMinorGridLines: TsChartline;
+ FInverted: Boolean;
+ FLabelFont: TsFont;
+ FLabelFormat: String;
+ FLogarithmic: Boolean;
+ FMajorInterval: Double;
+ FMajorTickLines: TsChartLine;
+ FMax: Double;
+ FMin: Double;
+ FMinorSteps: Double;
+ FMinorTickLines: TsChartLine;
+ FPosition: TsChartAxisPosition;
+ FPositionValue: Double;
+ FShowMajorGridLines: Boolean;
+ FShowMinorGridLines: Boolean;
+ FShowLabels: Boolean;
+ public
+ constructor Create(AChart: TsChart);
+ destructor Destroy; override;
+
+ property AutomaticMax: Boolean read FAutomaticMax write FAutomaticMax;
+ property AutomaticMin: Boolean read FAutomaticMin write FAutomaticMin;
+ property AutomaticMajorInterval: Boolean read FAutomaticMajorInterval write FAutomaticMajorInterval;
+ property AutomaticMinorSteps: Boolean read FAutomaticMinorSteps write FAutomaticMinorSteps;
+ property AxisLine: TsChartLine read FAxisLine write FAxisLine;
+ property Inverted: Boolean read FInverted write FInverted;
+ property LabelFont: TsFont read FLabelFont write FLabelFont;
+ property LabelFormat: String read FLabelFormat write FLabelFormat;
+ property Logarithmic: Boolean read FLogarithmic write FLogarithmic;
+ property MajorGridLines: TsChartLine read FMajorGridLines write FMajorGridLines;
+ property MajorInterval: Double read FMajorInterval write FMajorInterval;
+ property MajorTickLines: TsChartLine read FMajorTickLines write FMajorTickLines;
+ property Max: Double read FMax write FMax;
+ property Min: Double read FMin write FMin;
+ property MinorGrid: TsChartLine read FMinorGridLines write FMinorGridLines;
+ property MinorSteps: Double read FMinorSteps write FMinorSteps;
+ property MinorTickLines: TsChartLine read FMinorTickLines write FMinorTickLines;
+ property Position: TsChartAxisPosition read FPosition write FPosition;
+ property PositionValue: Double read FPositionValue write FPositionValue;
+ property ShowMajorGridLines: Boolean read FShowMajorGridLines write FShowMajorGridLines;
+ property ShowMinorGridLines: Boolean read FShowMinorGridLines write FShowMinorGridLines;
+ property ShowLabels: Boolean read FShowLabels write FShowLabels;
+ end;
+
+ TsChartLegend = class(TsChartText)
+ end;
+
+ TsChartAxisLink = (alPrimary, alSecondary);
+
+ TsChartSeries = class(TsChartElement)
+ private
+ FChartType: TsChartType;
+ FXRange: TsCellRange; // cell range containing the x data
+ FYRange: TsCellRange;
+ FLabelRange: TsCellRange;
+ FYAxis: TsChartAxisLink;
+ FTitleAddr: TsCellCoord;
+ FLabelFormat: String;
+ public
+ constructor Create(AChart: TsChart);
+ function GetCount: Integer;
+ function GetXCount: Integer;
+ function GetYCount: Integer;
+ function HasLabels: Boolean;
+ function HasXValues: Boolean;
+ function HasYValues: Boolean;
+ procedure SetTitleAddr(ARow, ACol: Cardinal);
+ procedure SetLabelRange(ARow1, ACol1, ARow2, ACol2: Cardinal);
+ procedure SetXRange(ARow1, ACol1, ARow2, ACol2: Cardinal);
+ procedure SetYRange(ARow1, ACol1, ARow2, ACol2: Cardinal);
+ function LabelsInCol: Boolean;
+ function XValuesInCol: Boolean;
+ function YValuesInCol: Boolean;
+ property ChartType: TsChartType read FChartType;
+ property Count: Integer read GetCount;
+ property LabelFormat: String read FLabelFormat write FLabelFormat; // Number format in Excel notation, e.g. '0.00'
+ property LabelRange: TsCellRange read FLabelRange;
+ property TitleAddr: TsCellCoord read FTitleAddr write FTitleAddr;
+ property XRange: TsCellRange read FXRange;
+ property YRange: TsCellRange read FYRange;
+ property YAxis: TsChartAxisLink read FYAxis write FYAxis;
+ end;
+
+ TsChartSeriesSymbol = (
+ cssRect, cssDiamond, cssTriangle, cssTriangleDown, cssCircle, cssStar
+ );
+
+ TsLineSeries = class(TsChartSeries)
+ private
+ FLineStyle: TsChartLine;
+ FShowLines: Boolean;
+ FShowSymbols: Boolean;
+ FSymbol: TsChartSeriesSymbol;
+ FSymbolFill: TsChartFill;
+ FSymbolBorder: TsChartLine;
+ FSymbolHeight: Double; // in mm
+ FSymbolWidth: Double; // in mm
+ public
+ constructor Create(AChart: TsChart);
+ property LineStyle: TsChartLine read FLineStyle write FLineStyle;
+ property ShowLines: Boolean read FShowLines write FShowLines;
+ property ShowSymbols: Boolean read FShowSymbols write FShowSymbols;
+ property Symbol: TsChartSeriesSymbol read FSymbol write FSymbol;
+ property SymbolBorder: TsChartLine read FSymbolBorder write FSymbolBorder;
+ property SymbolFill: TsChartFill read FSymbolFill write FSymbolFill;
+ property SymbolHeight: double read FSymbolHeight write FSymbolHeight;
+ property SymbolWidth: double read FSymbolWidth write FSymbolWidth;
+ end;
+
+ TsChartSeriesList = class(TFPList)
+ private
+ function GetItem(AIndex: Integer): TsChartSeries;
+ procedure SetItem(AIndex: Integer; AValue: TsChartSeries);
+ public
+ property Items[AIndex: Integer]: TsChartSeries read GetItem write SetItem; default;
+ end;
+
+ TsChart = class(TsChartFillElement)
+ private
+ FIndex: Integer; // Index in workbook's chart list
+ FSheetIndex: Integer;
+ FRow, FCol: Cardinal;
+ FOffsetX, FOffsetY: Double;
+ FWidth, FHeight: Double; // Width, Height of the chart, in mm.
+
+ FPlotArea: TsChartFillElement;
+ FFloor: TsChartFillElement;
+ FXAxis: TsChartAxis;
+ FX2Axis: TsChartAxis;
+ FYAxis: TsChartAxis;
+ FY2Axis: TsChartAxis;
+
+ FTitle: TsChartText;
+ FSubTitle: TsChartText;
+ FLegend: TsChartLegend;
+ FSeriesList: TsChartSeriesList;
+
+ FLineStyles: TsChartLineStyleList;
+ function GetCategoryLabelRange: TsCellRange;
+
+ public
+ constructor Create;
+ destructor Destroy; override;
+ function AddSeries(ASeries: TsChartSeries): Integer;
+ procedure DeleteSeries(AIndex: Integer);
+
+ function GetChartType: TsChartType;
+ function GetLineStyle(AIndex: Integer): TsChartLineStyle;
+ function IsScatterChart: Boolean;
+ function NumLineStyles: Integer;
+ {
+ function CategoriesInCol: Boolean;
+ function CategoriesInRow: Boolean;
+ function GetCategoryCount: Integer;
+ function HasCategories: Boolean;
+ }
+ { Index of chart in workbook's chart list. }
+ property Index: Integer read FIndex write FIndex;
+ { Index of worksheet sheet which contains the chart. }
+ property SheetIndex: Integer read FSheetIndex write FSheetIndex;
+ { Row index of the cell in which the chart has its top/left corner (anchor) }
+ property Row: Cardinal read FRow write FRow;
+ { Column index of the cell in which the chart has its top/left corner (anchor) }
+ property Col: Cardinal read FCol write FCol;
+ { Offset of the left chart edge relative to the anchor cell, in mm }
+ property OffsetX: double read FOffsetX write FOffsetX;
+ { Offset of the top chart edge relative to the anchor cell, in mm }
+ property OffsetY: double read FOffsetY write FOffsetY;
+ { Width of the chart, in mm }
+ property Width: double read FWidth write FWidth;
+ { Height of the chart, in mm }
+ property Height: double read FHeight write FHeight;
+
+ { Attributes of the plot area (rectangle enclosed by axes) }
+ property PlotArea: TsChartFillElement read FPlotArea write FPlotArea;
+ { Attributes of the floor of a 3D chart }
+ property Floor: TsChartFillElement read FFloor write FFloor;
+
+ { Attributes of the chart's title }
+ property Title: TsChartText read FTitle write FTitle;
+ { Attributes of the chart's subtitle }
+ property Subtitle: TsChartText read FSubtitle write FSubTitle;
+ { Attributs of the chart's legend }
+ property Legend: TsChartLegend read FLegend write FLegend;
+
+ { Attributes of the plot's primary x axis (bottom) }
+ property XAxis: TsChartAxis read FXAxis write FXAxis;
+ { Attributes of the plot's secondary x axis (top) }
+ property X2Axis: TsChartAxis read FX2Axis write FX2Axis;
+ { Attributes of the plot's primary y axis (left) }
+ property YAxis: TsChartAxis read FYAxis write FYAxis;
+ { Attributes of the plot's secondary y axis (right) }
+ property Y2Axis: TsChartAxis read FY2Axis write FY2Axis;
+
+ property CategoryLabelRange: TsCellRange read GetCategoryLabelRange;
+
+ { Attributes of the series }
+ property Series: TsChartSeriesList read FSeriesList write FSeriesList;
+ end;
+
+ TsChartList = class(TObjectList)
+ private
+ function GetItem(AIndex: Integer): TsChart;
+ procedure SetItem(AIndex: Integer; AValue: TsChart);
+ public
+ property Items[AIndex: Integer]: TsChart read GetItem write SetItem; default;
+ end;
+
+
+implementation
+
+const
+ DEFAULT_LINE_WIDTH = 0.75; // pts
+
+{ TsChartLineStyle }
+
+function TsChartLineStyle.GetID: String;
+var
+ i: Integer;
+begin
+ Result := Name;
+ for i:=1 to Length(Result) do
+ if Result[i] in [' ', '-'] then Result[i] := '_';
+ Result := 'FPS' + Result;
+end;
+
+
+{ TsChartLineStyleList }
+
+function TsChartLineStyleList.Add(AName: String;
+ ASeg1Length: Double; ASeg1Count: Integer;
+ ASeg2Length: Double; ASeg2Count: Integer;
+ ADistance: Double; ARelativeToLineWidth: Boolean): Integer;
+var
+ ls: TsChartLineStyle;
+begin
+ ls := TsChartLineStyle.Create;
+ ls.Name := AName;
+ ls.Segment1.Count := ASeg1Count;
+ ls.Segment1.Length := ASeg1Length;
+ ls.Segment2.Count := ASeg2Count;
+ ls.Segment2.Length := ASeg2Length;
+ ls.Distance := ADistance;
+ ls.RelativeToLineWidth := ARelativeToLineWidth;
+ result := inherited Add(ls);
+end;
+
+function TsChartLineStyleList.GetItem(AIndex: Integer): TsChartLineStyle;
+begin
+ Result := TsChartLineStyle(inherited);
+end;
+
+procedure TsChartLineStyleList.SetItem(AIndex: Integer; AValue: TsChartLineStyle);
+begin
+ inherited Items[AIndex] := AValue;
+end;
+
+
+{ TsChartElement }
+
+constructor TsChartElement.Create(AChart: TsChart);
+begin
+ inherited Create;
+ FChart := AChart;
+ FVisible := true;
+end;
+
+
+{ TsChartFillElement }
+
+constructor TsChartFillElement.Create(AChart: TsChart);
+begin
+ inherited Create(AChart);
+ FBackground.Style := fsSolidFill;
+ FBackground.BgColor := scWhite;
+ FBackground.FgColor := scWhite;
+ FBorder.Style := clsSolid;
+ FBorder.Width := PtsToMM(DEFAULT_LINE_WIDTH);
+ FBorder.Color := scBlack;
+end;
+
+
+{ TsChartText }
+
+constructor TsChartText.Create(AChart: TsChart);
+begin
+ inherited Create(AChart);
+ FShowCaption := true;
+ FFont := TsFont.Create;
+ FFont.FontName := ''; // replace by workbook's default font
+ FFont.Size := 0; // replace by workbook's default font size
+ FFont.Style := [];
+ FFont.Color := scBlack;
+end;
+
+destructor TsChartText.Destroy;
+begin
+ FFont.Free;
+ inherited;
+end;
+
+
+{ TsChartAxis }
+
+constructor TsChartAxis.Create(AChart: TsChart);
+begin
+ inherited Create(AChart);
+
+ FAutomaticMajorInterval := true;
+ FAutomaticMinorSteps := true;
+
+ FLabelFont := TsFont.Create;
+ FLabelFont.FontName := ''; // replace by workbook's default font
+ FLabelFont.Size := 0; // Replace by workbook's default font size
+ FLabelFont.Style := [];
+ FLabelFont.Color := scBlack;
+
+ FShowLabels := true;
+
+ FAxisLine.Color := scBlack;
+ FAxisLine.Style := clsSolid;
+ FAxisLine.Width := PtsToMM(DEFAULT_LINE_WIDTH);
+
+ FMajorTickLines.Color := scBlack;
+ FMajorTickLines.Style := clsSolid;
+ FMajorTickLines.Width := PtsToMM(DEFAULT_LINE_WIDTH);
+
+ FMinorTickLines.Color := scBlack;
+ FMinorTickLines.Style := clsSolid;
+ FMinorTickLines.Width := PtsToMM(DEFAULT_LINE_WIDTH);
+
+ FMajorGridLines.Color := scSilver;
+ FMajorGridLines.Style := clsSolid;
+ FMajorGridLines.Width := PtsToMM(DEFAULT_LINE_WIDTH);
+
+ FMinorGridLines.Color := scSilver;
+ FMinorGridLines.Style := clsDot;
+ FMinorGridLines.Width := PtsToMM(DEFAULT_LINE_WIDTH);
+end;
+
+destructor TsChartAxis.Destroy;
+begin
+ FLabelFont.Free;
+ inherited;
+end;
+ (*
+{ Determines how many labels are provided in the CategoryLabelRange. }
+function TsChartAxis.GetCategoryCount: Integer;
+begin
+ if CategoriesInCol then
+ Result := FLabelRange.Col2 - FLabelRange.Col1 + 1
+ else
+ if CategoriesInCol then
+ Result := FLabelRange.Row2 - FLabelRange.Row1 + 1
+ else
+ Result := 0;
+end;
+
+{ Returns true when the axis owns its own category labels. Otherwise labels
+ are taken from the series }
+function TsChartAxis.HasCategoryLabels: Boolean;
+begin
+ Result := CategoriesInCol or CategoriesInRow;
+end;
+
+{ Determines whether the axis labels are taken from columns (true) or rows (false) }
+function TsChartAxis.CategoriesInCol: Boolean;
+begin
+ Result := (FCategoryLabelRange.Row1 <> FCategoryLabelRange.Row2) and
+ (FCategoryLabelRange.Col1 = FCategoryLabelRange.Col2);
+end;
+
+function TsChartAxis.CategoriesInRow: Boolean;
+begin
+ Result := (FCategoryLabelRange.Col1 <> FCategoryLabelRange.Col2) and
+ (FCategoryLabelRange.Row1 = FCategoryLabelRange.Row2);
+end;
+ *)
+
+{ TsChartSeries }
+
+constructor TsChartSeries.Create(AChart: TsChart);
+begin
+ inherited Create(AChart);
+ AChart.AddSeries(self);
+end;
+
+function TsChartSeries.GetCount: Integer;
+begin
+ Result := GetYCount;
+end;
+
+function TsChartSeries.GetXCount: Integer;
+begin
+ if (FXRange.Row1 = FXRange.Row2) and (FXRange.Col1 = FXRange.Col2) then
+ Result := 0
+ else
+ if (FXRange.Row1 = FXRange.Row2) then
+ Result := FXRange.Col2 - FXRange.Col1 + 1
+ else
+ Result := FXRange.Row2 - FXRange.Row1 + 1;
+end;
+
+function TsChartSeries.GetYCount: Integer;
+begin
+ if YValuesInCol then
+ Result := FYRange.Row2 - FYRange.Row1 + 1
+ else
+ Result := FYRange.Col2 - FYRange.Col1 + 1;
+end;
+
+function TsChartSeries.HasLabels: Boolean;
+begin
+ Result := not ((FLabelRange.Row1 = FLabelRange.Row2) and (FLabelRange.Col1 = FLabelRange.Col2));
+end;
+
+function TsChartSeries.HasXValues: Boolean;
+begin
+ Result := not ((FXRange.Row1 = FXRange.Row2) and (FXRange.Col1 = FXRange.Col2));
+end;
+
+function TsChartSeries.HasYValues: Boolean;
+begin
+ Result := not ((FYRange.Row1 = FYRange.Row2) and (FYRange.Col1 = FYRange.Col2));
+end;
+
+function TsChartSeries.LabelsInCol: Boolean;
+begin
+ Result := (FLabelRange.Col1 = FLabelRange.Col2) and (FLabelRange.Row1 <> FLabelRange.Row2);
+end;
+
+procedure TsChartSeries.SetTitleAddr(ARow, ACol: Cardinal);
+begin
+ FTitleAddr.Row := ARow;
+ FTitleAddr.Col := ACol;
+end;
+
+procedure TsChartSeries.SetLabelRange(ARow1, ACol1, ARow2, ACol2: Cardinal);
+begin
+ if (ARow1 <> ARow2) and (ACol1 <> ACol2) then
+ raise Exception.Create('Series labels can only be located in a single column or row.');
+ FLabelRange.Row1 := ARow1;
+ FLabelRange.Col1 := ACol1;
+ FLabelRange.Row2 := ARow2;
+ FLabelRange.Col2 := ACol2;
+end;
+
+procedure TsChartSeries.SetXRange(ARow1, ACol1, ARow2, ACol2: Cardinal);
+begin
+ if (ARow1 <> ARow2) and (ACol1 <> ACol2) then
+ raise Exception.Create('Series x values can only be located in a single column or row.');
+ FXRange.Row1 := ARow1;
+ FXRange.Col1 := ACol1;
+ FXRange.Row2 := ARow2;
+ FXRange.Col2 := ACol2;
+end;
+
+procedure TsChartSeries.SetYRange(ARow1, ACol1, ARow2, ACol2: Cardinal);
+begin
+ if (ARow1 <> ARow2) and (ACol1 <> ACol2) then
+ raise Exception.Create('Series y values can only be located in a single column or row.');
+ FYRange.Row1 := ARow1;
+ FYRange.Col1 := ACol1;
+ FYRange.Row2 := ARow2;
+ FYRange.Col2 := ACol2;
+end;
+
+function TsChartSeries.XValuesInCol: Boolean;
+begin
+ Result := (FXRange.Col1 = FXRange.Col2) and (FXRange.Row1 <> FXRange.Row2);
+end;
+
+function TsChartSeries.YValuesInCol: Boolean;
+begin
+ Result := (FYRange.Col1 = FYRange.Col2) and (FYRange.Row1 <> FYRange.Row2);
+end;
+
+
+{ TsChartSeriesList }
+
+function TsChartSeriesList.GetItem(AIndex: Integer): TsChartSeries;
+begin
+ Result := TsChartSeries(inherited Items[AIndex]);
+end;
+
+procedure TsChartSeriesList.SetItem(AIndex: Integer; AValue: TsChartSeries);
+begin
+ inherited Items[AIndex] := AValue;
+end;
+
+
+{ TsLineSeries }
+
+constructor TsLineSeries.Create(AChart: TsChart);
+begin
+ inherited Create(AChart);
+
+ FChartType := ctLine;
+
+ FLineStyle.Color := scBlack;
+ FLineStyle.Style := clsSolid;
+ FLineStyle.Width := PtsToMM(DEFAULT_LINE_WIDTH);
+
+ FSymbolBorder.Color := scBlack;
+ FSymbolBorder.Style := clsSolid;
+ FSymbolBorder.Width := PtsToMM(DEFAULT_LINE_WIDTH);
+
+ FSymbolFill.FgColor := scWhite;
+ FSymbolFill.BgColor := scWhite;
+ FSymbolFill.Style := fsSolidFill;
+
+ FSymbolWidth := 2.5;
+ FSymbolHeight := 2.5;
+end;
+
+
+{ TsChart }
+
+constructor TsChart.Create;
+begin
+ inherited Create(nil);
+
+ FLineStyles := TsChartLineStyleList.Create;
+ clsFineDot := FLineStyles.Add('fine-dot', 100, 1, 0, 0, 100, false);
+ clsDot := FLineStyles.Add('dot', 150, 1, 0, 0, 150, true);
+ clsDash := FLineStyles.Add('dash', 300, 1, 0, 0, 150, true);
+ clsDashDot := FLineStyles.Add('dash-dot', 300, 1, 100, 1, 150, true);
+ clsLongDash := FLineStyles.Add('long dash', 400, 1, 0, 0, 200, true);
+ clsLongDashDot := FLineStyles.Add('long dash-dot', 500, 1, 100, 1, 200, true);
+ clsLongDashDotDot := FLineStyles.Add('long dash-dot-dot', 500, 1, 100, 2, 200, true);
+
+ FSheetIndex := 0;
+ FRow := 0;
+ FCol := 0;
+ FOffsetX := 0.0;
+ FOffsetY := 0.0;
+ FWidth := 12;
+ FHeight := 9;
+
+ FPlotArea := TsChartFillElement.Create(self);
+ FFloor := TsChartFillElement.Create(self);
+
+ FTitle := TsChartText.Create(self);
+ FTitle.Font.Size := 14;
+
+ FSubTitle := TsChartText.Create(self);
+ FSubTitle.Font.Size := 12;
+
+ FLegend := TsChartLegend.Create(self);
+
+ FXAxis := TsChartAxis.Create(self);
+ FXAxis.Caption := 'x axis';
+ FXAxis.LabelFont.Size := 9;
+ FXAxis.Font.Size := 10;
+ FXAxis.Font.Style := [fssBold];
+ FXAxis.Position := capStart;
+
+ FX2Axis := TsChartAxis.Create(self);
+ FX2Axis.Caption := 'Secondary x axis';
+ FX2Axis.LabelFont.Size := 9;
+ FX2Axis.Font.Size := 10;
+ FX2Axis.Font.Style := [fssBold];
+ FX2Axis.Visible := false;
+ FX2Axis.Position := capEnd;
+
+ FYAxis := TsChartAxis.Create(self);
+ FYAxis.Caption := 'y axis';
+ FYAxis.LabelFont.Size := 9;
+ FYAxis.Font.Size := 10;
+ FYAxis.Font.Style := [fssBold];
+ FYAxis.Position := capStart;
+
+ FY2Axis := TsChartAxis.Create(self);
+ FY2Axis.Caption := 'Secondary y axis';
+ FY2Axis.LabelFont.Size := 9;
+ FY2Axis.Font.Size := 10;
+ FY2Axis.Font.Style := [fssBold];
+ FY2Axis.Visible := false;
+ FY2Axis.Position := capEnd;
+
+ FSeriesList := TsChartSeriesList.Create;
+end;
+
+destructor TsChart.Destroy;
+begin
+ FSeriesList.Free;
+ FXAxis.Free;
+ FYAxis.Free;
+ FY2Axis.Free;
+ FLegend.Free;
+ FTitle.Free;
+ FSubtitle.Free;
+ FLineStyles.Free;
+ FFloor.Free;
+ FPlotArea.Free;
+ inherited;
+end;
+
+function TsChart.AddSeries(ASeries: TsChartSeries): Integer;
+begin
+ Result := FSeriesList.IndexOf(ASeries);
+ if Result = -1 then
+ Result := FSeriesList.Add(ASeries);
+end;
+
+procedure TsChart.DeleteSeries(AIndex: Integer);
+begin
+ if (AIndex >= 0) and (AIndex < FSeriesList.Count) then
+ FSeriesList.Delete(AIndex);
+end;
+
+function TsChart.GetCategoryLabelRange: TsCellRange;
+begin
+ if FSeriesList.Count > 0 then
+ Result := Series[0].LabelRange
+ else
+ begin
+ Result.Row1 := 0;
+ Result.Col1 := 0;
+ Result.Row2 := 0;
+ Result.Col2 := 0;
+ end;
+end;
+
+function TsChart.GetChartType: TsChartType;
+begin
+ if FSeriesList.Count > 0 then
+ Result := Series[0].ChartType
+ else
+ Result := ctEmpty;
+end;
+
+function TsChart.GetLineStyle(AIndex: Integer): TsChartLineStyle;
+begin
+ if AIndex >= 0 then
+ Result := FLineStyles[AIndex]
+ else
+ Result := nil;
+end;
+
+function TsChart.IsScatterChart: Boolean;
+begin
+ Result := GetChartType = ctScatter;
+end;
+
+function TsChart.NumLineStyles: Integer;
+begin
+ Result := FLineStyles.Count;
+end;
+
+
+{ TsChartList }
+
+function TsChartList.GetItem(AIndex: Integer): TsChart;
+begin
+ Result := TsChart(inherited Items[AIndex]);
+end;
+
+procedure TsChartlist.SetItem(AIndex: Integer; AValue: TsChart);
+begin
+ inherited Items[AIndex] := AValue;
+end;
+
+end.
+
diff --git a/components/fpspreadsheet/source/common/fpsopendocument.pas b/components/fpspreadsheet/source/common/fpsopendocument.pas
index ef4466f83..12cab99b1 100644
--- a/components/fpspreadsheet/source/common/fpsopendocument.pas
+++ b/components/fpspreadsheet/source/common/fpsopendocument.pas
@@ -40,7 +40,7 @@ uses
fpszipper,
{$ENDIF}
fpstypes, fpsReaderWriter, fpsutils, fpsHeaderFooterParser,
- fpsNumFormat, fpsxmlcommon, fpsPagelayout;
+ fpsNumFormat, fpsxmlcommon, fpsPagelayout, fpsChart;
type
TDateModeODS=(
@@ -236,6 +236,7 @@ type
procedure WriteCellRow(AStream: TStream; ASheet: TsBasicWorksheet;
ARowIndex, ALastColIndex: Integer);
procedure WriteCellStyles(AStream: TStream);
+
procedure WriteColStyles(AStream: TStream);
procedure WriteColumns(AStream: TStream; ASheet: TsBasicWorksheet);
procedure WriteConditionalFormats(AStream: TStream; ASheet: TsBasicWorksheet);
@@ -244,10 +245,12 @@ type
ARowIndex, AFirstColIndex, ALastColIndex, ALastRowIndex: Integer;
out ARowsRepeated: Integer);
procedure WriteFontNames(AStream: TStream);
+ procedure WriteGraphicStyles(AStream: TStream);
procedure WriteMasterStyles(AStream: TStream);
procedure WriteNamedExpressions(AStream: TStream; ASheet: TsBasicWorksheet);
procedure WriteNumFormats(AStream: TStream);
procedure WriteOfficeStyles(AStream: TStream);
+ procedure WriteParagraphStyles(AStream: TStream);
procedure WriteRowStyles(AStream: TStream);
procedure WriteRowsAndCells(AStream: TStream; ASheet: TsBasicWorksheet);
procedure WriteShapes(AStream: TStream; ASheet: TsBasicWorksheet);
@@ -281,11 +284,24 @@ type
function WriteVertAlignmentStyleXMLAsString(const AFormat: TsCellFormat): String;
function WriteWordwrapStyleXMLAsString(const AFormat: TsCellFormat): String;
+ { Chart support }
+ procedure PrepareChartTable(AChart: TsChart; AWorksheet: TsBasicWorksheet);
+ procedure WriteChart(AStream: TStream; AChart: TsChart);
+ procedure WriteChartAxis(AStream: TStream; AChart: TsChart; IsX, IsPrimary: Boolean; AIndent: Integer);
+ procedure WriteChartLegend(AStream: TStream; AChart: TsChart; AIndent: Integer);
+ procedure WriteChartPlotArea(AStream: TStream; AChart: TsChart; AIndent: Integer);
+ procedure WriteChartSeries(AStream: TStream; AChart: TsChart; ASeriesIndex: Integer; AIndent: Integer);
+ procedure WriteChartStyles(AStream: TStream; AChart: TsChart; AIndent: Integer);
+ procedure WriteChartTable(AStream: TStream; AChart: TsChart; AIndent: Integer);
+ procedure WriteChartTitle(AStream: TStream; AChart: TsChart; IsSubtitle: Boolean; AIndent: Integer);
+
protected
FPointSeparatorSettings: TFormatSettings;
+
// Streams with the contents of files
FSMeta, FSSettings, FSStyles, FSContent: TStream;
FSMimeType, FSMetaInfManifest: TStream;
+ FSCharts: array of TStream;
{ Helpers }
procedure AddBuiltinNumFormats; override;
@@ -309,6 +325,7 @@ type
procedure ResetStreams;
{ Routines to write those files }
+ procedure WriteCharts;
procedure WriteContent;
procedure WriteMetaInfManifest;
procedure WriteMeta;
@@ -360,6 +377,8 @@ uses
fpsExprParser, fpsImages, fpsConditionalFormat;
const
+ LE = LineEnding;
+
{ OpenDocument general XML constants }
XML_HEADER = '';
@@ -369,8 +388,9 @@ const
OPENDOC_PATH_SETTINGS = 'settings.xml';
OPENDOC_PATH_STYLES = 'styles.xml';
OPENDOC_PATH_MIMETYPE = 'mimetype';
- {%H-}OPENDOC_PATH_METAINF = 'META-INF' + '/';
- {%H-}OPENDOC_PATH_METAINF_MANIFEST = 'META-INF' + '/' + 'manifest.xml';
+ {%H-}OPENDOC_PATH_METAINF = 'META-INF/';
+ OPENDOC_PATH_METAINF_MANIFEST = 'META-INF/manifest.xml';
+ OPENDOC_PATH_CHART_CONTENT = 'Object %d/content.xml';
{ OpenDocument schemas constants }
SCHEMAS_XMLNS_OFFICE = 'urn:oasis:names:tc:opendocument:xmlns:office:1.0';
@@ -485,6 +505,11 @@ const
'5Rating', '5Quarters', '5Boxes' // is5Rating, is5Quarters, is5Boxes
);
+ CHART_TYPE_NAMES: array[TsChartType] of string = (
+ '', 'bar', 'line', 'area', 'barLine', 'scatter'
+ );
+
+
function CFOperandToStr(v: variant; AWorksheet: TsWorksheet;
const AFormatSettings: TFormatSettings): String;
var
@@ -2912,7 +2937,6 @@ var
sheetName: String;
tablestyleName: String;
err: String;
- isEncrypted: Boolean = false;
function CreateXMLStream: TStream;
begin
@@ -2930,10 +2954,10 @@ begin
Doc := nil;
try
- // Read the META-INF/manifest.xml file to learn about encryption
+ // Read the "META-INF/manifest.xml" file to learn about encryption
XMLStream := CreateXMLStream;
try
- if UnzipToStream(AStream, 'META-INF/manifest.xml', XMLStream) then
+ if UnzipToStream(AStream, OPENDOC_PATH_METAINF_MANIFEST, XMLStream) then
begin
ReadXMLStream(Doc, XMLStream);
if Assigned(Doc) then
@@ -5682,6 +5706,8 @@ end;
{ Creates the streams for the individual data files. Will be zipped into a
single xlsx file. }
procedure TsSpreadOpenDocWriter.CreateStreams;
+var
+ i, n: Integer;
begin
FSMeta := CreateTempStream(FWorkbook, 'fpsM');
FSSettings := CreateTempStream(FWorkbook, 'fpsS');
@@ -5689,39 +5715,19 @@ begin
FSContent := CreateTempStream(FWorkbook, 'fpsC');
FSMimeType := CreateTempStream(FWorkbook, 'fpsMT');
FSMetaInfManifest := CreateTempStream(FWorkbook, 'fpsMIM');
- {
- if boFileStream in FWorkbook.Options then
- begin
- FSMeta := TFileStream.Create(GetTempFileName('', 'fpsM'), fmCreate);
- FSSettings := TFileStream.Create(GetTempFileName('', 'fpsS'), fmCreate);
- FSStyles := TFileStream.Create(GetTempFileName('', 'fpsSTY'), fmCreate);
- FSContent := TFileStream.Create(GetTempFileName('', 'fpsC'), fmCreate);
- FSMimeType := TFileStream.Create(GetTempFileName('', 'fpsMT'), fmCreate);
- FSMetaInfManifest := TFileStream.Create(GetTempFileName('', 'fpsMIM'), fmCreate);
- end else
- if (boBufStream in Workbook.Options) then
- begin
- FSMeta := TBufStream.Create(GetTempFileName('', 'fpsM'));
- FSSettings := TBufStream.Create(GetTempFileName('', 'fpsS'));
- FSStyles := TBufStream.Create(GetTempFileName('', 'fpsSTY'));
- FSContent := TBufStream.Create(GetTempFileName('', 'fpsC'));
- FSMimeType := TBufStream.Create(GetTempFileName('', 'fpsMT'));
- FSMetaInfManifest := TBufStream.Create(GetTempFileName('', 'fpsMIM'));
- end else
- begin
- FSMeta := TMemoryStream.Create;
- FSSettings := TMemoryStream.Create;
- FSStyles := TMemoryStream.Create;
- FSContent := TMemoryStream.Create;
- FSMimeType := TMemoryStream.Create;
- FSMetaInfManifest := TMemoryStream.Create;
- end;
- }
+
+ n := TsWorkbook(FWorkbook).GetChartCount;
+ SetLength(FSCharts, n);
+ for i := 0 to n - 1 do
+ FSCharts[i] := CreateTempStream(FWorkbook, 'fpsCh');
+
// FSSheets will be created when needed.
end;
{ Destroys the temporary streams that were created by the writer }
procedure TsSpreadOpenDocWriter.DestroyStreams;
+var
+ i: Integer;
begin
DestroyTempStream(FSMeta);
DestroyTempStream(FSSettings);
@@ -5729,6 +5735,9 @@ begin
DestroyTempStream(FSContent);
DestroyTempStream(FSMimeType);
DestroyTempStream(FSMetaInfManifest);
+ for i := 0 to High(FSCharts) do
+ DestroyTempStream(FSCharts[i]);
+ Setlength(FSCharts, 0);
end;
procedure TsSpreadOpenDocWriter.GetHeaderFooterImageName(
@@ -5810,6 +5819,7 @@ end;
procedure TsSpreadOpenDocWriter.InternalWriteToStream(AStream: TStream);
var
FZip: TZipper;
+ i: Integer;
begin
{ Analyze the workbook and collect all information needed }
ListAllNumFormats;
@@ -5827,6 +5837,7 @@ begin
WriteSettings();
WriteStyles();
WriteContent;
+ WriteCharts;
{ Now compress the files }
FZip := TZipper.Create;
@@ -5839,6 +5850,9 @@ begin
FZip.Entries.AddFileEntry(FSMimetype, OPENDOC_PATH_MIMETYPE);
FZip.Entries.AddFileEntry(FSMetaInfManifest, OPENDOC_PATH_METAINF_MANIFEST);
ZipPictures(FZip);
+ for i := 0 to TsWorkbook(FWorkbook).GetChartCount-1 do
+ FZip.Entries.AddFileEntry(
+ FSCharts[i], Format(OPENDOC_PATH_CHART_CONTENT, [i+1]));
ResetStreams;
@@ -6028,6 +6042,8 @@ end;
{ Is called before zipping the individual file parts. Rewinds the streams. }
procedure TsSpreadOpenDocWriter.ResetStreams;
+var
+ i: Integer;
begin
FSMeta.Position := 0;
FSSettings.Position := 0;
@@ -6035,6 +6051,8 @@ begin
FSContent.Position := 0;
FSMimeType.Position := 0;
FSMetaInfManifest.Position := 0;
+ for i := 0 to High(FSCharts) do
+ FSCharts[i].Position := 0;
end;
{ Writes the node "office:automatic-styles". Although this node occurs in both
@@ -6113,17 +6131,19 @@ var
embObj: TsEmbeddedObj;
begin
AppendToStream(FSMetaInfManifest,
- '');
+ XML_HEADER);
AppendToStream(FSMetaInfManifest,
- '');
+ '' + LE);
AppendToStream(FSMetaInfManifest,
- '');
+ ' ' + LE);
AppendToStream(FSMetaInfManifest,
- '');
+ ' ' + LE);
AppendToStream(FSMetaInfManifest,
- '');
+ ' ' + LE);
AppendToStream(FSMetaInfManifest,
- '');
+ ' ' + LE);
+ AppendToStream(FSMetaInfManifest,
+ ' ' + LE);
for i:=0 to (FWorkbook as TsWorkbook).GetEmbeddedObjCount-1 do
begin
embObj := TsWorkbook(FWorkbook).GetEmbeddedObj(i);
@@ -6133,10 +6153,22 @@ begin
mime := GetImageMimeType(imgtype);
ext := GetImageTypeExt(imgType);
AppendToStream(FSMetaInfManifest, Format(
- '',
+ ' ' + LE,
[mime, i+1, ext]
));
end;
+ for i:=0 to (FWorkbook as TsWorkbook).GetChartCount-1 do
+ begin
+ AppendToStream(FSMetaInfManifest, Format(
+ ' ' + LE,
+ [i+1]
+ ));
+ AppendToStream(FSMetaInfManifest, Format(
+ ' ' + LE,
+ [i+1]
+ ));
+ // Object X/styles.xml, Object X/meta.xml and ObjectReplacement/Object X not needed necessarily
+ end;
AppendToStream(FSMetaInfManifest,
'');
end;
@@ -6150,57 +6182,49 @@ begin
book := TsWorkbook(FWorkbook);
AppendToStream(FSMeta,
- XML_HEADER);
+ XML_HEADER + LE);
AppendToStream(FSMeta,
- '');
- {
+ '' + LE);
AppendToStream(FSMeta,
- '');
- }
- AppendToStream(FSMeta,
- '',
- 'FPSpreadsheet Library' +
- '');
+ ' ' + LE,
+ ' FPSpreadsheet Library' + LE +
+ ' ' + LE);
if book.Metadata.Title <> '' then
begin
s := book.Metadata.Title;
AppendToStream(FSMeta, Format(
- '%s', [UTF8TextToXMLText(s)]));
+ ' %s' + LE, [UTF8TextToXMLText(s)]));
end;
if book.Metadata.Subject <> '' then
begin
s := book.Metadata.Subject;
AppendToStream(FSMeta, Format(
- '%s', [UTF8TextToXMLText(s)]));
+ ' %s' + LE, [UTF8TextToXMLText(s)]));
end;
if book.Metadata.CreatedBy <> '' then
AppendToStream(FSMeta, Format(
- '%s', [UTF8TextToXMLText(book.MetaData.CreatedBy)]));
+ ' %s' + LE, [UTF8TextToXMLText(book.MetaData.CreatedBy)]));
if book.MetaData.LastModifiedBy <> '' then
AppendToStream(FSMeta, Format(
- '%s', [UTF8TextToXMLText(book.Metadata.LastModifiedBy)]));
+ ' %s' + LE, [UTF8TextToXMLText(book.Metadata.LastModifiedBy)]));
if book.MetaData.DateCreated > 0 then
begin
// ODS stored the creation date in UTC.
s := FormatDateTime(ISO8601FormatExtendedUTC, book.MetaData.DateCreated);
AppendToStream(FSMeta, Format(
- '%s', [s]));
+ ' %s' + LE, [s]));
end;
if book.MetaData.DateLastModified > 0 then
@@ -6208,13 +6232,13 @@ begin
// Date of last modification is NOT UTC.
s := FormatDateTime(ISO8601FormatExtended, book.MetaData.DateLastModified);
AppendToStream(FSMeta, Format(
- '%s', [s]));
+ ' %s' + LE, [s]));
end;
if book.MetaData.Keywords.Count > 0 then
for i := 0 to book.MetaData.Keywords.Count-1 do
AppendToStream(FSMeta, Format(
- '%s', [UTF8TextToXMLText(book.MetaData.Keywords[i])]));
+ ' %s' + LE, [UTF8TextToXMLText(book.MetaData.Keywords[i])]));
if book.MetaData.Comments.Count > 0 then
begin
@@ -6222,21 +6246,21 @@ begin
for i := 1 to book.MetaData.Comments.Count-1 do
s := s + #10 + book.MetaData.Comments[i];
AppendToStream(FSMeta, Format(
- '%s', [UTF8TextToXMLText(s)]));
+ ' %s' + LE, [UTF8TextToXMLText(s)]));
end;
if book.MetaData.Custom.Count > 0 then
begin
for i := 0 to book.Metadata.Custom.Count-1 do
AppendToStream(FSMeta, Format(
- '%s', [
+ ' %s' + LE, [
book.Metadata.Custom.Names[i],
UTF8TextToXMLText(book.Metadata.Custom.ValueFromIndex[i])
]));
end;
AppendToStream(FSMeta,
- '');
+ ' ' + LE);
AppendToStream(FSMeta,
'');
end;
@@ -6411,6 +6435,18 @@ begin
'');
end;
+procedure TsSpreadOpenDocWriter.WriteCharts;
+var
+ i: Integer;
+ chart: TsChart;
+begin
+ for i := 0 to TsWorkbook(FWorkbook).GetChartCount - 1 do
+ begin
+ chart := TsWorkbook(FWorkbook).GetChartByIndex(i);
+ WriteChart(FSCharts[i], chart);
+ end;
+end;
+
procedure TsSpreadOpenDocWriter.WriteContent;
var
i: Integer;
@@ -6500,6 +6536,8 @@ begin
WriteTableStyles(FSContent); // "ta1" ...
WriteCellStyles(FSContent); // "ce1" ...
WriteTextStyles(FSContent); // "T1" ...
+ WriteGraphicStyles(FSContent); // "gr1" ...
+ WriteParagraphStyles(FSContent); // "P1" ...
AppendToStream(FSContent,
'');
@@ -6636,6 +6674,827 @@ begin
end;
end;
+procedure TsSpreadOpenDocWriter.WriteChart(AStream: TStream; AChart: TsChart);
+var
+ chartClass: String;
+begin
+ AppendToStream(AStream,
+ XML_HEADER + LE);
+
+ AppendToStream(AStream,
+ '' + LE
+ );
+
+ WriteChartStyles(AStream, AChart, 2);
+
+ AppendToStream(AStream,
+ ' ' + LE +
+ ' ' + LE
+ );
+
+ chartClass := CHART_TYPE_NAMES[AChart.GetChartType];
+ if chartClass <> '' then
+ chartClass := ' chart:class="chart:' + chartClass + '"';
+
+ AppendToStream(AStream, Format(
+ ' ' + LE,
+ [
+ AChart.Width, // Width, Height are in mm
+ AChart.Height
+ ], FPointSeparatorSettings
+ ));
+
+ WriteChartTitle(AStream, AChart, false, 8); // Title
+ WriteChartTitle(AStream, AChart, true, 8); // Subtitle
+ WriteChartLegend(AStream, AChart, 8);
+ WriteChartPlotArea(AStream, AChart, 8);
+ WriteChartTable(AStream, AChart, 8);
+
+ AppendToStream(AStream,
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ '');
+end;
+
+procedure TsSpreadOpenDocWriter.WriteChartAxis(AStream: TStream; AChart: TsChart;
+ IsX, IsPrimary: Boolean; AIndent: Integer);
+const
+ AXIS_ID: array[boolean] of string = ('y', 'x');
+ AXIS_LEVEL: array[boolean] of string = ('secondary', 'primary');
+var
+ ind: String;
+ axis: TsChartAxis;
+ series: TsChartSeries;
+ sheet: TsWorksheet;
+ refStr: String;
+ styleID, titleStyleID: Integer;
+ majorGridStyleID: Integer = 8;
+ minorGridstyleID: Integer = 9;
+ r1, c1, r2, c2: Cardinal;
+begin
+ ind := DupeString(' ', AIndent);
+ sheet := TsWorkbook(FWorkbook).GetWorksheetByIndex(AChart.SheetIndex);
+
+ if IsX then
+ begin
+ if IsPrimary then
+ begin
+ axis := AChart.XAxis;
+ styleID := 6;
+ end else
+ begin
+ axis := AChart.X2Axis;
+ styleID := 10;
+ end;
+ titleStyleID := 7;
+ end else
+ begin
+ if IsPrimary then
+ begin
+ axis := AChart.YAxis;
+ styleID := 11;
+ end else
+ begin
+ axis := AChart.Y2Axis;
+ styleID := 12;
+ end;
+ titleStyleID := 12;
+ end;
+
+ AppendToStream(AStream, Format(
+ ind + '' + LE,
+ [ AXIS_ID[IsX], AXIS_LEVEL[IsPrimary], AXIS_ID[IsX], styleID ]
+ ));
+
+ if IsX and (not AChart.IsScatterChart) and (AChart.Series.Count > 0) then
+ begin
+ series := AChart.Series[0];
+ 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(AStream, Format(
+ ind + ' ' + LE,
+ [ refStr ]
+ ));
+ end;
+
+ if axis.Caption <> '' then
+ AppendToStream(AStream, Format(
+ ind + ' ' + LE +
+ ind + ' %s' + LE +
+ ind + ' ' + LE,
+ [ titleStyleID, axis.Caption ]
+ ));
+
+ if axis.ShowMajorGridLines then
+ AppendToStream(AStream, Format(
+ ind + ' ' + LE,
+ [ majorGridStyleID ]
+ ));
+
+ if axis.ShowMinorGridLines then
+ AppendToStream(AStream, Format(
+ ind + ' ' + LE,
+ [ minorGridStyleID ]
+ ));
+
+ AppendToStream(AStream,
+ ind + '' + LE
+ );
+end;
+
+{ Writes the chart's legend to the xml stream }
+procedure TsSpreadOpenDocWriter.WriteChartLegend(AStream: TStream; AChart: TsChart;
+ AIndent: Integer);
+var
+ ind: String;
+ legendStyleID: Integer = 4;
+begin
+ if (not AChart.Legend.Visible) then
+ exit;
+
+ ind := DupeString(' ', AIndent);
+ AppendToStream(AStream, Format(
+ ind + '' + LE,
+ [ legendStyleID ]
+ ));
+end;
+
+procedure TsSpreadOpenDocWriter.WriteChartPlotArea(AStream: TStream;
+ AChart: TsChart; AIndent: Integer);
+var
+ ind: String;
+ i: Integer;
+ plotAreaStyleID: Integer = 5;
+ wallStyleID: Integer = 22; // usually second to last of style list
+ floorstyleID: Integer = 23; // usually last of style list
+begin
+ ind := DupeString(' ', AIndent);
+ AppendToStream(AStream, Format(
+ ind + '' + LE,
+ [ plotAreastyleID ]
+ ));
+ // ods has a table:cell-range-address here but it is reconstructed by Calc
+
+ WriteChartAxis(AStream, AChart, true, true, AIndent + 2); // true = x axis true = primary
+ WriteChartAxis(AStream, AChart, false, true, AIndent + 2); // false = y axis true = primary
+
+ for i := 0 to AChart.Series.Count-1 do
+ WriteChartSeries(AStream, AChart, i, AIndent + 2);
+
+ AppendToStream(AStream, Format(
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+ ind + '' + LE,
+ [ wallStyleID, floorStyleID ]
+ ));
+end;
+
+procedure TsSpreadOpenDocWriter.WriteChartSeries(AStream: TStream;
+ AChart: TsChart; ASeriesIndex: Integer; AIndent: Integer);
+var
+ ind: String;
+ sheet: TsWorksheet;
+ series: TsChartSeries;
+ valuesRange: String;
+ titleAddr: String;
+ count: Integer;
+ seriesStyleID: Integer;
+begin
+ ind := DupeString(' ', AIndent);
+
+ series := AChart.Series[ASeriesIndex];
+ sheet := TsWorkbook(FWorkbook).GetWorksheetByIndex(AChart.sheetIndex);
+
+ valuesRange := GetSheetCellRangeString_ODS(
+ sheet.Name, sheet.Name,
+ series.YRange.Row1, series.YRange.Col1,
+ series.YRange.Row2, series.YRange.Col2,
+ rfAllRel, false
+ );
+ 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;
+
+ seriesStyleID := 14 + ASeriesIndex;
+
+ AppendToStream(AStream, Format(
+ ind + '' + LE,
+ [ seriesStyleID, valuesRange, titleAddr, CHART_TYPE_NAMES[series.ChartType] ]
+ ));
+ AppendToStream(AStream, Format(
+ ind + ' ' + LE,
+ [ count ]
+ ));
+ AppendToStream(AStream,
+ ind + '' + LE
+ );
+end;
+
+{ To do: The list of styles must be updated to the real chart element settings. }
+procedure TsSpreadOpenDocWriter.WriteChartStyles(AStream: TStream;
+ AChart: TsChart; AIndent: Integer);
+var
+ ind: String;
+begin
+ ind := DupeString(' ', AIndent);
+
+ AppendToStream(AStream,
+ ind + '' + LE +
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+
+ // ch1: style for element
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+
+ // ch2: style for element
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+
+ // ch3: style for ' + LE +
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+
+ // ch 4: style for element
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+
+ // ch5: style for element
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+ ind + ' ' +
+
+ {
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+ }
+
+ // ch6: style for first element, primary x axis
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+
+ // ch7: style for title at horizontal axes
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+
+ // ch8: style for major grid (all axes)
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+
+ // ch9: style for minor grid (all axes)
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+
+ // ch10: style for second element, secondary x axis
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+
+ // ch11: style for third element: primary y axis
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+
+ // ch12: style for vertical axis title (both y and y2 axis)
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+ ind + ' ' +
+
+ // ch13: style for fourth element: secondary y axis
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+
+ // ch14: style for frist series - this is for a line series
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+ ind + ' ' +
+ ind + ' ' + LE +
+
+ (*
+ // ch14: style for first series - this is for a bar series
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+ *)
+
+ // ch15: style for second series - this is for a bar series
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+
+ // ch16: style for third series (bar series, here)
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+
+ // ch17: style for 4th series (bar series, here)
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+
+ // ch18: style for 5th series (bar series, here)
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+
+ // ch19: style for 6th series (bar series, here)
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+
+ // ch20: style for 7th series (bar series, here)
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+
+ // ch21: style for 8th series (bar series, here)
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+
+ // next to last: style for wall
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+
+ // last: last style for floor
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+
+ ind + '' + LE
+ );
+end;
+
+{ Extracts the cells needed by the given chart from the chart's worksheet and
+ copies their values into a temporary worksheet, AWorksheet, so that these
+ data can be written to the xml immediately.
+ Independently of the layout in the original worksheet, data are arranged in
+ columns of AWorksheet, starting at cell A1.
+ - First column: Categories (or index in case of scatter chart)
+ - Second column:
+ in case of category charts: y values of the first series,
+ in case of scatter series: x values of the first series
+ - Third column:
+ in case of category charts: y values of the second series
+ in case of scatter series. y values of the first series
+ - etc.
+ The first row contains
+ - nothing in case of the first column
+ - cell range reference in ODS syntax for the cells in the original worksheet.
+ The aux worksheet should be contained in a separate workbook to avoid
+ interfering with the writing process.
+ }
+procedure TsSpreadOpenDocWriter.PrepareChartTable(AChart: TsChart;
+ AWorksheet: TsBasicWorksheet);
+var
+ isScatterChart: Boolean;
+ series: TsChartSeries;
+ seriesSheet: TsWorksheet;
+ auxSheet: TsWorksheet;
+ refStr, txt: String;
+ i, j: Integer;
+ srcCell, destCell: PCell;
+ destCol: Cardinal;
+ r1, c1, r2, c2: Cardinal;
+ nRows: Integer;
+begin
+ if AChart.Series.Count = 0 then
+ exit;
+
+ auxSheet := TsWorksheet(AWorksheet);
+ seriesSheet := TsWorkbook(FWorkbook).GetWorksheetByIndex(AChart.SheetIndex);
+ isScatterChart := AChart.IsScatterChart;
+
+ // Determine the number of rows in auxiliary output worksheet.
+ nRows := 0;
+ for i := 0 to AChart.Series.Count-1 do
+ begin
+ series := AChart.Series[i];
+ j := series.GetXCount;
+ if j > nRows then nRows := j;
+ j := series.GetYCount;
+ if j > nRows then nRows := j;
+ end;
+
+ // Write label column. If missing, write consecutive numbers 1, 2, 3, ...
+ destCol := 0;
+ for i := 0 to AChart.Series.Count-1 do
+ begin
+ series := AChart.Series[i];
+
+ // Write the label column. Use consecutive numbers 1, 2, 3, ... if there
+ // are no labels.
+ if series.HasLabels then
+ begin
+ r1 := series.LabelRange.Row1;
+ c1 := series.LabelRange.Col1;
+ r2 := series.LabelRange.Row2;
+ c2 := series.LabelRange.Col2;
+ refStr := GetSheetCellRangeString_ODS(seriesSheet.Name, seriesSheet.Name, r1, c1, r2, c2, rfAllRel, false);
+ end else
+ refStr := '';
+
+ auxSheet.WriteText(0, destCol, '');
+
+ for j := 1 to nRows do
+ begin
+ if series.HasLabels then
+ begin
+ if series.LabelsInCol then
+ srcCell := seriesSheet.FindCell(r1 + j - 1, c1)
+ else
+ srcCell := seriesSheet.FindCell(r1, c1 + j - 1);
+ end else
+ srcCell := nil;
+ if srcCell <> nil then
+ begin
+ destCell := auxsheet.GetCell(j, destCol);
+ seriesSheet.CopyValue(srcCell, destCell);
+ end else
+ destCell := auxSheet.WriteNumber(j, destCol, j);
+ end;
+ if (refStr <> '') then
+ auxsheet.WriteComment(1, destCol, refStr);
+
+ // In case of scatter plot write the x column. Use consecutive numbers 1, 2, 3, ...
+ // if there are no x values.
+ if isScatterChart then
+ begin
+ inc(destCol);
+
+ if series.HasXValues then
+ begin
+ r1 := series.XRange.Row1;
+ c1 := series.XRange.Col1;
+ r2 := series.XRange.Row2;
+ c2 := series.XRange.Col2;
+ refStr := GetSheetCellRangeString_ODS(seriesSheet.Name, seriesSheet.Name, r1, c1, r2, c2, rfAllRel, false);
+ if series.XValuesInCol then
+ txt := 'Col ' + GetColString(c1)
+ else
+ txt := 'Row ' + GetRowString(r1);
+ end else
+ begin
+ refStr := '';
+ txt := '';
+ end;
+
+ auxSheet.WriteText(0, destCol, txt);
+
+ for j := 1 to nRows do
+ begin
+ if series.HasXValues then
+ begin
+ if series.XValuesInCol then
+ srcCell := seriesSheet.FindCell(r1 + j - 1, c1)
+ else
+ srcCell := seriesSheet.FindCell(r1, c1 + j - 1);
+ end else
+ srcCell := nil;
+ if srcCell <> nil then
+ begin
+ destCell := auxsheet.GetCell(j, destCol);
+ seriesSheet.CopyValue(srcCell, destCell);
+ end else
+ destCell := auxSheet.WriteNumber(j, destCol, j);
+ end;
+ if (refStr <> '') then
+ auxsheet.WriteComment(1, destCol, refStr);
+ end;
+
+ // Write the y column
+ if not series.HasYValues then
+ Continue;
+
+ inc(destCol);
+
+ r1 := series.TitleAddr.Row; // Series title
+ c1 := series.TitleAddr.Col;
+ txt := seriesSheet.ReadAsText(r1, c1);
+ auxsheet.WriteText(0, destCol, txt);
+ refStr := GetSheetCellRangeString_ODS(seriesSheet.Name, seriesSheet.Name, r1, c1, r1, c1, rfAllRel, false);
+ if (refStr <> '') then
+ auxSheet.WriteComment(0, destCol, refStr); // Store title reference as comment for svg node
+
+ r1 := series.YRange.Row1;
+ c1 := series.YRange.Col1;
+ r2 := series.YRange.Row2;
+ c2 := series.YRange.Col2;
+ refStr := GetSheetCellRangeString_ODS(seriesSheet.Name, seriesSheet.Name, r1, c1, r2, c2, rfAllRel, false);
+ for j := 1 to series.GetYCount do
+ begin
+ if series.YValuesInCol then
+ srcCell := seriesSheet.FindCell(r1 + j - 1, c1)
+ else
+ srcCell := seriesSheet.FindCell(r1, c1 + j - 1);
+ if srcCell <> nil then
+ begin
+ destCell := auxSheet.GetCell(j, destCol);
+ seriesSheet.CopyValue(srcCell, destCell);
+ end else
+ destCell := auxSheet.WriteNumber(j, destCol, j);
+ end;
+
+ if (refStr <> '') then
+ auxSheet.WriteComment(1, destCol, refStr); // Store y range reference as comment for svg node
+ end;
+end;
+
+{ Writes the chart's data table. NOTE: The chart gets its data from this table
+ rather than from the worksheet! }
+procedure TsSpreadOpenDocWriter.WriteChartTable(AStream: TStream; AChart: TsChart;
+ AIndent: Integer);
+var
+ auxBook: TsWorkbook;
+ auxSheet: TsWorksheet;
+
+ procedure WriteAuxCell(AIndent: Integer; ACell: PCell);
+ var
+ ind: String;
+ valueType: String;
+ value: String;
+ officeValue: String;
+ draw: String;
+ begin
+ ind := DupeString(' ', AIndent);
+ if (ACell = nil) or (ACell^.ContentType = cctEmpty) then
+ begin
+ AppendToStream(AStream,
+ ind + '' + LE +
+ ind + ' ' + LE +
+ ind + '' + LE
+ );
+ exit;
+ end;
+
+ case ACell^.ContentType of
+ cctUTF8String:
+ begin
+ valueType := 'string';
+ value := auxSheet.ReadAsText(ACell);
+ officeValue := '';
+ end;
+ cctNumber, cctDateTime, cctBool:
+ begin
+ valueType := 'float';
+ value := Format('%g', [auxSheet.ReadAsNumber(ACell)], FPointSeparatorSettings);
+ end;
+ cctError:
+ begin
+ valueType := 'float';
+ value := 'NaN';
+ end;
+ end;
+
+ if ACell^.ContentType = cctUTF8String then
+ officeValue := ''
+ else
+ officeValue := ' office:value="' + value + '"';
+
+ if auxSheet.HasComment(ACell) then
+ begin
+ draw := auxSheet.ReadComment(ACell);
+ if draw <> '' then
+ draw := Format(
+ ind + ' ' + LE +
+ ind + ' %s' + LE +
+ ind + ' ' + LE, [draw]);
+ end else
+ draw := '';
+ AppendToStream(AStream, Format(
+ ind + '' + LE +
+ ind + ' %s' + LE +
+ draw +
+ ind + '' + LE,
+ [ valueType, officevalue, value ]
+ ));
+ end;
+
+var
+ ind: String;
+ colCountStr: String;
+ n: Integer;
+ r, c: Cardinal;
+begin
+ ind := DupeString(' ', AIndent);
+ n := AChart.Series.Count;
+ if n > 0 then
+ begin
+ if AChart.IsScatterChart then
+ n := n * 2;
+ colCountStr := Format('table:number-columns-repeated="%d"', [n]);
+ end else
+ colCountStr := '';
+
+ AppendToStream(AStream, Format(
+ ind + '' + LE +
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+ ind + ' ' + LE, [ colCountStr ]
+ ));
+
+ auxBook := TsWorkbook.Create;
+ try
+ auxSheet := auxBook.AddWorksheet('chart');
+ PrepareChartTable(AChart, auxSheet);
+
+ // Header rows (containing the series names)
+ AppendToStream(AStream,
+ ind + ' ' + LE +
+ ind + ' ' + LE );
+ for c := 0 to auxSheet.GetLastColIndex do
+ WriteAuxCell(AIndent + 6, auxSheet.FindCell(0, c));
+ AppendToStream(AStream,
+ ind + ' ' + LE +
+ ind + ' ' + LE
+ );
+
+ // Write data rows
+ AppendToStream(AStream,
+ ind + ' ' + LE
+ );
+ for r := 1 to auxSheet.GetLastRowIndex do
+ begin
+ AppendToStream(AStream,
+ ind + ' ' + LE
+ );
+ for c := 0 to auxSheet.GetlastColIndex do
+ WriteAuxCell(AIndent + 6, auxSheet.FindCell(r, c));
+ AppendToStream(AStream,
+ ind + ' ' + LE
+ );
+ end;
+ AppendToStream(AStream,
+ ind + ' ' + LE +
+ ind + '' + LE
+ );
+
+ //auxBook.WriteToFile('table.ods', true);
+ finally
+ auxBook.Free;
+ end;
+end;
+
+{ Writes the chart's title to the xml stream }
+procedure TsSpreadOpenDocWriter.WriteChartTitle(AStream: TStream; AChart: TsChart;
+ IsSubTitle: Boolean; AIndent: Integer);
+var
+ ind: String;
+ elementName: String;
+ titleStyleID: Integer;
+ cap: String;
+begin
+ if (not AChart.Title.Visible) or (AChart.Title.Caption = '') then
+ exit;
+
+ ind := DupeString(' ', AIndent);
+ if IsSubTitle then
+ begin
+ elementName := 'subtitle';
+ titleStyleID := 3;
+ cap := AChart.SubTitle.Caption;
+ end else
+ begin
+ elementName := 'title';
+ titleStyleID := 2;
+ cap := AChart.Title.Caption;
+ end;
+ AppendToStream(AStream, Format(
+// ind + '' + LE +
+ ind + '' + LE +
+ ind + ' %s' + LE +
+ ind + '' + lE,
+ [ elementName, titleStyleID, cap, elementName ], FPointSeparatorSettings
+ ));
+end;
+
{ Writes the "office:automatic" > "style:style" node for table-column style family
in content.xml for all column records. }
procedure TsSpreadOpenDocWriter.WriteColStyles(AStream: TStream);
@@ -7079,6 +7938,21 @@ begin
end;
end;
+procedure TsSpreadOpenDocWriter.WriteGraphicStyles(AStream: TStream);
+begin
+ if TsWorkbook(FWorkbook).GetChartCount = 0 then
+ exit;
+
+ AppendToStream(AStream,
+ '' +
+ '' +
+ '' +
+ ''
+ );
+end;
+
procedure TsSpreadOpenDocWriter.WriteMasterStyles(AStream: TStream);
var
defFnt: TsHeaderFooterFont;
@@ -7330,6 +8204,19 @@ begin
end;
+procedure TsSpreadOpenDocWriter.WriteParagraphStyles(AStream: TStream);
+begin
+ if TsWorkbook(FWorkbook).GetChartCount = 0 then
+ exit;
+
+ AppendToStream(AStream,
+ '' +
+ '' +
+ '' +
+ ''
+ );
+end;
+
function TsSpreadOpenDocWriter.WritePrintContentStyleXMLAsString(
const AFormat: TsCellFormat): String;
begin
@@ -8905,22 +9792,94 @@ procedure TsSpreadOpenDocWriter.WriteShapes(AStream: TStream;
}
var
+ sheet: TsWorksheet absolute ASheet;
i: Integer;
+ sheetIdx: Integer;
+ chart: TsChart;
+ series: TsChartSeries;
img: TsImage;
imgType: TsImageType;
r1,c1,r2,c2: Cardinal;
roffs1,coffs1, roffs2, coffs2: Double;
x, y, w, h: Double;
+ xRngAddr, yRngAddr, titleAddr: String;
xml: String;
target, bookmark: String;
u: TURI;
begin
- if (ASheet as TsWorksheet).GetImageCount = 0 then
+ if (sheet.GetImageCount = 0) and (sheet.GetChartCount = 0) then
exit;
AppendToStream(AStream,
'');
+ sheetIdx := sheet.Index;
+ for i:=0 to TsWorkbook(FWorkbook).GetChartCount-1 do
+ begin
+ chart := TsWorkbook(FWorkbook).GetChartByIndex(i);
+ if chart.SheetIndex <> sheetIdx then
+ Continue;
+ if chart.Series.Count = 0 then
+ Continue;
+
+ r1 := chart.Row;
+ c1 := chart.Col;
+ rOffs1 := chart.OffsetX;
+ cOffs1 := chart.OffsetY;
+ w := chart.Width;
+ h := chart.Height;
+ sheet.CalcDrawingExtent(true, w, h, r1, c1, r2, c2, rOffs1, cOffs1, rOffs2, cOffs2, x, y);
+
+ series := chart.Series[0];
+ if (series.XRange.Row1 <> series.XRange.Row2) or (series.XRange.Col1 <> series.XRange.Col2) then
+ xRngAddr := GetSheetCellRangeString_ODS(
+ sheet.Name, sheet.Name,
+ series.XRange.Row1, series.XRange.Col1,
+ series.XRange.Row2, series.XRange.Col2,
+ rfAllRel, false
+ )
+ else
+ xRngAddr := GetSheetCellRangeString_ODS(
+ sheet.Name, sheet.Name,
+ series.LabelRange.Row1, series.LabelRange.Col1,
+ series.LabelRange.Row2, series.LabelRange.Col2,
+ rfAllRel, false
+ );
+ yRngAddr := GetSheetCellRangeString_ODS(
+ sheet.Name, sheet.Name,
+ series.YRange.Row1, series.YRange.Col1,
+ series.YRange.Row2, series.YRange.Col2,
+ rfAllRel, false
+ );
+ titleAddr := GetSheetCellRangeString_ODS(
+ sheet.Name, sheet.Name,
+ series.TitleAddr.row, series.TitleAddr.Col,
+ series.TitleAddr.row, series.TitleAddr.Col,
+ rfAllRel, false
+ );
+
+ xml := Format(
+ '' +
+ ' ' +
+ '' +
+ '' +
+ '', [
+ i+1,
+ w, h,
+ x, y,
+ xRngAddr + ' ' + titleAddr + ' ' + yrngAddr,
+ chart.Index+1
+ ], FPointSeparatorSettings);
+
+ AppendToStream(AStream, xml);
+ end;
+
for i:=0 to (ASheet as TsWorksheet).GetImageCount-1 do
begin
img := (ASheet as TsWorksheet).GetImage(i);
diff --git a/components/fpspreadsheet/source/common/fpspreadsheet.pas b/components/fpspreadsheet/source/common/fpspreadsheet.pas
index 3b2a243a7..3ab2e4828 100644
--- a/components/fpspreadsheet/source/common/fpspreadsheet.pas
+++ b/components/fpspreadsheet/source/common/fpspreadsheet.pas
@@ -24,7 +24,7 @@ uses
{$endif}{$endif}{$endif}
Classes, SysUtils, fpimage, avglvltree, lconvencoding,
fpsTypes, fpsExprParser, fpsClasses, fpsNumFormat, fpsPageLayout,
- fpsImages, fpsConditionalFormat;
+ fpsImages, fpsConditionalFormat, fpsChart;
type
{ Forward declarations }
@@ -625,6 +625,15 @@ type
procedure AddHyperlinkToImage(AImageIndex: Integer; ATarget: String;
AToolTip: String = '');
+ procedure CalcDrawingExtent(
+ UsePixels: Boolean; AWidth, AHeight: Double;
+ var ARow1, ACol1: Cardinal; out ARow2, ACol2: Cardinal;
+ ARowOffs1, AColOffs1: Double; out ARowOffs2, AColOffs2: Double;
+ out x,y: Double);
+
+ { Chart support }
+ function GetChartCount: Integer;
+
{ Protection }
procedure Protect(AEnable: Boolean);
@@ -771,6 +780,7 @@ type
FCellFormatList: TsCellFormatList;
FConditionalFormatList: TsConditionalFormatList;
FEmbeddedObjList: TFPList;
+ FCharts: TsChartList;
{ Internal methods }
class procedure GetFormatFromFileHeader(const AFileName: TFileName;
@@ -911,6 +921,12 @@ type
function HasEmbeddedSheetImages: Boolean;
procedure RemoveAllEmbeddedObj;
+ { Charts }
+ function AddChart(ASheet: TsBasicWorksheet; ARow, ACol: Cardinal;
+ AWidth, AHeight: Double; AOffsetX: Double = 0.0; AOffsetY: Double = 0.0): TsChart;
+ function GetChartByIndex(AIndex: Integer): TsChart;
+ function GetChartCount: Integer;
+
{ Utilities }
function ConvertUnits(AValue: Double; AFromUnits, AToUnits: TsSizeUnits): Double;
procedure UpdateCaches;
@@ -1412,6 +1428,147 @@ begin
// to others sheets. If not call the faster "CalcSheet".
end;
+{@@ ----------------------------------------------------------------------------
+ Calculates the extent of an image or chart (in Excel-speak: a "drawing")
+
+ @param UsePixels If @TRUE then pixels are used for calculation - this improves the display of images in Excel
+ @param AWidth Width of the drawing
+ @param AHeight Height of the drawing
+ @param ARow1 Index of the row containing the top edge of the drawing
+ @param ACol1 Index of the column containing the left edege of the drawing
+ @param ARow2 Index of the row containing the right edge of the drawing
+ @param ACol2 Index of the column containing the bottom edge of the drawing
+ @param ARowOffs1 Distance between the top edge of drawing and row 1
+ @param AColOffs1 Distance between the left edge of drawing and column 1
+ @param ARowOffs2 Distance between the bottom edge of drawing and top of row 2
+ @param AColOffs2 Distance between the right edge of drawing and left of col 2
+ @param x Absolute coordinate of left edge of the drawing
+ @param y Absolute coordinate of top edge of the drawing
+
+ All dimensions are in workbook units
+-------------------------------------------------------------------------------}
+procedure TsWorksheet.CalcDrawingExtent(
+ UsePixels: Boolean; AWidth, AHeight: Double;
+ var ARow1, ACol1: Cardinal; out ARow2, ACol2: Cardinal;
+ ARowOffs1, AColOffs1: Double; out ARowOffs2, AColOffs2: Double;
+ out x,y: Double);
+var
+ colW, rowH: Double;
+ totW, totH: Double;
+ r, c: Integer;
+ w_px, h_px: Integer;
+ totH_px, rowH_px: Integer;
+ totW_px, colW_px: Integer;
+ ppi: Integer;
+ u: TsSizeUnits;
+begin
+ // Abbreviations
+ ppi := ScreenPixelsPerInch;
+ u := FWorkbook.Units;
+
+ // Find x coordinate of left graphic edge, in workbook units
+ x := AColOffs1;
+ for c := 0 to ACol1-1 do
+ begin
+ colW := GetColWidth(c, u);
+ x := x + colW;
+ end;
+ // Find y coordinate of top image edge, in workbook units.
+ y := ARowOffs1;
+ for r := 0 to ARow1 - 1 do
+ begin
+ rowH := CalcRowHeight(r);
+ y := y + rowH;
+ end;
+
+ if UsePixels then
+ // Use pixels for calculation. Better for Excel, maybe due to rounding error?
+ begin
+ // If we don't know the ppi of the screen the calculation is not exact!
+ w_px := ptsToPx(FWorkbook.ConvertUnits(AWidth, u, suPoints), ppi);
+ h_px := ptsToPx(FWorkbook.ConvertUnits(AHeight, u, suPoints), ppi);
+ // Find column with right image edge. Find horizontal within-cell-offsets
+ totW_px := -ptsToPx(FWorkbook.ConvertUnits(AColOffs1, u, suPoints), ppi);
+ ACol2 := ACol1;
+ while (totW_px < w_px) do
+ begin
+ colW := GetColWidth(ACol2, u);
+ colW_px := ptsToPx(FWorkbook.ConvertUnits(colW, u, suPoints), ppi);
+ totW_px := totW_px + colW_px;
+ if totW_px > w_px then
+ begin
+ AColOffs2 := FWorkbook.ConvertUnits(pxToPts(colW_px - (totW_px - w_px), ppi), suPoints, u);
+ break;
+ end;
+ inc(ACol2);
+ end;
+ // Find row with bottom image edge. Find vertical within-cell-offset.
+ totH_px := -ptsToPx(FWorkbook.ConvertUnits(ARowOffs1, u, suPoints), ppi);
+ ARow2 := ARow1;
+ while (totH_px < h_px) do
+ begin
+ rowH := CalcRowHeight(ARow2);
+ rowH_px := ptsToPx(FWorkbook.ConvertUnits(rowH, u, suPoints), ppi);
+ totH_px := totH_px + rowH_px;
+ if totH_px > h_px then
+ begin
+ ARowOffs2 := FWorkbook.ConvertUnits(pxToPts(rowH_px - (totH_px - h_px), ppi), suPoints, u);
+ break;
+ end;
+ inc(ARow2);
+ end;
+ end
+ else // Use workbook units for calculation
+ begin
+ // Find row with bottom image edge. Find horizontal within-cell offset
+ totW := -AColOffs1;
+ ACol2 := ACol1;
+ while (totW < AWidth) do
+ begin
+ colW := GetColWidth(ACol2, u);
+ totW := totW + colW;
+ if totW >= AWidth then
+ begin
+ AColOffs2 := colW - (totW - AWidth);
+ break;
+ end;
+ inc(ACol2);
+ end;
+ // Find row with right image edge. Find vertical within-cell-offsets
+ totH := -ARowOffs1;
+ ARow2 := ARow1;
+ while (totH < AHeight) do
+ begin
+ rowH := CalcRowHeight(ARow2);
+ totH := totH + rowH;
+ if totH >= AHeight then
+ begin
+ ARowOffs2 := rowH - (totH - AHeight);
+ break;
+ end;
+ inc(ARow2);
+ end;
+ end;
+end;
+
+{@@ ----------------------------------------------------------------------------
+ Determines the count of charts on this worksheet
+-------------------------------------------------------------------------------}
+function TsWorksheet.GetChartCount: Integer;
+var
+ i: Integer;
+ chart: TsChart;
+ idx: Integer;
+begin
+ Result := 0;
+ idx := GetIndex;
+ for i := 0 to Workbook.GetChartCount-1 do
+ begin
+ chart := Workbook.GetChartByIndex(i);
+ if chart.SheetIndex = idx then inc(Result);
+ end;
+end;
+
{@@ ----------------------------------------------------------------------------
Calculates all formulas of the worksheet
@@ -2318,7 +2475,7 @@ begin
WriteFormula(AToRow, AToCol, formulaStr);
// Fix formula references to the source cell (ACell)
- for i := 0 to FWorkbook.GetWorksheetcount-1 do begin
+ for i := 0 to FWorkbook.GetWorksheetCount-1 do begin
sheet := FWorkbook.GetWorksheetByIndex(i);
sheet.Formulas.FixReferenceToMovedCell(ACell, AToRow, AToCol, self);
end;
@@ -6383,6 +6540,7 @@ begin
FCellFormatList := TsCellFormatList.Create(false);
FConditionalFormatList := TsConditionalFormatList.Create;
FEmbeddedObjList := TFPList.Create;
+ FCharts := TsChartList.Create;
// Add default cell format
InitFormatRecord(fmt);
@@ -6415,6 +6573,7 @@ begin
RemoveAllEmbeddedObj;
FEmbeddedObjList.Free;
+ FCharts.Free;
inherited Destroy;
end;
@@ -7627,6 +7786,7 @@ end;
{$include fpspreadsheet_hyperlinks.inc} // hyperlinks
{$include fpspreadsheet_embobj.inc} // embedded objects
{$include fpspreadsheet_clipbrd.inc} // clipboard access
+{$include fpspreadsheet_chart.inc} // chart support
end. {** End Unit: fpspreadsheet }
diff --git a/components/fpspreadsheet/source/common/fpspreadsheet_embobj.inc b/components/fpspreadsheet/source/common/fpspreadsheet_embobj.inc
index 700e65ae1..e81334d34 100644
--- a/components/fpspreadsheet/source/common/fpspreadsheet_embobj.inc
+++ b/components/fpspreadsheet/source/common/fpspreadsheet_embobj.inc
@@ -72,6 +72,28 @@ end;
All dimensions are in workbook units
-------------------------------------------------------------------------------}
+procedure TsWorksheet.CalcImageExtent(AIndex: Integer; UsePixels: Boolean;
+ out ARow1, ACol1, ARow2, ACol2: Cardinal;
+ out ARowOffs1, AColOffs1, ARowOffs2, AColOffs2: Double;
+ out x,y, AWidth, AHeight: Double);
+var
+ img: TsImage;
+ obj: TsEmbeddedObj;
+begin
+ img := GetImage(AIndex);
+ ARow1 := img.Row;
+ ACol1 := img.Col;
+ ARowOffs1 := img.OffsetX; // in workbook units
+ AColOffs1 := img.OffsetY; // in workbook units
+
+ obj := FWorkbook.GetEmbeddedObj(img.Index);
+ AWidth := obj.ImageWidth * img.ScaleX; // in workbook units
+ AHeight := obj.ImageHeight * img.ScaleY; // in workbook units
+
+ CalcDrawingExtent(UsePixels, AWidth, AHeight, ARow1, ACol1, ARow2, ACol2,
+ ARowOffs1, AColOffs1, ARowOffs2, AColOffs2, x, y);
+end;
+(*
procedure TsWorksheet.CalcImageExtent(AIndex: Integer; UsePixels: Boolean;
out ARow1, ACol1, ARow2, ACol2: Cardinal;
out ARowOffs1, AColOffs1, ARowOffs2, AColOffs2: Double;
@@ -172,7 +194,7 @@ begin
end;
end;
end;
-
+*)
{@@ ----------------------------------------------------------------------------
Returns the parameters of the image stored in the internal image list at
diff --git a/components/fpspreadsheet/source/common/fpsutils.pas b/components/fpspreadsheet/source/common/fpsutils.pas
index e69ab134a..176103d13 100644
--- a/components/fpspreadsheet/source/common/fpsutils.pas
+++ b/components/fpspreadsheet/source/common/fpsutils.pas
@@ -127,11 +127,18 @@ function TryStrToCellRange_ODS(const AStr: String; out ASheet1, ASheet2: String;
out ARow1, ACol1, ARow2, ACol2: Cardinal; out AFlags: TsRelFlags): Boolean;
function GetCellRangeString_ODS(ASheet1, ASheet2: String; ARow1, ACol1, ARow2, ACol2: Cardinal;
- AFlags: TsRelFlags = rfAllRel): String; overload;
+ AFlags: TsRelFlags = rfAllRel; WithBrackets: Boolean = true): String; overload;
function GetCellRangeString_ODS(ARow1, ACol1, ARow2, ACol2: Cardinal;
- AFlags: TsRelFlags = rfAllRel; Compact: Boolean = false): String; overload;
+ AFlags: TsRelFlags = rfAllRel; Compact: Boolean = false;
+ WithBrackets: Boolean = true): String; overload;
function GetCellRangeString_ODS(ARange: TsCellRange;
- AFlags: TsRelFlags = rfAllRel; Compact: Boolean = false): String; overload;
+ AFlags: TsRelFlags = rfAllRel; Compact: Boolean = false;
+ WithBrackets: Boolean = true): String; overload;
+function GetSheetCellString_ODS(ASheet: String; ARow, ACol: Cardinal;
+ AFlags: TsRelFlags = rfAllRel; WithBrackets: Boolean = true): String;
+function GetSheetCellRangeString_ODS(ASheet1, ASheet2: String;
+ ARow1, ACol1, ARow2, ACol2: Cardinal; AFlags: TsRelFlags = rfAllRel;
+ WithBrackets: Boolean = true): String;
// Error strings
@@ -1386,7 +1393,8 @@ end;
Calculates a cell range string with sheet specification in OpenDocument syntax
-------------------------------------------------------------------------------}
function GetCellRangeString_ODS(ASheet1, ASheet2: String;
- ARow1, ACol1, ARow2, ACol2: Cardinal; AFlags: TsRelFlags = rfAllRel): String;
+ ARow1, ACol1, ARow2, ACol2: Cardinal; AFlags: TsRelFlags = rfAllRel;
+ WithBrackets: Boolean = true): String;
var
s1, s2: String;
begin
@@ -1402,37 +1410,65 @@ begin
if (ASheet1 = '') and (ASheet2 = '') then
begin
if s1 = s2 then
- Result := '[.' + s1 + ']' // --> [.A1]
+ Result := '.' + s1 // --> [.A1]
else
- Result := Format('[.%s:.%s]', [s1, s2]) // --> [.A1:.B3]
+ Result := Format('.%s:.%s', [s1, s2]) // --> [.A1:.B3]
end else
if (ASheet2 = '') or (ASheet1 = ASheet2) then begin
if s1 = s2 then
- Result := Format('[%s.%s]', [ASheet1, s1]) // [Sheet1.A1]
+ Result := Format('%s.%s', [ASheet1, s1]) // [Sheet1.A1]
else
- Result := Format('[%s.%s:.%s]', [ASheet1, s1, s2]); // [Sheet1.A1:.B2]
+ Result := Format('%s.%s:.%s', [ASheet1, s1, s2]); // [Sheet1.A1:.B2]
end else
- Result := Format('[%s.%s:%s.%s]', [ASheet1, s1, ASheet2, s2]); // [Sheet.A1:Sheet2.B2]
+ Result := Format('%s.%s:%s.%s', [ASheet1, s1, ASheet2, s2]); // [Sheet.A1:Sheet2.B2]
+
+ if WithBrackets then
+ Result := '[' + Result + ']';
end;
function GetCellRangeString_ODS(ARow1, ACol1, ARow2, ACol2: Cardinal;
- AFlags: TsRelFlags = rfAllRel; Compact: Boolean = false): String;
+ AFlags: TsRelFlags = rfAllRel; Compact: Boolean = false; WithBrackets: Boolean = true): String;
begin
if Compact and (ARow1 = ARow2) and (ACol1 = ACol2) then
- Result := Format('[.%s]', [GetCellString(ARow1, ACol1, AFlags)])
+ Result := Format('.%s', [GetCellString(ARow1, ACol1, AFlags)])
else
- Result := Format('[.%s%s%s%d:.%s%s%s%d]', [
+ Result := Format('.%s%s%s%d:.%s%s%s%d', [
RELCHAR[rfRelCol in AFlags], GetColString(ACol1),
RELCHAR[rfRelRow in AFlags], ARow1 + 1,
RELCHAR[rfRelCol2 in AFlags], GetColString(ACol2),
RELCHAR[rfRelRow2 in AFlags], ARow2 + 1
]);
+ if WithBrackets then
+ Result := '[' + Result + ']';
end;
function GetCellRangeString_ODS(ARange: TsCellRange;
- AFlags: TsRelFlags = rfAllRel; Compact: Boolean = false): String;
+ AFlags: TsRelFlags = rfAllRel; Compact: Boolean = false;
+ WithBrackets: Boolean = true): String;
begin
- Result := GetCellRangeString_ODS(ARange, AFlags, Compact);
+ Result := GetCellRangeString_ODS(ARange, AFlags, Compact, WithBrackets);
+end;
+
+function GetSheetCellString_ODS(ASheet: String; ARow, ACol: Cardinal;
+ AFlags: TsRelFlags = rfAllRel; WithBrackets: Boolean = true): String;
+begin
+ Result := Format('%s.%s%s%s%d', [
+ ASheet, RELCHAR[rfRelCol in AFlags], GetColString(ACol), RELCHAR[rfRelRow in AFlags], ARow + 1
+ ]);
+ if WithBrackets then
+ Result := '[' + Result + ']';
+end;
+
+function GetSheetCellRangeString_ODS(ASheet1, ASheet2: String;
+ ARow1, ACol1, ARow2, ACol2: Cardinal; AFlags: TsRelFlags = rfAllRel;
+ WithBrackets: Boolean = true): String;
+begin
+ Result := Format('%s.%s%s%s%d:%s.%s%s%s%d', [
+ ASheet1, RELCHAR[rfRelCol in AFlags], GetColString(ACol1), RELCHAR[rfRelRow in AFlags], ARow1 + 1,
+ ASheet2, RELCHAR[rfRelCol2 in AFlags], GetColString(ACol2), RELCHAR[rfRelRow2 in AFlags], ARow2 + 1
+ ]);
+ if WithBrackets then
+ Result := '[' + Result + ']';
end;
diff --git a/components/fpspreadsheet/source/common/xlsxooxml.pas b/components/fpspreadsheet/source/common/xlsxooxml.pas
index 46b07054b..e98decf89 100644
--- a/components/fpspreadsheet/source/common/xlsxooxml.pas
+++ b/components/fpspreadsheet/source/common/xlsxooxml.pas
@@ -184,6 +184,11 @@ type
procedure WriteCFColorRangeRule(AStream: TStream; ARule: TsCFColorRangeRule; APriority: Integer);
procedure WriteCFDataBarRule(AStream: TStream; ARule: TsCFDatabarRule; APriority: Integer);
procedure WriteCFIconSetRule(AStream: TStream; ARule: TsCFIconSetRule; APriority: Integer);
+ procedure WriteChart(AStream: TStream; AChartIndex: Integer);
+ procedure WriteChartColors;
+ procedure WriteChartRels;
+ procedure WriteCharts;
+ procedure WriteChartStyles;
procedure WriteColBreaks(AStream: TStream; AWorksheet: TsBasicWorksheet);
procedure WriteCols(AStream: TStream; AWorksheet: TsBasicWorksheet);
procedure WriteComments(AWorksheet: TsBasicWorksheet);
@@ -241,6 +246,10 @@ type
FSMedia: array of TStream;
FSSheets: array of TStream;
FSSheetRels: array of TStream;
+ FSCharts: array of TStream;
+ FSChartRels: array of TStream;
+ FSChartStyles: array of TStream;
+ FSChartColors: array of TStream;
FSComments: array of TStream;
FSDrawings: array of TStream;
FSDrawingsRels: array of TStream;
@@ -297,7 +306,7 @@ implementation
uses
variants, strutils, dateutils, math, lazutf8, LazFileUtils, uriparser, typinfo,
{%H-}fpsPatches, fpSpreadsheet, fpsCrypto, fpsExprParser,
- fpsStrings, fpsStreams, fpsClasses, fpsImages;
+ fpsStrings, fpsStreams, fpsClasses, fpsImages, fpsChart;
const
{ OOXML general XML constants }
@@ -319,6 +328,8 @@ const
OOXML_PATH_XL_STRINGS = 'xl/sharedStrings.xml';
OOXML_PATH_XL_WORKSHEETS = 'xl/worksheets/';
OOXML_PATH_XL_WORKSHEETS_RELS = 'xl/worksheets/_rels/';
+ OOXML_PATH_XL_CHARTS = 'xl/charts/';
+ OOXML_PATH_XL_CHARTS_RELS = 'xl/charts/_rels/';
OOXML_PATH_XL_DRAWINGS = 'xl/drawings/';
OOXML_PATH_XL_DRAWINGS_RELS = 'xl/drawings/_rels/';
OOXML_PATH_XL_THEME = 'xl/theme/theme1.xml';
@@ -341,8 +352,12 @@ const
SCHEMAS_VMLDRAWING = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing';
SCHEMAS_HYPERLINK = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink';
SCHEMAS_IMAGE = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image';
+ SCHEMAS_CHART = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart';
SCHEMAS_SPREADML = 'http://schemas.openxmlformats.org/spreadsheetml/2006/main';
SCHEMAS_CORE = 'http://schemas.openxmlformats.org/package/2006/metadata/core-properties';
+ SCHEMAS_DRAWINGML_CHART= 'http://schemas.openxmlformats.org/drawingml/2006/chart';
+ SCHEMAS_CHART_COLORS = 'http://schemas.microsoft.com/office/2011/relationships/chartColorStyle';
+ SCHEMAS_CHART_STYLE = 'http://schemas.microsoft.com/office/2011/relationships/chartStyle';
{ OOXML mime types constants }
MIME_XML = 'application/xml';
@@ -357,12 +372,15 @@ const
MIME_STRINGS = MIME_SPREADML + '.sharedStrings+xml';
MIME_COMMENTS = MIME_SPREADML + '.comments+xml';
MIME_DRAWING = MIME_OFFICEDOCUMENT + '.drawing+xml'; // 'application/vnd.openxmlformats-officedocument.drawing+xml
+ MIME_DRAWINGML_CHART = MIME_OFFICEDOCUMENT + '.drawingml.chart+xml';
MIME_VMLDRAWING = MIME_OFFICEDOCUMENT + '.vmlDrawing';
LAST_PALETTE_INDEX = 63;
CFB_SIGNATURE = $E11AB1A1E011CFD0; // Compound File Binary Signature
+ LE = LineEnding;
+
type
TFillListData = class
PatternType: String;
@@ -4689,8 +4707,10 @@ procedure TsSpreadOOXMLWriter.Get_rId(AWorksheet: TsBasicWorksheet;
var
next_rId: Integer;
sheet: TsWorksheet;
+ book: TsWorkbook;
begin
sheet := AWorksheet as TsWorksheet;
+ book := sheet.Workbook;
AComment_rId := -1;
AFirstHyperlink_rId := -1;
@@ -4705,8 +4725,8 @@ begin
inc(next_rId, 2); // there are two .rels entries in case of comments
end;
- // Embedded images next
- if sheet.GetImageCount > 0 then
+ // Charts or embedded images next
+ if (sheet.GetImageCount > 0) or (book.GetChartCount > 0) then
begin
ADrawing_rId := next_rId;
inc(next_rId);
@@ -5126,6 +5146,762 @@ begin
end;
+procedure TsSpreadOOXMLWriter.WriteChart(AStream: TStream;
+ AChartIndex: Integer);
+
+ function GetChartAxisXML(Indent: Integer; AChart: TsChart;
+ AxisID, OtherAxisID: Integer; NodeName, AxPos: String): String;
+ var
+ ind: String;
+ begin
+ ind := DupeString(' ', Indent);
+ Result := Format(
+ ind + '<%s>' + LE + // 1
+ ind + ' ' + LE + // 2
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+ ind + ' ' + LE + // 3
+ ind + ' ' + LE +
+ IfThen(AxPos='l', ind + ' ' + LE, '') +
+ ind + ' ' + LE + // 4
+ ind + ' ' + LE +
+ IfThen(AxPos='l', ind + ' ' + LE, '') +
+ IfThen(AxPos='b', ind + ' ' + LE, '') +
+ IfThen(AxPos='b', ind + ' ' + LE, '') +
+ IfThen(AxPos='b', ind + ' ' + LE, '') +
+ ind + '%s>', [ // 5
+ NodeName, // 1
+ AxisID, // 2
+ AxPos, // 3
+ OtherAxisID, // 4
+ NodeName // 5
+ ]);
+ end;
+
+ function GetBarChartXML(Indent: Integer; AChart: TsChart; CatAxID, ValAxID: Integer): String;
+ var
+ ind: String;
+ begin
+ ind := DupeString(' ', Indent);
+ Result := Format(
+ ind + '' + LE +
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+ ind + ' ' + LE + // categories axis (x)
+ ind + ' ' + LE + // values axis (y)
+ ind + '', [
+ CatAxID,
+ ValAxID
+ ]);
+ end;
+
+ function GetLegendXML(Indent: Integer; AChart: TsChart): string;
+ var
+ ind: String;
+ begin
+ ind := DupeString(' ', Indent);
+ Result :=
+ ind + '' + LE +
+ ind + ' ' + LE +
+ ind + ' ' + LE +
+ ind + '';
+ end;
+
+var
+ chart: TsChart;
+ xAxID, yAxID: Integer;
+begin
+ chart := TsWorkbook(FWorkbook).GetChartByIndex(AChartIndex);
+ AppendToStream(AStream,
+ XML_HEADER + LE);
+
+ AppendToStream(AStream,
+ '' + LE
+// ' xmlns:c16r2="http://schemas.microsoft.com/office/drawing/2015/06/chart">' + LE
+ );
+
+ xAxID := Random(MaxInt);
+ yAxID := Random(MaxInt);
+
+ AppendToStream(AStream,
+ ' ' + LE + // to do: get correct value
+ ' ' + LE +
+ // ' ' + LE +
+ // ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ GetBarChartXML(6, chart, xAxID, yAxID) + LE + // to be replaced by real series
+ GetChartAxisXML(6, chart, xAxID, yAxID, 'c:catAx', 'b') + LE +
+ GetChartAxisXML(6, chart, yAxID, xAxID, 'c:valAx', 'l') + LE +
+ ' ' + LE +
+ GetLegendXML(4, chart) + LE +
+ ' ' + LE +
+ ' ' + LE
+ );
+
+ AppendToStream(AStream,
+ '' + LE
+ );
+end;
+
+procedure TsSpreadOOXMLWriter.WriteChartColors;
+var
+ i, n: Integer;
+begin
+ n := TsWorkbook(FWorkbook).GetChartCount;
+ SetLength(FSChartColors, n);
+
+ for i := 0 to n - 1 do
+ begin
+ FSChartColors[i] := CreateTempStream(FWorkbook, Format('fpsChCol%d', [i]));
+
+ AppendToStream(FSChartColors[i],
+ XML_Header);
+
+ AppendToStream(FSChartColors[i],
+ '' + LE +
+ '' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' '+ LE +
+ ' ' + LE +
+ ' '+ LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' '+ LE +
+ '' + LE
+ );
+ end;
+end;
+
+{ Write the relationship file for all workbook's chart. The file defines which
+ xml files contain the ChartStyles and Colors needed by each chart. }
+procedure TsSpreadOOXMLWriter.WriteChartRels;
+var
+ i, n: Integer;
+begin
+ n := TsWorkbook(FWorkbook).GetChartCount;
+ SetLength(FSChartRels, n);
+
+ for i := 0 to n-1 do
+ begin
+ FSChartRels[i] := CreateTempStream(FWorkbook, Format('fpsChRels%d', [i]));
+ AppendToStream(FSChartRels[i],
+ XML_HEADER);
+ AppendToStream(FSChartRels[i], Format(
+ '' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ '' + LE, [
+ SCHEMAS_RELS,
+ i+1, SCHEMAS_CHART_STYLE,
+ i+1, SCHEMAS_CHART_COLORS
+ ]));
+ end;
+end;
+
+procedure TsSpreadOOXMLWriter.WriteCharts;
+var
+ i, n: Integer;
+ chart: TsChart;
+begin
+ n := TsWorkbook(FWorkbook).GetChartCount;
+ SetLength(FSCharts, n);
+
+ for i := 0 to n - 1 do
+ begin
+ FSCharts[i] := CreateTempStream(FWorkbook, Format('fpsCh%d', [i]));
+ WriteChart(FSCharts[i], i);
+ end;
+end;
+
+procedure TsSpreadOOXMLWriter.WriteChartStyles;
+var
+ i, n: Integer;
+begin
+ n := TsWorkbook(FWorkbook).GetChartCount;
+ SetLength(FSChartStyles, n);
+
+ for i := 0 to n - 1 do
+ begin
+ FSChartStyles[i] := CreateTempStream(FWorkbook, Format('fpsChSty%d', [i]));
+
+ AppendToStream(FSChartStyles[i],
+ XML_Header);
+
+ AppendToStream(FSChartStyles[i],
+ '' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+
+ ' '+ LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE+
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + lE+
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+
+ ' ' + LE +
+
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + lE +
+ ' ' + LE +
+ ' ' + LE +
+
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE+
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE+
+ ' ' + LE +
+ ' ' + LE +
+
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE+
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE+
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' +
+
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE+
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+
+ '' + LE
+ );
+ end;
+end;
+
procedure TsSpreadOOXMLWriter.WriteColBreaks(AStream: TStream;
AWorksheet: TsBasicWorksheet);
var
@@ -6271,41 +7047,47 @@ begin
end;
procedure TsSpreadOOXMLWriter.WriteDrawings(AWorksheet: TsBasicWorksheet);
-var
- i: Integer;
- img: TsImage;
- r1, c1, r2, c2: Cardinal;
- roffs1, coffs1, roffs2, coffs2: Double;
- x, y, w, h: Double;
- descr: String;
- hlink: String;
- xdr_cNvPr: String;
- rId: Integer;
- book: TsWorkbook;
- sheet: TsWorksheet absolute AWorksheet;
-begin
- if sheet.GetImageCount= 0 then
- exit;
- book := FWorkbook as TsWorkbook;
-
- SetLength(FSDrawings, FCurSheetNum + 1);
- FSDrawings[FCurSheetNum] := CreateTempStream(FWorkbook, Format('fpsD%d', [FCurSheetNum]));
-
- // Header
- AppendToStream(FSDrawings[FCurSheetNum],
- XML_HEADER,
- '');
-
- // Repeat for each image
- rId := 1;
- for i:=0 to sheet.GetImageCount - 1 do
+ function AnchorAsXML(Indent: Integer; AName: String; ACol, ARow: Integer;
+ AColOffset, ARowOffset: Double): String;
+ var
+ ind: String;
begin
- img := sheet.GetImage(i);
+ ind := DupeString(' ', Indent);
+ Result := Format(
+ ind + '<%s>' + LE +
+ ind + ' %d' + LE +
+ ind + ' %d' + LE +
+ ind + ' %d' + LE +
+ ind + ' %d' + LE +
+ ind + '%s>', [
+ AName,
+ ACol, mmToEMU(AColOffset),
+ ARow, mmToEMU(ARowOffset),
+ AName
+ ]);
+ end;
+
+ procedure DoWriteImage(AStream: TStream; AIndex: Integer; var RelID: Integer);
+ var
+ img: TsImage;
+ book: TsWorkbook;
+ sheet: TsWorksheet;
+ r1, c1, r2, c2: Cardinal;
+ roffs1, coffs1, roffs2, coffs2: Double;
+ x, y, w, h: Double;
+ descr: String;
+ hlink: String;
+ xdr_cNvPr: String;
+ begin
+ book := FWorkbook as TsWorkbook;
+ sheet := TsWorksheet(AWorksheet);
+
+ img := sheet.GetImage(AIndex);
if book.GetEmbeddedObj(img.Index).ImageType = itUnknown then
- Continue;
- sheet.CalcImageExtent(i, true,
+ exit;
+
+ sheet.CalcImageExtent(AIndex, true,
r1, c1, r2, c2,
roffs1, coffs1, roffs2, coffs2, // mm
x, y, w, h); // mm;
@@ -6315,96 +7097,184 @@ begin
// This part defines the relationship to the graphic and, if available, to
// a hyperlink.
- xdr_cNvPr := Format('id="%d" name="Graphic %d" descr="%s"', [i+3,i+2, descr]);
- if img.HyperlinkTarget <> '' then begin
+ xdr_cNvPr := Format('id="%d" name="Graphic %d" descr="%s"', [AIndex+3, AIndex+2, descr]);
+ if img.HyperlinkTarget <> '' then
+ begin
hlink := Format(' '' then
hlink := hlink + Format('tooltip="%s" ', [img.HyperlinkToolTip]);
hlink := hlink + '/>';
- xdr_cNvPr := '' +
- hlink +
- '';
+ xdr_cNvPr := ' ' + LE +
+ ' ' + hlink + LE +
+ ' ' + LE;
end else
- xdr_cNvPr := '';
+ xdr_cNvPr := ' ';
- AppendToStream(FSDrawings[FCurSheetNum],
- '');
- AppendToStream(FSDrawings[FCurSheetNum], Format(
- ''+
- '%d' +
- '%d'+
- '%d'+
- '%d'+
- '', [
- c1, mmToEMU(coffs1),
- r1, mmToEMU(roffs1)
- ]));
- AppendToStream(FSDrawings[FCurSheetNum], Format(
- ''+
- '%d'+
- '%d'+
- '%d'+
- '%d'+
- '', [
- c2, mmToEMU(coffs2),
- r2, mmToEMU(roffs2)
- ]));
- AppendToStream(FSDrawings[FCurSheetNum], Format(
- ''+
- ''+
- xdr_cNvPr +
- ''+
- ''+
- ''+
- ''+
- ''+
- ''+
- ''+
- ''+
- ''+
- ''+
- '' +
- ''+
- '' +
- '' + // size in EMU
- ''+
- ''+
- ''+
- ''+
- ''+
- '' +
- '', [
+ AppendToStream(AStream,
+ ' ' + LE);
+ AppendToStream(AStream,
+ AnchorAsXML(4, 'xdr:from', c1, r1, coffs1, roffs1) + LE
+ );
+ AppendToStream(AStream,
+ AnchorAsXML(4, 'xdr:to', c2, r2, coffs2, roffs2) + LE
+ );
+ AppendToStream(AStream, Format(
+ ' ' + LE +
+ ' ' + LE +
+ xdr_cNvPr + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE + // size in EMU
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE, [
// i + 3, i+2, descr,
- SCHEMAS_DOC_RELS, rId,
+ SCHEMAS_DOC_RELS, RelID,
mmToEMU(x), mmToEMU(y),
mmToEMU(w), mmToEMU(h)
]));
- AppendToStream(FSDrawings[FCurSheetNum],
- '');
+ AppendToStream(AStream,
+ ' ' + LE);
+ end;
+
+ procedure DoWriteChart(AStream: TStream; AChart: TsChart;
+ AChartNoInSheet: Integer; var RelID: Integer);
+ var
+ r1, c1, r2, c2: Cardinal;
+ roffs1, coffs1, roffs2, coffs2: Double;
+ x, y, w, h: Double;
+ sheet: TsWorksheet;
+ begin
+ r1 := AChart.Row;
+ c1 := AChart.Col;
+ rOffs1 := AChart.OffsetX;
+ cOffs1 := AChart.OffsetY;
+ w := AChart.Width;
+ h := AChart.Height;
+ sheet := TsWorkbook(FWorkbook).GetWorksheetByIndex(AChart.SheetIndex);
+ sheet.CalcDrawingExtent(true, w, h, r1, c1, r2, c2, rOffs1, cOffs1, rOffs2, cOffs2, x, y);
+
+ AppendToStream(AStream,
+ ' ' + LE);
+
+ AppendToStream(AStream,
+ AnchorAsXML(4, 'xdr:from', c1, r1, coffs1, roffs1) + LE
+ );
+ AppendToStream(AStream,
+ AnchorAsXML(4, 'xdr:to', c2, r2, coffs2, roffs2) + LE
+ );
+
+ AppendToStream(AStream, Format(
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE + // line 1
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE + // line 2
+ ' '+ LE + // line 3
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE +
+ ' ' + LE,
+ [
+ AChartNoInSheet + 1, AChartNoInSheet, // --> line 1
+ SCHEMAS_DRAWINGML_CHART, // --> line 2
+ SCHEMAS_DRAWINGML_CHART, SCHEMAS_DOC_RELS, RelID // --> line 3
+ ]));
+
+ AppendToStream(AStream,
+ ' ' + LE);
+ end;
+
+var
+ i, j: Integer;
+ rId: Integer;
+ sheet: TsWorksheet absolute AWorksheet;
+ sheetIdx: Integer;
+ chart: TsChart;
+begin
+ if (sheet.GetImageCount = 0) and (sheet.GetChartCount = 0) then
+ exit;
+
+ SetLength(FSDrawings, FCurSheetNum + 1);
+ FSDrawings[FCurSheetNum] := CreateTempStream(FWorkbook, Format('fpsD%d', [FCurSheetNum]));
+
+ // Header
+ AppendToStream(FSDrawings[FCurSheetNum],
+ XML_HEADER + LE +
+ '' + LE
+ );
+
+ // Repeat for each image
+ rId := 1;
+ for i:=0 to sheet.GetImageCount - 1 do
+ begin
+ DoWriteImage(FSDrawings[FCurSheetNum], i, rId);
inc(rId, 1);
end;
+
+ // Repeat for each chart
+ sheetIdx := sheet.Index;
+ j := 1; // Counts the charts in the current sheet
+ for i := 0 to TsWorkbook(FWorkbook).GetChartCount - 1 do
+ begin
+ chart := TsWorkbook(FWorkbook).GetChartByIndex(i);
+ if chart.SheetIndex = sheetIdx then
+ begin
+ DoWriteChart(FSDrawings[FCurSheetNum], chart, j, rId);
+ inc(j);
+ inc(rId);
+ end;
+ end;
+
+ // Close node
AppendToStream(FSDrawings[FCurSheetNum],
'');
end;
// For each sheet, writes a "drawingX.xml.rels" file to
-// folder "../drawings/_rels". X matches the (1-base) sheet index.
+// folder "../drawings/_rels".
+// X is a sequential number (starting 1), not neccessarily identical with the
+// sheet index.
// See also: WriteVmlDrawingRels
procedure TsSpreadOOXMLWriter.WriteDrawingRels(AWorksheet: TsBasicWorksheet);
var
i: Integer;
ext: String;
img: TsImage;
+ chart: TsChart;
rId: Integer;
target, bookmark: String;
u: TURI;
sheet: TsWorksheet absolute AWorksheet;
+ sheetIdx: Integer;
begin
- if (sheet.GetImageCount = 0) then
+ if (sheet.GetImageCount = 0) and (sheet.GetChartCount = 0) then
exit;
SetLength(FSDrawingsRels, FCurSheetNum + 1);
@@ -6413,7 +7283,7 @@ begin
// Header
AppendToStream(FSDrawingsRels[FCurSheetNum],
XML_HEADER + LineEnding,
- '' + LineEnding);
+ '' + LE);
// Repeat for each image
rId := 1;
@@ -6433,7 +7303,7 @@ begin
target := target + '#' + bookmark;
AppendToStream(FSDrawingsRels[FCurSheetNum], Format(
- ' ' + LineEnding, [
+ ' ' + LE, [
rId, SCHEMAS_HYPERLINK, target
]));
inc(rId);
@@ -6441,12 +7311,27 @@ begin
ext := GetImageTypeExt((FWorkbook as TsWorkbook).GetEmbeddedObj(img.Index).Imagetype);
AppendToStream(FSDrawingsRels[FCurSheetNum], Format(
- ' ' + LineEnding, [
+ ' ' + LE, [
rId, SCHEMAS_IMAGE, img.Index+1, ext
]));
inc(rId);
end;
+ // Repeat for each chart
+ sheetIdx := sheet.Index;
+ for i := 0 to TsWorkbook(FWorkbook).GetChartCount - 1 do
+ begin
+ chart := TsWorkbook(FWorkbook).GetChartByIndex(i);
+ if chart.SheetIndex = sheetIdx then
+ begin
+ AppendToStream(FSDrawingsRels[FCurSheetNum], Format(
+ ' ' + LE, [
+ rId, SCHEMAS_CHART, i+1
+ ]));
+ inc(rId);
+ end;
+ end;
+
AppendToStream(FSDrawingsRels[FCurSheetNum],
'');
end;
@@ -6812,7 +7697,8 @@ begin
// Anything to write?
if (sheet.Comments.Count = 0) and (sheet.Hyperlinks.Count = 0) and
- (sheet.GetImageCount = 0) and not (sheet.PageLayout.HasHeaderFooterImages)
+ (sheet.GetImageCount = 0) and (sheet.GetChartCount = 0) and
+ (not (sheet.PageLayout.HasHeaderFooterImages))
then
exit;
@@ -6861,10 +7747,10 @@ begin
end;
end;
- // Relationships for embedded images
+ // Relationships for charts or embedded images
// relationship with to the ../drawings/drawingX.xml file containing all
- // image infos. X is the 1-base sheet index
- if sheet.GetImageCount > 0 then
+ // chart/image infos. X is the 1-based sheet index
+ if (sheet.GetImageCount > 0) or (sheet.GetChartCount > 0) then
AppendToStream(FSSheetRels[FCurSheetNum], Format(
' ' + LineEnding,
[rId_Drawing, FCurSheetNum + 1, SCHEMAS_DRAWING]
@@ -7124,13 +8010,19 @@ begin
'');
end;
+ { Write all charts }
+ WriteChartRels;
+ WriteChartStyles;
+ WriteChartColors;
+ WriteCharts;
+
{ Workbook relations - Mark relation to all sheets }
WriteWorkbookRels(FSWorkbookRels);
end;
procedure TsSpreadOOXMLWriter.WriteContentTypes;
var
- i,j: Integer;
+ i, j, n: Integer;
imgext: TStringList;
ext: String;
sheet: TsWorksheet;
@@ -7172,6 +8064,19 @@ begin
AppendToStream(FSContentTypes,
'' + LineEnding);
+ n := 1;
+ for i:=0 to book.GetWorksheetCount-1 do
+ begin
+ sheet := book.GetWorksheetByIndex(i);
+ for j:=0 to sheet.GetChartCount-1 do
+ begin
+ AppendToStream(FSContentTypes, Format(
+ '' + LE,
+ [n, MIME_DRAWINGML_CHART]));
+ inc(n);
+ end;
+ end;
+
for i:=1 to book.GetWorksheetCount do
begin
AppendToStream(FSContentTypes, Format(
@@ -7541,7 +8446,10 @@ var
rId_Comments: Integer;
rId_FirstHyperlink: Integer;
rId_Drawing, rId_DrawingHF: Integer;
+ worksheet: TsWorksheet;
begin
+ worksheet := TsWorksheet(AWorksheet);
+
FCurSheetNum := Length(FSSheets);
SetLength(FSSheets, FCurSheetNum + 1);
@@ -7575,12 +8483,12 @@ begin
WriteColBreaks(FSSheets[FCurSheetNum], AWorksheet);
WriteHeaderFooter(FSSheets[FCurSheetNum], AWorksheet);
- { This item is required for all embedded images.
+ { This item is required for all embedded images and charts.
There must be a matching file in "drawingX.xml" file in "../drawings"
- which contains the image-related data of all images in this sheet.
+ which contains the image/chart-related data of all images/charts in this sheet.
The file in turn requires an entry "drawingX.xml.rels" in the drawings rels
folder }
- if (AWorksheet as TsWorksheet).GetImageCount > 0 then
+ if (worksheet.GetImageCount > 0) or (worksheet.GetChartCount > 0) then
AppendToStream(FSSheets[FCurSheetNum], Format(
'', [rId_Drawing]));
@@ -7682,6 +8590,14 @@ begin
DestroyTempStream(FSSharedStrings_complete);
for stream in FSSheets do DestroyTempStream(stream);
SetLength(FSSheets, 0);
+ for stream in FSCharts do DestroyTempStream(stream);
+ SetLength(FSCharts, 0);
+ for stream in FSChartRels do DestroyTempStream(stream);
+ SetLength(FSChartRels, 0);
+ for stream in FSChartStyles do DestroyTempStream(stream);
+ SetLength(FSChartStyles, 0);
+ for stream in FSChartColors do DestroyTempStream(stream);
+ SetLength(FSChartColors, 0);
for stream in FSComments do DestroyTempStream(stream);
SetLength(FSComments, 0);
for stream in FSSheetRels do DestroyTempStream(stream);
@@ -7815,6 +8731,42 @@ begin
FZip.Entries.AddFileEntry(FSSheetRels[i], OOXML_PATH_XL_WORKSHEETS_RELS + Format('sheet%d.xml.rels', [i+1]));
end;
+ { not used by Excel 2007
+ // Write chart styles
+ for i:=0 to High(FSChartStyles) do
+ begin
+ if (FSChartStyles[i] = nil) or (FSChartStyles[i].Size = 0) then Continue;
+ FSChartStyles[i].Position := 0;
+ FZip.Entries.AddFileEntry(FSChartStyles[i], OOXML_PATH_XL_CHARTS + Format('style%d.xml', [i+1]));
+ end;
+
+ // Write chart colors
+ for i:=0 to High(FSChartColors) do
+ begin
+ if (FSChartColors[i] = nil) or (FSChartColors[i].Size = 0) then Continue;
+ FSChartColors[i].Position := 0;
+ FZip.Entries.AddFileEntry(FSChartColors[i], OOXML_PATH_XL_CHARTS + Format('colors%d.xml', [i+1]));
+ end;
+ }
+
+ // Write charts
+ for i:=0 to High(FSCharts) do
+ begin
+ if (FSCharts[i] = nil) or (FSCharts[i].Size = 0) then Continue;
+ FSCharts[i].Position := 0;
+ FZip.Entries.AddFileEntry(FSCharts[i], OOXML_PATH_XL_CHARTS + Format('chart%d.xml', [i+1]));
+ end;
+
+ { not used by Excel 2007
+ // Write chart relationships
+ for i := 0 to High(FSChartRels) do
+ begin
+ if (FSChartRels[i] = nil) or (FSChartRels[i].Size = 0) then Continue;
+ FSChartRels[i].Position := 0;
+ FZip.Entries.AddFileEntry(FSChartRels[i], OOXML_PATH_XL_CHARTS_RELS + Format('chart%d.xml.rels', [i+1]));
+ end;
+ }
+
// Write drawings
for i:=0 to High(FSDrawings) do begin
if (FSDrawings[i] = nil) or (FSDrawings[i].Size = 0) then continue;