diff --git a/components/fpspreadsheet/examples/other/chart/linechart_write_demo.lpi b/components/fpspreadsheet/examples/other/chart/linechart_write_demo.lpi new file mode 100644 index 000000000..8a52ac6e3 --- /dev/null +++ b/components/fpspreadsheet/examples/other/chart/linechart_write_demo.lpi @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + <UseAppBundle Value="False"/> + <ResourceType Value="res"/> + </General> + <BuildModes> + <Item Name="Default" Default="True"/> + </BuildModes> + <PublishOptions> + <Version Value="2"/> + <UseFileFilters Value="True"/> + </PublishOptions> + <RunParams> + <FormatVersion Value="2"/> + </RunParams> + <RequiredPackages> + <Item> + <PackageName Value="laz_fpspreadsheet"/> + </Item> + </RequiredPackages> + <Units> + <Unit> + <Filename Value="linechart_write_demo.lpr"/> + <IsPartOfProject Value="True"/> + </Unit> + </Units> + </ProjectOptions> + <CompilerOptions> + <Version Value="11"/> + <PathDelim Value="\"/> + <Target> + <Filename Value="linechart_write_demo"/> + </Target> + <SearchPaths> + <IncludeFiles Value="$(ProjOutDir)"/> + <UnitOutputDirectory Value="lib\$(TargetCPU)-$(TargetOS)"/> + </SearchPaths> + <Linking> + <Debugging> + <DebugInfoType Value="dsDwarf3"/> + </Debugging> + </Linking> + </CompilerOptions> + <Debugging> + <Exceptions> + <Item> + <Name Value="EAbort"/> + </Item> + <Item> + <Name Value="ECodetoolError"/> + </Item> + <Item> + <Name Value="EFOpenError"/> + </Item> + </Exceptions> + </Debugging> +</CONFIG> diff --git a/components/fpspreadsheet/examples/other/chart/linechart_write_demo.lpr b/components/fpspreadsheet/examples/other/chart/linechart_write_demo.lpr new file mode 100644 index 000000000..33ac20548 --- /dev/null +++ b/components/fpspreadsheet/examples/other/chart/linechart_write_demo.lpr @@ -0,0 +1,128 @@ +program linechart_write_demo; + +{.$DEFINE DARK_MODE} + +uses + SysUtils, + fpspreadsheet, fpstypes, fpsUtils, fpschart, xlsxooxml, fpsopendocument; + +procedure WriteHelp; +begin + WriteLn('SYNTAX: linechart_write_demo [rotated] [normal|stacked|percent-stacked] '); + WriteLn(' (no argument) ..... normal orientation (x horizontal)'); + WriteLn(' rotated ........... axes rotated (x vertical)'); + WriteLn(' normal ............ back-to-bottom areas (default)'); + WriteLn(' stacked ........... stacked areas'); + WriteLn(' percent-stacked ... stacked by percentage'); + Halt; +end; + +const + FILE_NAME = 'line'; +var + book: TsWorkbook; + sheet: TsWorksheet; + ch: TsChart; + ser: TsLineSeries; + dir, fn: String; + stackMode: TsChartStackMode = csmDefault; + rotated: Boolean = false; + i: Integer; +begin + fn := FILE_NAME; + + for i := 1 to ParamCount do + case lowercase(ParamStr(i)) of + 'rotated': + rotated := true; + 'normal' ,'default': + stackMode := csmDefault; + 'stacked': + stackMode := csmStacked; + 'percent-stacked', 'stacked-percent', 'percentstacked', 'stackedpercent', 'percent', 'percentage': + stackMode := csmStackedPercentage; + else + WriteHelp; + end; + + if rotated then + fn := fn + '-rotated'; + case stackMode of + csmDefault: ; + csmStacked: fn := fn + '-stacked'; + csmStackedPercentage: fn := fn + '-stackedpercent'; + end; + + dir := ExtractFilePath(ParamStr(0)) + 'files/'; + ForceDirectories(dir); + + book := TsWorkbook.Create; + try + // worksheet + sheet := book.AddWorksheet('line_series'); + + // Enter data + sheet.WriteText( 0, 0, 'School Grades'); + sheet.WriteFont( 0, 0, '', 12, [fssBold], scBlack); + sheet.WriteText( 2, 0, ''); sheet.WriteText ( 2, 1, 'Student 1'); sheet.WriteText ( 2, 2, 'Student 2'); + sheet.WriteText( 3, 0, 'Biology'); sheet.WriteNumber( 3, 1, 12); sheet.WriteNumber( 3, 2, 15); + sheet.WriteText( 4, 0, 'History'); sheet.WriteNumber( 4, 1, 11); sheet.WriteNumber( 4, 2, 13); + sheet.WriteText( 5, 0, 'French'); sheet.WriteNumber( 5, 1, 16); sheet.WriteNumber( 5, 2, 11); + sheet.WriteText( 6, 0, 'English'); sheet.WriteNumber( 6, 1, 18); sheet.WriteNumber( 6, 2, 11); + sheet.WriteText( 7, 0, 'Sports'); sheet.WriteNumber( 7, 1, 16); sheet.WriteNumber( 7, 2, 7); + sheet.WriteText( 8, 0, 'Maths'); sheet.WriteNumber( 8, 1, 10); sheet.WriteNumber( 8, 2, 17); + sheet.WriteText( 9, 0, 'Physics'); sheet.WriteNumber( 9, 1, 12); sheet.WriteNumber( 9, 2, 19); + sheet.WriteText(10, 0, 'Computer'); sheet.WriteNumber(10, 1, 16); sheet.WriteNumber(10, 2, 18); + + // Create chart: left/top in cell D4, 160 mm x 100 mm + ch := book.AddChart(sheet, 2, 3, 160, 100); + + // Chart properties + ch.Border.Style := clsNoLine; + ch.Title.Caption := 'School Grades'; + ch.Title.Font.Style := [fssBold]; + ch.Title.Font.Color := scBlue; + ch.Legend.Border.Style := clsNoLine; + ch.XAxis.Title.Caption := ''; + ch.YAxis.Title.Caption := 'Grade points'; + ch.YAxis.AxisLine.Color := scSilver; + ch.YAxis.MajorTicks := []; + ch.RotatedAxes := rotated; + ch.StackMode := stackMode; + + // Add 1st line series ("Student 1") + ser := TsLineSeries.Create(ch); + ser.SetTitleAddr(2, 1); // series 1 title in cell B3 + ser.SetLabelRange(3, 0, 10, 0); // series 1 x labels in A4:A11 + ser.SetYRange(3, 1, 10, 1); // series 1 y values in B4:B11 + ser.Line.Color := scRed; + ser.ShowSymbols := true; + ser.SymbolFill.Color := scRed; + ser.SymbolFill.Style := cfsSolid; + ser.SymbolBorder.Color := scBlack; + ser.Smooth := true; +// ser.GroupIndex := -1; + + // Add 2nd line series ("Student 2") + ser := TsLineSeries.Create(ch); + ser.SetTitleAddr(2, 2); // series 2 title in cell C3 + ser.SetLabelRange(3, 0, 10, 0); // series 2 x labels in A4:A11 + ser.SetYRange(3, 2, 10, 2); // series 2 y values in C4:C11 + ser.Line.Color := scBlue; + ser.SymbolFill.Color := scBlue; + ser.SymbolFill.Style := cfsSolid; + ser.SymbolBorder.Color := scBlack; + //ser.Smooth := true; + ser.ShowSymbols := true; +// ser.GroupIndex := -1; + + book.WriteToFile(dir + fn + '.xlsx', true); + WriteLn('... ', fn + '.xlsx'); + + book.WriteToFile(dir + fn + '.ods', true); + WriteLn('... ', fn + '.ods'); + finally + book.Free; + end; +end. + diff --git a/components/fpspreadsheet/examples/other/chart/run_write_demos.bat b/components/fpspreadsheet/examples/other/chart/run_write_demos.bat index 3973ef869..662de180b 100644 --- a/components/fpspreadsheet/examples/other/chart/run_write_demos.bat +++ b/components/fpspreadsheet/examples/other/chart/run_write_demos.bat @@ -20,13 +20,21 @@ barchart_write_demo horiz stacked barchart_write_demo vert percentage barchart_write_demo horiz percentage echo. -echoe BAR SERIES WITH TWO AXES +echo BAR SERIES WITH TWO AXES barchart_2axes_write_demo barchart_2axes_write_demo rotated echo. echo BUBBLE SERIES bubblechart_write_demo echo. +echo LINE SERIES +linechart_write_demo +linechart_write_demo stacked +linechart_write_demo percentage +linechart_write_demo rotated +linechart_write_demo stacked rotated +linechart_write_demo percentage rotated +echo. echo PIE SERIES piechart_write_demo piechart_write_demo ring diff --git a/components/fpspreadsheet/examples/other/chart/write_demos.lpg b/components/fpspreadsheet/examples/other/chart/write_demos.lpg index 1c328c638..70a6998f4 100644 --- a/components/fpspreadsheet/examples/other/chart/write_demos.lpg +++ b/components/fpspreadsheet/examples/other/chart/write_demos.lpg @@ -22,6 +22,11 @@ <Mode Name="Default"/> </BuildModes> </Target> + <Target FileName="linechart_write_demo.lpi"> + <BuildModes> + <Mode Name="Default"/> + </BuildModes> + </Target> <Target FileName="piechart_write_demo.lpi"> <BuildModes> <Mode Name="Default"/> diff --git a/components/fpspreadsheet/source/common/fpschart.pas b/components/fpspreadsheet/source/common/fpschart.pas index a5a99c481..9c3e0d35a 100644 --- a/components/fpspreadsheet/source/common/fpschart.pas +++ b/components/fpspreadsheet/source/common/fpschart.pas @@ -177,6 +177,7 @@ type TsChartFillStyle = (cfsNoFill, cfsSolid, cfsGradient, cfsHatched, cfsSolidHatched, cfsImage); TsChartFill = class + public Style: TsChartFillStyle; Color: TsColor; Gradient: Integer; // Index into chart's Gradients list @@ -621,8 +622,15 @@ type cssDash, cssDot ); + TsChartInterpolation = ( + ciLinear, + ciCubicSpline, ciBSpline, + ciStepStart, ciStepEnd, ciStepCenterX, ciStepCenterY + ); + TsCustomLineSeries = class(TsChartSeries) private + FInterpolation: TsChartInterpolation; FSymbol: TsChartSeriesSymbol; FSymbolHeight: Double; // in mm FSymbolWidth: Double; // in mm @@ -630,7 +638,11 @@ type FShowSymbols: Boolean; FSymbolBorder: TsChartLine; FSymbolFill: TsChartFill; + function GetSmooth: Boolean; + procedure SetSmooth(AValue: Boolean); protected + property Interpolation: TsChartInterpolation read FInterpolation write FInterpolation; + property Smooth: Boolean read GetSmooth write SetSmooth; property Symbol: TsChartSeriesSymbol read FSymbol write FSymbol; property SymbolBorder: TsChartLine read FSymbolBorder write FSymbolBorder; property SymbolFill: TsChartFill read FSymbolFill write FSymbolFill; @@ -645,6 +657,9 @@ type TsLineSeries = class(TsCustomLineSeries) public + constructor Create(AChart: TsChart); override; + property Interpolation; + property Smooth; property Symbol; property SymbolBorder; property SymbolFill; @@ -691,6 +706,8 @@ type TsScatterSeries = class(TsCustomScatterSeries) public + property Interpolation; + property Smooth; property Symbol; property SymbolBorder; property SymbolFill; @@ -761,11 +778,6 @@ type end; TsChartStackMode = (csmDefault, csmStacked, csmStackedPercentage); - TsChartInterpolation = ( - ciLinear, - ciCubicSpline, ciBSpline, - ciStepStart, ciStepEnd, ciStepCenterX, ciStepCenterY - ); TsChart = class(TsChartFillElement) private @@ -2550,6 +2562,30 @@ begin inherited; end; +function TsCustomLineSeries.GetSmooth: Boolean; +begin + Result := FInterpolation in [ciBSpline, ciCubicSpline]; +end; + +procedure TsCustomLineSeries.SetSmooth(AValue: Boolean); +begin + if AValue then + begin + if not (FInterpolation in [ciBSpline, ciCubicSpline]) then + FInterpolation := ciCubicSpline; + end else + begin + if (FInterpolation in [ciBSpline, ciCubicSpline]) then + FInterpolation := ciLinear; + end; +end; + +{ TsLineSeries } +constructor TsLineSeries.Create(AChart: TsChart); +begin + inherited Create(AChart); + FGroupIndex := 0; +end; { TsPieSeries } constructor TsPieSeries.Create(AChart: TsChart); diff --git a/components/fpspreadsheet/source/common/fpsopendocumentchart.pas b/components/fpspreadsheet/source/common/fpsopendocumentchart.pas index 14eadf8d3..66c2af17a 100644 --- a/components/fpspreadsheet/source/common/fpsopendocumentchart.pas +++ b/components/fpspreadsheet/source/common/fpsopendocumentchart.pas @@ -173,7 +173,10 @@ uses type TAxisKind = 3..6; - TsOpenedCustomLineSeries = class(TsCustomLineSeries); + TsOpenedCustomLineSeries = class(TsCustomLineSeries) + public + property Interpolation; + end; TsOpenedTrendlineSeries = class(TsChartSeries) public @@ -2603,6 +2606,7 @@ function TsSpreadOpenDocChartWriter.GetChartPlotAreaStyleAsXML(AChart: TsChart; AIndent, AStyleID: Integer): String; var indent: String; + interpolation: TsChartInterpolation; interpolationStr: String = ''; verticalStr: String = ''; stackModeStr: String = ''; @@ -2625,15 +2629,24 @@ begin if (AChart.Series.Count > 0) and (AChart.Series[0] is TsPieSeries) then startAngleStr := Format('chart:angle-offset="%d" ', [TsPieSeries(AChart.Series[0]).StartAngle]); - case AChart.Interpolation of - ciLinear: ; - ciCubicSpline: interpolationStr := 'chart:interpolation="cubic-spline" '; - ciBSpline: interpolationStr := 'chart:interpolation="b-spline" '; - ciStepStart: interpolationStr := 'chart:interpolation="step-start" '; - ciStepEnd: interpolationStr := 'chart:interpolation="step-end" '; - ciStepCenterX: interpolationStr := 'chart:interpolation="step-center-x" '; - ciStepCenterY: interpolationStr := 'chart:interpolation="step-center-y" '; - end; + // In FPSpreadsheet individual series can be "smooth", in Calc only all. + // As a compromise, when we find at least one smooth series, all series are + // treated as such by writing the "chart:interpolation" attribute + for i := 0 to AChart.Series.Count-1 do + if AChart.Series[i] is TsCustomLineSeries then + begin + interpolation := TsOpenedCustomLineSeries(AChart.Series[i]).Interpolation; + case interpolation of + ciLinear: Continue; + ciCubicSpline: interpolationStr := 'chart:interpolation="cubic-spline" '; + ciBSpline: interpolationStr := 'chart:interpolation="b-spline" '; + ciStepStart: interpolationStr := 'chart:interpolation="step-start" '; + ciStepEnd: interpolationStr := 'chart:interpolation="step-end" '; + ciStepCenterX: interpolationStr := 'chart:interpolation="step-center-x" '; + ciStepCenterY: interpolationStr := 'chart:interpolation="step-center-y" '; + end; + break; + end; if not (AChart.GetChartType in [ctRadar, ctFilledRadar, ctPie]) then rightAngledAxes := 'chart:right-angled-axes="true" '; diff --git a/components/fpspreadsheet/source/common/xlsxooxmlchart.pas b/components/fpspreadsheet/source/common/xlsxooxmlchart.pas index 5fe49ac47..176e48b71 100644 --- a/components/fpspreadsheet/source/common/xlsxooxmlchart.pas +++ b/components/fpspreadsheet/source/common/xlsxooxmlchart.pas @@ -114,6 +114,7 @@ type procedure WriteAreaSeries(AStream: TStream; AIndent: Integer; ASeries: TsAreaSeries; ASeriesIndex, APosInAxisGroup: Integer); procedure WriteBarSeries(AStream: TStream; AIndent: Integer; ASeries: TsBarSeries; ASeriesIndex, APosInAxisGroup: Integer); procedure WriteBubbleSeries(AStream: TStream; AIndent: Integer; ASeries: TsBubbleSeries; ASeriesIndex, APosInAxisGroup: Integer); + procedure WriteLineSeries(AStream: TStream; AIndent: Integer; ASeries: TsLineSeries; ASeriesIndex, APosInAxisGroup: Integer); procedure WritePieSeries(AStream: TStream; AIndent: Integer; ASeries: TsPieSeries; ASeriesIndex: Integer); procedure WriteRadarSeries(AStream: TStream; AIndent: Integer; ASeries: TsRadarSeries; ASeriesIndex: Integer); procedure WriteScatterSeries(AStream: TStream; AIndent: Integer; ASeries: TsScatterSeries; ASeriesIndex, APosInAxisGroup: Integer); @@ -1280,6 +1281,7 @@ begin while Assigned(ANode) do begin nodeName := ANode.NodeName; + s := GetAttrValue(ANode, 'val'); case nodeName of 'c:ser': begin @@ -1287,19 +1289,14 @@ begin ReadChartSeriesProps(ANode.FirstChild, ser); end; 'c:grouping': - begin - s := GetAttrValue(ANode, 'val'); - case s of - 'stacked': AChart.StackMode := csmStacked; - 'percentStacked': AChart.StackMode := csmStackedPercentage; - end; + case s of + 'stacked': AChart.StackMode := csmStacked; + 'percentStacked': AChart.StackMode := csmStackedPercentage; end; 'c:varyColors': ; 'c:dLbls': ; - 'c:gapWidth': - ; { 'c:axId': ReadChartSeriesAxis(ANode, ser); @@ -1481,14 +1478,6 @@ begin 1: ReadChartAxis(workNode.FirstChild, AChart, AChart.X2Axis, FX2AxisID, FX2AxisDelete); end; inc(catAxCounter); - { - if (AChart.X2Axis.Alignment = AChart.XAxis.Alignment) and FX2AxisDelete then - begin - // Force using only a single x axis in this case - FX2AxisID := FXAxisID; - AChart.X2Axis.Visible := false; - end; - } end; 'c:dateAx': begin @@ -1503,14 +1492,6 @@ begin end; end; inc(dateAxCounter); - { - if (dateAxCounter > 1) and (AChart.X2Axis.Alignment = AChart.XAxis.Alignment) and FX2AxisDelete then - begin - // Force using only a single x axis in this case. - FX2AxisID := FXAxisID; - AChart.X2Axis.Visible := false; - end; - } end; 'c:valAx': begin @@ -1527,14 +1508,6 @@ begin 2: ReadChartAxis(workNode.FirstChild, AChart, AChart.Y2Axis, FY2AxisID, FY2AxisDelete); 3: ReadChartAxis(workNode.FirstChild, AChart, AChart.X2Axis, FX2AxisID, FX2AxisDelete); end; - { - if (AChart.X2Axis.Alignment = AChart.XAxis.Alignment) and FX2AxisDelete then - begin - // Force using only a single x axis in this case. - FX2AxisID := FXAxisID; - AChart.X2Axis.Visible := false; - end; - } end else begin case valAxCounter of @@ -1665,6 +1638,7 @@ var nodeName: String; s: String; ser: TsScatterSeries; + smooth: Boolean = false; begin if ANode = nil then exit; @@ -1681,9 +1655,10 @@ begin begin s := GetAttrValue(ANode, 'val'); if (s = 'smoothMarker') then - AChart.Interpolation := ciCubicSpline; + smooth := true; end; - 'c:varyColors': ; + 'c:varyColors': + ; 'c:dLbls': ; 'c:axId': @@ -1691,6 +1666,8 @@ begin end; ANode := ANode.NextSibling; end; + + ser.Smooth := smooth; end; {@@ ---------------------------------------------------------------------------- @@ -1971,6 +1948,7 @@ var nodeName, s: String; n: Integer; idx: Integer; + smooth: Boolean = false; begin if ANode = nil then exit; @@ -2011,6 +1989,8 @@ begin ReadChartSeriesTrendLine(ANode.FirstChild, ASeries); 'c:errBars': ReadChartSeriesErrorBars(ANode.FirstChild, ASeries); + 'c:smooth': + smooth := (s <> '0'); 'c:invertIfNegative': ; 'c:extLst': @@ -2018,6 +1998,9 @@ begin end; ANode := ANode.NextSibling; end; + + if ASeries is TsCustomLineSeries then + TsOpenedCustomLineSeries(ASeries).Smooth := smooth; end; {@@ ---------------------------------------------------------------------------- @@ -4028,25 +4011,28 @@ begin chart := ASeries.Chart; ser := TsOpenedCustomLineSeries(ASeries); - if ser.ShowSymbols then - case ser.Symbol of - cssRect: markerStr := 'square'; - cssDiamond: markerStr := 'diamond'; - cssTriangle: markerStr := 'triangle'; - cssTriangleDown: markerStr := 'triangle'; // !!!! - cssTriangleLeft: markerStr := 'triangle'; // !!!! - cssTriangleRight: markerStr := 'triangle'; // !!!! - cssCircle: markerStr := 'circle'; - cssStar: markerStr := 'star'; - cssX: markerstr := 'x'; - cssPlus: markerStr := '+'; - cssAsterisk: markerStr := 'star'; // !!! - cssDash: markerStr := 'dash'; - cssDot: markerStr := 'dot'; - else markerStr := 'star'; // !!! - end // The symbols marked by !!! are not available in Excel - else - markerStr := 'none'; + if not ser.ShowSymbols then + begin + Result := indent + '<c:symbol val="none"/>'; + exit; + end; + + case ser.Symbol of + cssRect: markerStr := 'square'; + cssDiamond: markerStr := 'diamond'; + cssTriangle: markerStr := 'triangle'; + cssTriangleDown: markerStr := 'triangle'; // !!!! + cssTriangleLeft: markerStr := 'triangle'; // !!!! + cssTriangleRight: markerStr := 'triangle'; // !!!! + cssCircle: markerStr := 'circle'; + cssStar: markerStr := 'star'; + cssX: markerstr := 'x'; + cssPlus: markerStr := '+'; + cssAsterisk: markerStr := 'star'; // !!! + cssDash: markerStr := 'dash'; + cssDot: markerStr := 'dot'; + else markerStr := 'star'; // !!! + end; // The symbols marked by !!! are not available in Excel symbolSizePts := round(mmToPts((ser.SymbolWidth + ser.SymbolHeight)/2)); if symbolSizePts > 72 then symbolSizePts := 72; @@ -4190,6 +4176,8 @@ begin WriteBubbleSeries(AStream, AIndent + 2, TsBubbleSeries(ser), j, posInGroup); xAxKind := 'c:valAx'; end; + ctLine: + WriteLineSeries(AStream, AIndent + 2, TsLineSeries(ser), j, posInGroup); ctPie, ctRing: WritePieSeries(AStream, AIndent + 2, TsPieSeries(ser), j); ctRadar, ctFilledRadar: @@ -4573,12 +4561,11 @@ begin indent + ' </c:spPr>' + LE ); end; - if TsOpenedCustomLineSeries(ASeries).ShowSymbols then - AppendToStream(AStream, - indent + ' <c:marker>' + LE + - GetChartSeriesMarkerXML(AIndent + 4, TsOpenedCustomLineSeries(ASeries)) + LE + - indent + ' </c:marker>' + LE - ); + AppendToStream(AStream, + indent + ' <c:marker>' + LE + + GetChartSeriesMarkerXML(AIndent + 4, TsOpenedCustomLineSeries(ASeries)) + LE + + indent + ' </c:marker>' + LE + ); end else // Series main formatting AppendToStream(AStream, @@ -4629,9 +4616,9 @@ begin [ yValName ] )); + // Bubble series: Bubble size range if (ASeries is TsBubbleSeries) then begin - // Bubble size range AppendToStream(AStream, indent + '<c:bubbleSize>' + LE + indent + GetChartRangeXML(AIndent + 4, TsBubbleSeries(ASeries).BubbleRange, 'c:numRef') + LE + @@ -4639,6 +4626,13 @@ begin ); end; + // Line series: Interpolation + if ASeries is TsLineSeries then + if TsLineSeries(ASeries).Interpolation in [ciLinear, ciStepStart, ciStepEnd, ciStepCenterX, ciStepCenterY] then + AppendToStream(AStream, + indent + ' <c:smooth val="0"/>' + LE + ); + AppendToStream(AStream, indent + '</c:ser>' + LE ); @@ -4734,6 +4728,67 @@ begin ); end; +{@@ ---------------------------------------------------------------------------- + Writes the properties of the given line series to the <c:plotArea> node of + file chartN.xml +-------------------------------------------------------------------------------} +procedure TsSpreadOOXMLChartWriter.WriteLineSeries(AStream: TStream; + AIndent: Integer; ASeries: TsLineSeries; ASeriesIndex, APosInAxisGroup: Integer); +const + GROUPING: Array[TsChartStackMode] of string = ('standard', 'stacked', 'percentStacked'); +var + indent: String; + chart: TsChart; + xAxis: TsChartAxis; + isFirstOfGroup: Boolean; + isLastOfGroup: Boolean; + prevSeriesGroupIndex: Integer = -1; + nextSeriesGroupIndex: Integer = -1; +begin + indent := DupeString(' ', AIndent); + chart := ASeries.Chart; + + if (ASeriesIndex > 0) and (chart.Series[ASeriesIndex-1].YAxis = ASeries.YAxis) then + prevSeriesGroupIndex := chart.Series[ASeriesIndex-1].GroupIndex; + if (ASeriesIndex < chart.Series.Count-1) and (chart.Series[ASeriesIndex+1].YAxis = ASeries.YAxis) then + nextSeriesGroupIndex := chart.Series[ASeriesIndex+1].GroupIndex; + + isFirstOfGroup := APosInAxisGroup and 1 = 1; + isLastOfGroup := APosInAxisgroup and 2 = 2; + + if ((ASeries.GroupIndex > -1) and (prevSeriesGroupIndex = ASeries.GroupIndex)) then + isFirstOfGroup := false; + if ((ASeries.GroupIndex > -1) and (nextSeriesGroupIndex = ASeries.GroupIndex)) then + isLastOfGroup := false; + + if isFirstOfGroup then + AppendToStream(AStream, Format( + indent + '<c:lineChart>' + LE + + indent + ' <c:grouping val="%s"/>' + LE, + [ GROUPING[chart.StackMode] ] + )); + + WriteChartSeriesNode(AStream, AIndent + 2, ASeries, ASeriesIndex); + + if isLastOfGroup then + begin + if ASeries.YAxis = calPrimary then + xAxis := chart.XAxis + else + xAxis := chart.X2Axis; + + AppendToStream(AStream, Format( + indent + ' <c:axId val="%d"/>' + LE + + indent + ' <c:axId val="%d"/>' + LE + + indent + '</c:lineChart>' + LE, + [ + FAxisID[xAxis.Alignment], // <c:axId> + FAxisID[ASeries.GetYAxis.Alignment] // <c:axId> + ] + )); + end; +end; + {$ENDIF} end. diff --git a/components/fpspreadsheet/source/visual/fpspreadsheetchart.pas b/components/fpspreadsheet/source/visual/fpspreadsheetchart.pas index e38955d7a..e57cbcae5 100644 --- a/components/fpspreadsheet/source/visual/fpspreadsheetchart.pas +++ b/components/fpspreadsheet/source/visual/fpspreadsheetchart.pas @@ -19,7 +19,7 @@ interface {$ifdef FPS_CHARTS} -uses +uses lazloggerbase, // RTL/FCL Classes, Contnrs, SysUtils, Types, FPCanvas, // LCL @@ -1079,7 +1079,6 @@ procedure TsWorkbookChartSource.UseDataPointColors(ASeries: TsChartSeries); var datapointStyle: TsChartDataPointStyle; - style: TChartStyle; i, j: Integer; c: TsColor; g: TsChartGradient; @@ -1187,6 +1186,7 @@ var firstSeries: TChartSeries; ch: TsChart; src: TsWorkbookChartSource; + interpolation: TsChartInterpolation = ciLinear; calcSrc: TCalculatedChartSource; style: TChartStyle; axAlign: TChartAxisAlignment; @@ -1246,6 +1246,14 @@ begin src := TsWorkbookChartSource.Create(self); src.WorkbookSource := FWorkbookSource; + if (ASeries is TsCustomLineSeries) then + begin + interpolation := TsOpenedCustomLineSeries(ASeries).Interpolation; + // TAChart cannot stack spline series. + if (interpolation in [ciBSpline, ciCubicSpline]) and (ch.StackMode <> csmDefault) then + interpolation := ciLinear; + end; + case ASeries.ChartType of ctBar: begin @@ -1253,11 +1261,11 @@ begin src.IntegerX := true; end; ctLine, ctScatter: - case ch.Interpolation of + case interpolation of ciLinear, ciStepStart, ciStepEnd, ciStepCenterX, ciStepCenterY: begin Result := TLineSeries.Create(FChart); - case ch.Interpolation of + case interpolation of ciLinear: TLineSeries(Result).LineType := ltFromPrevious; ciStepStart: TLineSeries(Result).LineType := ltStepXY; ciStepEnd: TLineSeries(Result).LineType := ltStepYX; @@ -1890,26 +1898,33 @@ end; function TsWorkbookChartLink.IsStackable(ASeries: TsChartSeries): Boolean; var ch: TsChart; + ser: TsChartSeries; i, numSeries: Integer; begin - Result := (ASeries.ChartType in [ctBar, ctLine, ctArea]); + Result := (ASeries.ChartType in [ctBar, ctLine, ctArea]) and (ASeries.GroupIndex > -1); if Result then begin ch := ASeries.Chart; numSeries := ch.Series.Count; if numSeries = 1 then + begin + Result := false; exit; + end; // Check whether all series are the same type and same y axis as ASeries. // NOTE: Not perfect yet since there might abe two stackable groups, // one for the left and one for the right axis... for i := 0 to numSeries - 1 do - if (ch.Series[i].ChartType <> ASeries.ChartType) or - (ch.Series[i].YAxis <> ASeries.YAxis) then + begin + ser := ch.Series[i]; + if (ser.ChartType <> ASeries.ChartType) or (ser.GroupIndex <> ASeries.GroupIndex) or + (ser.YAxis <> ASeries.YAxis) then begin Result := false; exit; end; + end; end; end; @@ -2661,10 +2676,15 @@ procedure TsWorkbookChartLink.UpdateChartStyle(AWorkbookSeries: TsChartSeries; AStyleIndex: Integer); var style: TChartStyle; + ch: TsChart; begin + ch := AWorkbookSeries.Chart; style := TChartStyle(FChartStyles.Styles[AStyleIndex]); - UpdateChartPen(AWorkbookSeries.Chart, AWorkbookSeries.Line, style.Pen); - UpdateChartBrush(AWorkbookSeries.Chart, AWorkbookSeries.Fill, style.Brush); + UpdateChartPen(ch, AWorkbookSeries.Line, style.Pen); + if (AWorkbookSeries is TsCustomLineSeries) then + UpdateChartBrush(ch, TsOpenedCustomLineSeries(AWorkbookSeries).SymbolFill, style.Brush) + else + UpdateChartBrush(ch, AWorkbookSeries.Fill, style.Brush); end; {@@ Updates title and footer of the linked TAChart. @@ -2731,6 +2751,9 @@ begin seriesPointer.HorizSize := mmToPx(openedWorkbookSeries.SymbolWidth / 2, ppi); seriesPointer.VertSize := mmToPx(openedWorkbookSeries.SymbolHeight / 2, ppi); + if lineseries <> nil then + DebugLn([BoolToStr(lineseries.ShowPoints, true), ' ', intTostr(ord(lineseries.pointer.Style)), ' ', inttostr(ord(lineseries.pointer.brush.style))]); + // Error bars UpdateChartErrorBars(AWorkbookSeries, AChartSeries);