diff --git a/components/fpspreadsheet/examples/chartdemo/chartdemo.lpi b/components/fpspreadsheet/examples/chartdemo/chartdemo.lpi new file mode 100644 index 000000000..0dd0d610a --- /dev/null +++ b/components/fpspreadsheet/examples/chartdemo/chartdemo.lpi @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + <UseAppBundle Value="False"/> + <ResourceType Value="res"/> + </General> + <BuildModes Count="1"> + <Item1 Name="Default" Default="True"/> + </BuildModes> + <PublishOptions> + <Version Value="2"/> + <UseFileFilters Value="True"/> + </PublishOptions> + <RunParams> + <FormatVersion Value="2"/> + <Modes Count="0"/> + </RunParams> + <RequiredPackages Count="1"> + <Item1> + <PackageName Value="laz_fpspreadsheet"/> + </Item1> + </RequiredPackages> + <Units Count="1"> + <Unit0> + <Filename Value="chartdemo.lpr"/> + <IsPartOfProject Value="True"/> + </Unit0> + </Units> + </ProjectOptions> + <CompilerOptions> + <Version Value="11"/> + <PathDelim Value="\"/> + <Target> + <Filename Value="chartdemo"/> + </Target> + <SearchPaths> + <IncludeFiles Value="$(ProjOutDir)"/> + <UnitOutputDirectory Value="lib\$(TargetCPU)-$(TargetOS)"/> + </SearchPaths> + </CompilerOptions> + <Debugging> + <Exceptions Count="3"> + <Item1> + <Name Value="EAbort"/> + </Item1> + <Item2> + <Name Value="ECodetoolError"/> + </Item2> + <Item3> + <Name Value="EFOpenError"/> + </Item3> + </Exceptions> + </Debugging> +</CONFIG> diff --git a/components/fpspreadsheet/examples/chartdemo/chartdemo.lpr b/components/fpspreadsheet/examples/chartdemo/chartdemo.lpr new file mode 100644 index 000000000..fab54660d --- /dev/null +++ b/components/fpspreadsheet/examples/chartdemo/chartdemo.lpr @@ -0,0 +1,44 @@ +program chartdemo; + +uses + fpspreadsheet, fpsutils, fpstypes, fpschart; + +var + wb: TsWorkbook; + sh: TsWorksheet; + chart: TsChart; + ser: TsChartSeries; + idx: Integer; + +begin + wb := TsWorkbook.Create; + try + ws := wb.AddWorksheet('Test'); + // x values + ws.WriteNumber(0, 0, 1.0); + ws.WriteNumber(1, 0, 2.1); + ws.WriteNumber(2, 0, 2.9); + ws.WriteNumber(3, 0, 4.15); + ws.WriteNumber(4, 0, 5.05); + // y values + ws.WriteNumber(0, 1, 10.0); + ws.WriteNumber(1, 1, 12.0); + ws.WriteNumber(2, 1, 9.0); + ws.WriteNumber(3, 1, 7.5); + ws.WriteNumber(4, 1, 11.2); + + idx := ws.WriteChart(0, 0, 12, 9); + chart := ws.GetChart(idx); + ser := TsLineSeries.Create(chart); + ser.XRange := Range(0, 0, 4, 0); + ser.YRange := Range(0, 1, 4, 1); + ser.Title := 'Scatter series'; + ser.ShowSymbols := true; + ser.ShowLines := true; + + chart.AddSeries( + finally + wb.Free; + end; +end. + diff --git a/components/fpspreadsheet/laz_fpspreadsheet.lpk b/components/fpspreadsheet/laz_fpspreadsheet.lpk index 300e81067..16029f3a7 100644 --- a/components/fpspreadsheet/laz_fpspreadsheet.lpk +++ b/components/fpspreadsheet/laz_fpspreadsheet.lpk @@ -34,7 +34,7 @@ This package is all you need if you don't want graphical components (like grids and charts)."/> <License Value="LGPL with static linking exception. This is the same license as is used in the LCL (Lazarus Component Library)."/> <Version Major="1" Minor="11"/> - <Files Count="46"> + <Files Count="47"> <Item1> <Filename Value="source\fps.inc"/> <Type Value="Include"/> @@ -258,6 +258,10 @@ This package is all you need if you don't want graphical components (like grids <Filename Value="source\common\fpscrypto.pas"/> <UnitName Value="fpsCrypto"/> </Item46> + <Item47> + <Filename Value="source\common\fpschart.pas"/> + <UnitName Value="fpschart"/> + </Item47> </Files> <i18n> <EnableI18N Value="True"/> @@ -278,7 +282,6 @@ This package is all you need if you don't want graphical components (like grids </UsageOptions> <PublishOptions> <Version Value="2"/> - <IgnoreBinaries Value="False"/> </PublishOptions> <CustomOptions Items="ExternHelp" Version="2"> <_ExternHelp Items="Count"/> diff --git a/components/fpspreadsheet/source/common/fpschart.pas b/components/fpspreadsheet/source/common/fpschart.pas new file mode 100644 index 000000000..8ae46d7ba --- /dev/null +++ b/components/fpspreadsheet/source/common/fpschart.pas @@ -0,0 +1,426 @@ +unit fpschart; + +{$mode objfpc}{$H+} + +interface + +uses + Classes, SysUtils, Contnrs, fpsTypes, fpsUtils; + +type + TsChart = class; + + TsChartFill = record + Style: TsFillStyle; + FgColor: TsColor; + BgColor: TsColor; + end; + + TsChartPenStyle = (cpsSolid, cpsDashed, cpsDotted, cpsDashDot, cpsDashDotDot, cpsClear); + + TsChartLine = record + Style: TsChartPenStyle; + Width: Double; // mm + Color: TsColor; + end; + + TsChartObj = class + private + FOwner: TsChartObj; + FVisible: Boolean; + public + constructor Create(AOwner: TsChartObj); + property Visible: Boolean read FVisible write FVisible; + end; + + TsChartFillObj = class(TsChartObj) + private + FBackground: TsChartFill; + FBorder: TsChartLine; + public + constructor Create(AOwner: TsChartObj); + property Background: TsChartFill read FBackground write FBackground; + property Border: TsChartLine read FBorder write FBorder; + end; + + TsChartText = class(TsChartObj) + private + FCaption: String; + FShowCaption: Boolean; + FFont: TsFont; + public + constructor Create(AOwner: TsChartObj); + property Caption: String read FCaption write FCaption; + property Font: TsFont read FFont write FFont; + property ShowCaption: Boolean read FShowCaption write FShowCaption; + end; + + TsChartAxis = class(TsChartText) + private + FAutomaticMax: Boolean; + FAutomaticMin: Boolean; + FAutomaticMajorInterval: Boolean; + FAutomaticMinorSteps: Boolean; + FAxisLine: TsChartLine; + FGridLines: TsChartLine; + FInverted: Boolean; + FLabelFont: TsFont; + FLogarithmic: Boolean; + FMajorInterval: Double; + FMajorTickLines: TsChartLine; + FMax: Double; + FMin: Double; + FMinorSteps: Double; + FMinorTickLines: TsChartLine; + FShowGrid: Boolean; + FShowLabels: Boolean; + public + constructor Create(AOwner: TsChartObj); + 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 GridLines: TsChartLine read FGridLines write FGridLines; + property Inverted: Boolean read FInverted write FInverted; + property LabelFont: TsFont read FLabelFont write FLabelFont; + property Logarithmic: Boolean read FLogarithmic write FLogarithmic; + 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 MinorSteps: Double read FMinorSteps write FMinorSteps; + property MinorTickLines: TsChartLine read FMinorTickLines write FMinorTickLines; + property ShowGrid: Boolean read FShowGrid write FShowGrid; + property ShowLabels: Boolean read FShowLabels write FShowLabels; + end; + + TsChartLegend = class(TsChartFillObj) + end; + + TsChartAxisLink = (alPrimary, alSecondary); + + TsChartSeries = class(TsChartObj) + private + FXRange: TsCellRange; // cell range containing the x data + FYRange: TsCellRange; + FLabelsRange: TsCellRange; + FXIndex: array of Integer; // index of data point's x value within XRange + FYIndex: array of Integer; + FLabelsIndex: array of Integer; + FYAxis: TsChartAxisLink; + FTitle: String; + function GetCount: Integer; + public + constructor Create(AChart: TsChart); + property Count: Integer read GetCount; + property LabelsRange: TsCellRange read FLabelsRange; + property Title: String read FTitle; + property XRange: TsCellRange read FXRange write FXRange; + property YRange: TsCellRange read FYRange write 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(TsChartFillObj) + private + FSheetIndex: Integer; + FRow, FCol: Cardinal; + FOffsetX, FOffsetY: Double; + FWidth, FHeight: Double; // Width, Height of the chart, in mm. + + FPlotArea: TsChartFillObj; + FXAxis: TsChartAxis; + FYAxis: TsChartAxis; + FY2Axis: TsChartAxis; + + FTitle: TsChartText; + FSubTitle: TsChartText; + FLegend: TsChartLegend; + FSeriesList: TsChartSeriesList; + public + constructor Create; + destructor Destroy; override; + function AddSeries(ASeries: TsChartSeries): Integer; + + { 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 FHeight; + { Height of the chart, in mm } + property Height: double read FHeight write FHeight; + + { Attributes of the plot area (rectangle enclosed by axes) } + property PlotArea: TsChartFillObj read FPlotArea write FPlotArea; + + { 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 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; + + { 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 + + +{ TsChartObj } + +constructor TsChartObj.Create(AOwner: TsChartObj); +begin + inherited Create; + FOwner := AOwner; + FVisible := true; +end; + + +{ TsChartFillObj } + +constructor TsChartFillObj.Create(AOwner: TsChartObj); +begin + inherited Create(AOwner); + FBackground.Style := fsSolidFill; + FBackground.BgColor := scWhite; + FBackground.FgColor := scWhite; + FBorder.Style := cpsSolid; + FBorder.Width := PtsToMM(DEFAULT_LINE_WIDTH); + FBorder.Color := scBlack; +end; + + +{ TsChartText } + +constructor TsChartText.Create(AOwner: TsChartObj); +begin + inherited Create(AOwner); + FShowCaption := true; + FFont.FontName := ''; // replace by workbook's default font + FFont.Size := 0; // replace by workbook's default font size + FFont.Style := []; + FFont.Color := scBlack; +end; + + +{ TsChartAxis } + +constructor TsChartAxis.Create(AOwner: TsChartObj); +begin + inherited Create(AOwner); + + FAutomaticMajorInterval := true; + FAutomaticMinorSteps := true; + + 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 := cpsSolid; + FAxisLine.Width := PtsToMM(DEFAULT_LINE_WIDTH); + + FMajorTickLines.Color := scBlack; + FMajorTickLines.Style := cpsSolid; + FMajorTickLines.Width := PtsToMM(DEFAULT_LINE_WIDTH); + + FMinorTickLines.Color := scBlack; + FMinorTickLines.Style := cpsSolid; + FMinorTickLines.Width := PtsToMM(DEFAULT_LINE_WIDTH); + + FGridLines.Color := scSilver; + FGridLines.Style := cpsDotted; + FGridLines.Width := PtsToMM(DEFAULT_LINE_WIDTH); +end; + + +{ TsChartSeries } + +constructor TsChartSeries.Create(AChart: TsChart); +begin + inherited Create(AChart); + AChart.AddSeries(self); + FTitle := 'Series ' + IntToStr(AChart.Series.Count); +end; + +function TsChartSeries.GetCount: Integer; +begin + Result := Length(FYIndex); +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); + + FLineStyle.Color := scBlack; + FLineStyle.Style := cpsSolid; + FLineStyle.Width := PtsToMM(DEFAULT_LINE_WIDTH); + + FSymbolBorder.Color := scBlack; + FSymbolBorder.Style := cpsSolid; + 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); + + FSheetIndex := 0; + FRow := 0; + FCol := 0; + FOffsetX := 0.0; + FOffsetY := 0.0; + FWidth := 12; + FHeight := 9; + + 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]; + + FYAxis := TsChartAxis.Create(self); + FYAxis.Caption := 'y axis'; + FYAxis.LabelFont.Size := 9; + FYAxis.Font.Size := 10; + FYAxis.Font.Style := [fssBold]; + + FY2Axis := TsChartAxis.Create(self); + FY2Axis.Caption := 'Secondary y axis'; + FY2Axis.LabelFont.Size := 9; + FY2Axis.Font.Size := 10; + FY2Axis.Font.Style := [fssBold]; + FYAxis.Visible := false; + + FSeriesList := TsChartSeriesList.Create; +end; + +destructor TsChart.Destroy; +begin + FSeriesList.Free; + FXAxis.Free; + FYAxis.Free; + FY2Axis.Free; + FLegend.Free; + FTitle.Free; + FSubtitle.Free; + inherited; +end; + +function TsChart.AddSeries(ASeries: TsChartSeries): Integer; +begin + Result := FSeriesList.Add(ASeries); +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/fpspreadsheet.pas b/components/fpspreadsheet/source/common/fpspreadsheet.pas index 316bf55b1..dc0903799 100644 --- a/components/fpspreadsheet/source/common/fpspreadsheet.pas +++ b/components/fpspreadsheet/source/common/fpspreadsheet.pas @@ -23,7 +23,8 @@ uses clocale, {$endif}{$endif}{$endif} Classes, SysUtils, fpimage, avglvltree, lconvencoding, - fpsTypes, fpsExprParser, fpsClasses, fpsNumFormat, fpsPageLayout, fpsImages; + fpsTypes, fpsExprParser, fpsClasses, fpsNumFormat, fpsPageLayout, + fpsImages, fpsChart; type { Forward declarations } @@ -69,6 +70,7 @@ type FHyperlinks: TsHyperlinks; FFormulas: TsFormulas; FImages: TFPList; + FCharts: TsChartList; FRows, FCols: TIndexedAVLTree; // This lists contain only rows or cols with styles different from default FActiveCellRow: Cardinal; FActiveCellCol: Cardinal; @@ -558,6 +560,10 @@ type procedure AddHyperlinkToImage(AImageIndex: Integer; ATarget: String; AToolTip: String = ''); + { Charts } + function WriteChart(ARow, ACol: Cardinal; AWidth, AHeight: Double; + AOffsetX: Double = 0.0; AOffsetY: Double = 0.0): Integer; + { Protection } procedure Protect(AEnable: Boolean); @@ -1137,6 +1143,7 @@ begin FHyperlinks := TsHyperlinks.Create; FFormulas := TsFormulas.Create; FImages := TFPList.Create; + FCharts := TsChartList.Create; FPageLayout := TsPageLayout.Create(self); @@ -1179,6 +1186,7 @@ begin FHyperlinks.Free; FFormulas.Free; FImages.Free; + FCharts.Free; inherited Destroy; end; @@ -3732,6 +3740,26 @@ begin DeleteFormula(ACell); end; +{@@ ---------------------------------------------------------------------------- + Creates a chart object with its top/left corner in the specified row/colum and + having the specified width. Inserts the chart in the FCharts list of the + worksheet and returns its index. +-------------------------------------------------------------------------------} +function TsWorksheet.WriteChart(ARow, ACol: Cardinal; AWidth, AHeight: Double; + AOffsetX: Double = 0.0; AOffsetY: Double = 0.0): Integer; +var + chart: TsChart; +begin + chart := TsChart.Create; + chart.Row := ARow; + chart.Col := ACol; + chart.OffsetX := AOffsetX; + chart.OffsetY := AOffsetY; + chart.Width := AWidth; + chart.Height := AHeight; + Result := FCharts.Add(chart); +end; + {@@ ---------------------------------------------------------------------------- Returns the parameters of the image stored in the internal image list at the specified index.