diff --git a/components/fpspreadsheet/examples/other/chart/write_chart_demo.lpr b/components/fpspreadsheet/examples/other/chart/write_chart_demo.lpr index 482e8e812..4b2ccc7e7 100644 --- a/components/fpspreadsheet/examples/other/chart/write_chart_demo.lpr +++ b/components/fpspreadsheet/examples/other/chart/write_chart_demo.lpr @@ -9,9 +9,9 @@ const // SERIES_CLASS: TsChartSeriesClass = TsBarSeries; // SERIES_CLASS: TsChartSeriesClass = TsBubbleSeries; // SERIES_CLASS: TsChartSeriesClass = TsLineSeries; -// SERIES_CLASS: TsChartSeriesClass = TsScatterSeries; + SERIES_CLASS: TsChartSeriesClass = TsScatterSeries; // SERIES_CLASS: TsChartSeriesClass = TsRadarSeries; - SERIES_CLASS: TsChartSeriesClass = TsPieSeries; +// SERIES_CLASS: TsChartSeriesClass = TsPieSeries; r1 = 1; r2 = 8; FILL_COLORS: array[0..r2-r1] of TsColor = (scRed, scGreen, scBlue, scYellow, scMagenta, scSilver, scBlack, scOlive); diff --git a/components/fpspreadsheet/source/common/fpschart.pas b/components/fpspreadsheet/source/common/fpschart.pas index 40ae892f1..0d7c5d685 100644 --- a/components/fpspreadsheet/source/common/fpschart.pas +++ b/components/fpspreadsheet/source/common/fpschart.pas @@ -311,9 +311,50 @@ type constructor Create(AChart: TsChart); override; end; + TsRegressionType = (rtNone, rtLinear, rtLogarithmic, rtExponential, rtPower, rtPolynomial); + + TsRegressionEquation = class + Fill: TsChartFill; + Font: TsFont; + Border: TsChartLine; + NumberFormat: String; + Left, Top: Double; // mm, relative to outer chart boundaries! + XName: String; + YName: String; + constructor Create; + destructor Destroy; override; + function DefaultBorder: Boolean; + function DefaultFill: Boolean; + function DefaultFont: Boolean; + function DefaultNumberFormat: Boolean; + function DefaultPosition: Boolean; + function DefaultXName: Boolean; + function DefaultYName: Boolean; + end; + + TsChartRegression = class + Title: String; + RegressionType: TsRegressionType; + ExtrapolateForwardBy: Double; + ExtrapolateBackwardBy: Double; + ForceYIntercept: Boolean; + YInterceptValue: Double; + PolynomialDegree: Integer; + DisplayEquation: Boolean; + DisplayRSquare: Boolean; + Equation: TsRegressionEquation; + Line: TsChartLine; + constructor Create; + destructor Destroy; override; + end; + TsScatterSeries = class(TsLineSeries) + private + FRegression: TsChartRegression; public constructor Create(AChart: TsChart); override; + destructor Destroy; override; + property Regression: TsChartRegression read FRegression write FRegression; end; TsChartSeriesList = class(TFPList) @@ -846,6 +887,7 @@ begin Result := ctRadar; end; + { TsRingSeries } constructor TsRingSeries.Create(AChart: TsChart); begin @@ -855,12 +897,99 @@ begin end; +{ TsRegressionEquation } +constructor TsRegressionEquation.Create; +begin + inherited Create; + Font := TsFont.Create; + Font.Size := 9; + Border := TsChartLine.Create; + Border.Style := clsNoLine; + Border.Width := PtsToMM(DEFAULT_CHART_LINEWIDTH); + Border.Color := scBlack; + Fill := TsChartFill.Create; + Fill.FgColor := scWhite; + XName := 'x'; + YName := 'f(x)'; +end; + +destructor TsRegressionEquation.Destroy; +begin + Fill.Free; + Border.Free; + Font.Free; + inherited; +end; + +function TsRegressionEquation.DefaultBorder: Boolean; +begin + Result := Border.Style = clsNoLine; +end; + +function TsRegressionEquation.DefaultFill: Boolean; +begin + Result := Fill.Style = fsNoFill; +end; + +function TsRegressionEquation.DefaultFont: Boolean; +begin + Result := (Font.FontName = '') and (Font.Size = 9) and (Font.Style = []) and + (Font.Color = scBlack); +end; + +function TsRegressionEquation.DefaultNumberFormat: Boolean; +begin + Result := NumberFormat = ''; +end; + +function TsRegressionEquation.DefaultPosition: Boolean; +begin + Result := (Left = 0) and (Top = 0); +end; + +function TsRegressionEquation.DefaultXName: Boolean; +begin + Result := XName = 'x'; +end; + +function TsRegressionEquation.DefaultYName: Boolean; +begin + Result := YName = 'f(x)'; +end; + + +{ TsChartRegression } +constructor TsChartRegression.Create; +begin + inherited Create; + Line := TsChartLine.Create; + Line.Style := clsSolid; + Line.Width := PtsToMM(DEFAULT_CHART_LINEWIDTH); + Line.Color := scBlack; + + Equation := TsRegressionEquation.Create; +end; + +destructor TsChartRegression.Destroy; +begin + Line.Free; + inherited; +end; + + { TsScatterSeries } constructor TsScatterSeries.Create(AChart: TsChart); begin inherited Create(AChart); FChartType := ctScatter; + FRegression := TsChartRegression.Create; +end; + +destructor TsScatterSeries.Destroy; +begin + FRegression.Free; + inherited; end; diff --git a/components/fpspreadsheet/source/common/fpsopendocumentchart.pas b/components/fpspreadsheet/source/common/fpsopendocumentchart.pas index a20893843..8b3e94a71 100644 --- a/components/fpspreadsheet/source/common/fpsopendocumentchart.pas +++ b/components/fpspreadsheet/source/common/fpsopendocumentchart.pas @@ -23,11 +23,19 @@ type function GetChartBackgroundStyleAsXML(AChart: TsChart; AFill: TsChartFill; ABorder: TsChartLine; AIndent: Integer; AStyleID: Integer): String; function GetChartCaptionStyleAsXML(AChart: TsChart; ACaptionKind, AIndent, AStyleID: Integer): String; - function GetChartFillStyleGraphicPropsAsXML(AChart: TsChart; AFill: TsChartFill): String; - function GetChartLegendStyleAsXML(AChart: TsChart; AIndent, AStyleID: Integer): String; - function GetChartLineStyleAsXML(AChart: TsChart; ALine: TsChartLine; AIndent, AStyleID: Integer): String; - function GetChartLineStyleGraphicPropsAsXML(AChart: TsChart; ALine: TsChartLine): String; - function GetChartPlotAreaStyleAsXML(AChart: TsChart; AIndent, AStyleID: Integer): String; + function GetChartFillStyleGraphicPropsAsXML(AChart: TsChart; + AFill: TsChartFill): String; + function GetChartLegendStyleAsXML(AChart: TsChart; + AIndent, AStyleID: Integer): String; + function GetChartLineStyleAsXML(AChart: TsChart; + ALine: TsChartLine; AIndent, AStyleID: Integer): String; + function GetChartLineStyleGraphicPropsAsXML(AChart: TsChart; + ALine: TsChartLine): String; + function GetChartPlotAreaStyleAsXML(AChart: TsChart; + AIndent, AStyleID: Integer): String; + function GetChartRegressionEquationStyleAsXML(AChart: TsChart; + AEquation: TsRegressionEquation; AIndent, AStyleID: Integer): String; + function GetChartRegressionStyleAsXML(AChart: TsChart; ASeriesIndex, AIndent, AStyleID: Integer): String; function GetChartSeriesStyleAsXML(AChart: TsChart; ASeriesIndex, AIndent, AStyleID: integer): String; // function GetChartTitleStyleAsXML(AChart: TsChart; AStyleIndex, AIndent: Integer): String; procedure PrepareChartTable(AChart: TsChart; AWorksheet: TsBasicWorksheet); @@ -525,6 +533,100 @@ begin indent + ' ' + LE; end; +function TsSpreadOpenDocChartWriter.GetChartRegressionEquationStyleAsXML( + AChart: TsChart; AEquation: TsRegressionEquation; AIndent, AStyleID: Integer): String; +var + indent: String; + numStyle: String = 'N0'; + chartprops: String = ''; + lineprops: String = ''; + fillprops: String = ''; + textprops: String = ''; +begin + Result := ''; + + indent := DupeString(' ', AIndent); + + // TO DO: Create chart number style list and find the current style there! + if not AEquation.DefaultNumberFormat then + numStyle := 'N0'; + + if not AEquation.DefaultXName then + chartprops := chartprops + Format('loext:regression-x-name="%s" ', [AEquation.XName]); + if not AEquation.DefaultYName then + chartprops := chartprops + Format('loext:regression-y-name="%s" ', [AEquation.YName]) ; + + if not AEquation.DefaultBorder then + lineProps := GetChartLineStyleGraphicPropsAsXML(AChart, AEquation.Border); + + if not AEquation.DefaultFill then + fillProps := GetChartFillStyleGraphicPropsAsXML(AChart, AEquation.Fill); + + if not AEquation.DefaultFont then + textprops := TsSpreadOpenDocWriter(Writer).WriteFontStyleXMLAsString(AEquation.Font); + + Result := Format( + indent + '' + LE + + indent + ' ' + LE + + indent + ' ' + LE + + indent + ' ' + LE + + indent + '' + LE, + [ AStyleID, numStyle, chartprops, fillprops + lineprops, textprops ] + ); +end; + +function TsSpreadOpenDocChartWriter.GetChartRegressionStyleAsXML(AChart: TsChart; + ASeriesIndex, AIndent, AStyleID: Integer): String; +const + REGRESSION_TYPE: array [TsRegressionType] of string = ( + '', 'linear', 'logarithmic', 'exponential', 'power', 'polynomial'); +var + series: TsScatterSeries; + indent: String; + chartProps: String = ''; + graphProps: String = ''; + textProps: String = ''; + lineProps: String = ''; + fillProps: String = ''; + labelSeparator: String = ''; +begin + Result := ''; + series := AChart.Series[ASeriesIndex] as TsScatterSeries; + if series.Regression.RegressionType = rtNone then + exit; + + indent := DupeString(' ', AIndent); + + chartprops := Format( + 'chart:regression-name="%s" ' + + 'chart:regression-type="%s" ' + + 'chart:regression-extrapolate-forward="%g" ' + + 'chart:regression-extrapolate-backward="%g" ' + + 'chart:regression-force-intercept="%s" ' + + 'chart:regression-intercept-value="%g" ' + + 'chart:regression-max-degree="%d" ', + [ series.Regression.Title, + REGRESSION_TYPE[series.Regression.RegressionType] , + series.Regression.ExtrapolateForwardBy, + series.Regression.ExtrapolateBackwardBy, + FALSE_TRUE[series.Regression.ForceYIntercept], + series.Regression.YInterceptValue, + series.Regression.PolynomialDegree + ], FPointSeparatorSettings + ); + + graphprops := GetChartLineStyleGraphicPropsAsXML(AChart, series.Regression.Line); + + Result := Format( + indent + ' ' + LE + + indent + ' ' + LE + + indent + ' ' + LE + + indent + '' + LE, + [ AStyleID, chartprops, graphprops ] + ); +end; + + { clsNoLine) then - graphProps := graphProps + lineProps; + graphProps := graphProps + lineProps + else + graphProps := graphProps + 'draw:stroke="none" '; end else graphProps := fillProps + lineProps; @@ -1294,6 +1398,10 @@ var domainRangeY: String = ''; fillColorRange: String = ''; chartClass: String = ''; + regressionEquation: String = ''; + needRegressionStyle: Boolean = false; + needRegressionEquationStyle: Boolean = false; + regression: TsChartRegression = nil; titleAddr: String; count: Integer; begin @@ -1366,10 +1474,10 @@ begin // Store the series properties AppendToStream(AChartStream, Format( indent + '' + LE, - [ AStyleID, valuesRange, titleAddr, chartClass ] + 'chart:label-cell-address="%s">' + LE, // series title + [ AStyleID, chartClass, valuesRange, titleAddr, chartClass ] )); if domainRangeY <> '' then AppendToStream(AChartStream, Format( @@ -1390,11 +1498,57 @@ begin { --- not working... if borderColorRange <> '' then AppendToStream(AChartStream, Format( - indent + '' + LE, + indent + '' + LE, [ borderColorRange ] )); } + // Regression + if (series is TsScatterSeries) then + begin + regression := TsScatterSeries(series).Regression; + if regression.RegressionType <> rtNone then + begin + if regression.DisplayEquation or regression.DisplayRSquare then + begin + if (not regression.Equation.DefaultXName) or (not regression.Equation.DefaultYName) or + (not regression.Equation.DefaultBorder) or (not regression.Equation.DefaultFill) or + (not regression.Equation.DefaultFont) or (not regression.Equation.DefaultNumberFormat) or + (not regression.Equation.DefaultPosition) then + begin + regressionEquation := regressionEquation + Format('chart:style-name="ch%d" ', [AStyleID + 2]); + needRegressionEquationStyle := true; + end; + end; + if regression.DisplayEquation then + regressionEquation := regressionEquation + 'chart:display-equation="true" '; + if regression.DisplayRSquare then + regressionEquation := regressionEquation + 'chart:display-r-square="true" '; + + if regressionEquation <> '' then + begin + if not regression.Equation.DefaultPosition then + regressionEquation := regressionEquation + Format( + 'svg:x="%.2fmm" svg:y="%.2fmm" ', + [ regression.Equation.Left, regression.Equation.Top ], + FPointSeparatorSettings + ); + + AppendToStream(AChartStream, Format( + indent + ' ' + LE + + indent + ' ' + LE + + indent + ' ' + LE, + [ AStyleID + 1, regressionEquation ] + )) + end else + AppendToStream(AChartStream, Format( + indent + ' ', + [ AStyleID + 1 ] + )); + needRegressionStyle := true; + end; + end; + AppendToStream(AChartStream, Format( indent + ' ' + LE, [ count ] @@ -1408,6 +1562,24 @@ begin GetChartSeriesStyleAsXML(AChart, ASeriesIndex, AStyleIndent, AStyleID) ); + // Regression style + if needRegressionStyle then + begin + inc(AStyleID); + AppendToStream(AStyleStream, + GetChartRegressionStyleAsXML(AChart, ASeriesIndex, AStyleIndent, AStyleID) + ); + + // Style of regression equation + if needRegressionEquationStyle then + begin + inc(AStyleID); + AppendToStream(AStyleStream, + GetChartRegressionEquationStyleAsXML(AChart, regression.Equation, AStyleIndent, AStyleID) + ); + end; + end; + // Next style inc(AStyleID); end;