diff --git a/components/fpspreadsheet/examples/other/chart/piechart_write_demo.lpr b/components/fpspreadsheet/examples/other/chart/piechart_write_demo.lpr index 068a0be78..aca3357e6 100644 --- a/components/fpspreadsheet/examples/other/chart/piechart_write_demo.lpr +++ b/components/fpspreadsheet/examples/other/chart/piechart_write_demo.lpr @@ -63,10 +63,10 @@ begin line := TsChartline.CreateSolid(scWhite, 0.8); fill := TsChartFill.CreateHatchFill(ch.Hatches.AddLineHatch('ltHorz', chsSingle, $00C0FF, 1, 0.1, 0), scWhite); ser.DataPointStyles.AddSolidFill(0, $C47244, line); - ser.DataPointStyles.AddSolidFill(1, $317DED, line, 10); // with explode offset, as percentage + ser.DataPointStyles.AddSolidFill(1, $317DED, line, 20); // with explode offset, as percentage ser.DataPointStyles.AddSolidFill(2, $A5A5A5, line); ser.DataPointStyles.AddFillAndLine(3, fill, line); - ser.DataPointStyles.AddSolidFill(4, $D69B5B, line, 20); + ser.DataPointStyles.AddSolidFill(4, $D69B5B, line); line.Free; fill.Free; diff --git a/components/fpspreadsheet/source/common/fpschart.pas b/components/fpspreadsheet/source/common/fpschart.pas index 2456535ae..4f558b925 100644 --- a/components/fpspreadsheet/source/common/fpschart.pas +++ b/components/fpspreadsheet/source/common/fpschart.pas @@ -420,6 +420,7 @@ type constructor Create(AChart: TsChart); function AddFillAndLine(ADataPointIndex: Integer; AFill: TsChartFill; ALine: TsChartline; APieOffset: Integer = 0): Integer; function AddSolidFill(ADataPointIndex: Integer; AColor: TsColor; ALine: TsChartLine = nil; APieOffset: Integer = 0): Integer; + function IndexOfDataPoint(ADataPointIndex: Integer): Integer; property Items[AIndex: Integer]: TsChartDataPointStyle read GetItem write SetItem; default; end; @@ -2010,6 +2011,14 @@ begin Result := TsChartDataPointStyle(inherited Items[AIndex]); end; +function TsChartDataPointStyleList.IndexOfDataPoint(ADataPointIndex: Integer): Integer; +begin + for Result := 0 to Count - 1 do + if Items[Result].DataPointIndex = ADataPointIndex then + exit; + Result := -1; +end; + procedure TsChartDataPointStyleList.SetItem(AIndex: Integer; AValue: TsChartDataPointStyle); begin diff --git a/components/fpspreadsheet/source/common/fpsopendocumentchart.pas b/components/fpspreadsheet/source/common/fpsopendocumentchart.pas index 045047696..5035666d3 100644 --- a/components/fpspreadsheet/source/common/fpsopendocumentchart.pas +++ b/components/fpspreadsheet/source/common/fpsopendocumentchart.pas @@ -2733,8 +2733,7 @@ begin if dataPointStyle = nil then begin - // The series process by the caller has not individual style format. - // We must write a node nevertheless... + // No style information found. We write a node, nevertheless... (maybe can be dropped?) Result := Format( indent + '' + LE + indent + ' ' + LE + @@ -3744,7 +3743,7 @@ var trendlineEquation: String = ''; trendline: TsChartTrendline = nil; titleAddr: String; - i, j, idx, count: Integer; + i, idx, count: Integer; nextStyleID, seriesStyleID, trendlineStyleID, trendlineEquStyleID: Integer; xErrStyleID, yErrStyleID, dataStyleID: Integer; datapointStyle: TsChartDataPointStyle; @@ -3960,7 +3959,7 @@ begin for i := 0 to count - 1 do begin AppendToStream(AChartStream, Format( - indent + ' ' + LE, + indent + ' ' + LE, [ dataStyleID + i ] )); inc(nextStyleID); @@ -4028,17 +4027,7 @@ begin begin for i := 0 to count - 1 do begin - idx := -1; - for j := 0 to series.DataPointStyles.Count-1 do - begin - dataPointStyle := series.DataPointstyles[j]; - if (dataPointStyle <> nil) and (dataPointStyle.DataPointIndex = i) then - begin - idx := j; - break; - end; - end; - + idx := series.DataPointStyles.IndexOfDatapoint(i); AppendToStream(AStyleStream, GetChartSeriesDataPointStyleAsXML(AChart, ASeriesIndex, idx, AStyleIndent, dataStyleID) ); diff --git a/components/fpspreadsheet/source/visual/fpspreadsheetchart.pas b/components/fpspreadsheet/source/visual/fpspreadsheetchart.pas index d3aa72c22..531672d02 100644 --- a/components/fpspreadsheet/source/visual/fpspreadsheetchart.pas +++ b/components/fpspreadsheet/source/visual/fpspreadsheetchart.pas @@ -34,10 +34,6 @@ uses // FPSpreadsheet Visual fpSpreadsheetCtrls, fpSpreadsheetGrid, fpsVisualUtils; -{$if LCL_FullVersion >= 3990000} - {$define DATAPOINT_STYLES} -{$ifend} - type {@@ Chart data source designed to work together with TChart from Lazarus @@ -62,6 +58,10 @@ type FCyclicX: Boolean; FIntegerX: Boolean; // only integers allowed for x values FDataPointColors: array of TsColor; + FPieSeriesMode: boolean; + FPieOffsets: Array of Double; + FStyles: TChartStyles; + function GetRange(AIndex: TsXYLRange): String; function GetTitle: String; function GetWorkbook: TsWorkbook; @@ -86,7 +86,7 @@ type public constructor Create(AOwner: TComponent); override; destructor Destroy; override; - function RangeIsEmpty(ARange: TsCellRange): Boolean; + procedure CheckPieSeriesMode(ASeries: TsChartSeries); procedure Reset; procedure SetColorRange(ARange: TsChartRange); procedure SetLabelRange(ARange: TsChartRange); @@ -95,13 +95,17 @@ type procedure SetTitleAddr(Addr: TsChartCellAddr); procedure SetXErrorBarRange(APosRange, ANegRange: TsChartRange); procedure SetYErrorBarRange(APosRange, ANegRange: TsChartRange); - procedure UseDataPointColors(ASeries: TsChartSeries); property PointsNumber: Cardinal read FPointsNumber; property Workbook: TsWorkbook read GetWorkbook; public // Interface to TsWorkbookSource procedure ListenerNotification(AChangedItems: TsNotificationItems; AData: Pointer = nil); procedure RemoveWorkbookSource; + public + // Special methods to be called by TsWorkbookChartLink + function RangeIsEmpty(ARange: TsCellRange): Boolean; + procedure UseDatapointColors(ASeries: TsChartSeries); + property Styles: TChartStyles read FStyles; published property WorkbookSource: TsWorkbookSource read FWorkbookSource write SetWorkbookSource; property ColorRange: String index rngColor read GetRange write SetRange; @@ -180,9 +184,7 @@ type procedure UpdateScatterSeries(AWorkbookSeries: TsScatterSeries; AChartSeries: TLineSeries); procedure UpdateStockSeries(AWorkbookSeries: TsStockSeries; AChartSeries: TStockSeries); - {$ifdef DATAPOINT_STYLES} - procedure UseDatapointStyles(AWorkbookSeries: TsChartSeries; AChartSeries: TChartSeries); - {$endif} + procedure CreateChartStylesFromDatapoints(AWorkbookSeries: TsChartSeries; AChartStyles: TChartStyles); public constructor Create(AOwner: TComponent); override; @@ -377,6 +379,7 @@ end; constructor TsWorkbookChartSource.Create(AOwner: TComponent); begin inherited; + FStyles := TChartStyles.Create(self); FCurItemIndex := -1; ClearRanges; end; @@ -462,6 +465,9 @@ begin FRangeStr[rngY] := ''; FRangeStr[rngLabel] := ''; FRangeStr[rngColor] := ''; + + FStyles.Styles.Clear; + SetLength(FPieOffsets, 0); end; @@ -526,20 +532,30 @@ begin exit; end; - for i := 0 to XCount-1 do + { In PieSeriesMode, the x values are not taken from the worksheet, but + have been made available in the separate array FPieOffsets. } + if FPieSeriesMode then begin - if (FRanges[rngX, i] <> nil) then - begin - GetXYItem(rngX, i, AIndex, value, tmpLabel); - if FIntegerX then - value := trunc(value); - end else - if FCyclicX then - value := AIndex / FPointsNumber * TWO_PI + if (AIndex >= 0) and (AIndex < Length(FPieOffsets)) then + FCurItem.SetX(0, FPieOffsets[AIndex]) else - value := AIndex; - FCurItem.SetX(i, value); - end; + FCurItem.SetX(0, 0.0); + end + else + for i := 0 to XCount-1 do + begin + if (FRanges[rngX, i] <> nil) then + begin + GetXYItem(rngX, i, AIndex, value, tmpLabel); + if FIntegerX then + value := trunc(value); + end else + if FCyclicX then + value := AIndex / FPointsNumber * TWO_PI + else + value := AIndex; + FCurItem.SetX(i, value); + end; for i := 0 to YCount-1 do begin @@ -556,6 +572,7 @@ begin FCurItem.Color := clTAColor; // = clDefault if AIndex <= High(FDataPointColors) then FCurItem.Color := FDataPointColors[AIndex]; + if FRanges[rngColor] <> nil then begin GetXYItem(rngColor, 0, AIndex, dummyNumber, dummyString); @@ -997,14 +1014,44 @@ begin SetRangeFromChart(rngY, YIndex, ARange); end; +{@@ ---------------------------------------------------------------------------- + In case of a pie series, both xlsx and ods files do not provide the + pie offsets as worksheet cell ranges but as attributes in the xml files. + Therefore, we store these offsets separately in an array, FPieOffsets. +-------------------------------------------------------------------------------} +procedure TsWorkbookChartSource.CheckPieSeriesMode(ASeries: TsChartSeries); +var + i, j: Integer; + datapointStyle: TsChartDataPointStyle; +begin + FPieSeriesMode := (ASeries is TsPieSeries); + if FPieSeriesMode then + begin + SetLength(FPieOffsets, ASeries.Count); + for i := 0 to ASeries.Count-1 do + begin + j := ASeries.DataPointStyles.IndexOfDataPoint(i); + FPieOffsets[i] := 0; + dataPointStyle := ASeries.DataPointStyles[j]; + if dataPointStyle <> nil then + FPieOffsets[i] := dataPointStyle.PieOffset * 0.01; + end; + end else + SetLength(FPieOffsets, 0); +end; + {@@ ---------------------------------------------------------------------------- Extracts the fill color from the DataPointStyle items of the series. All the other elements are ignored because TAChart does not support them. + + But note: Some series types allow to use chartstyles for individual data point + formatting. In this case this method is not executed. -------------------------------------------------------------------------------} procedure TsWorkbookChartSource.UseDataPointColors(ASeries: TsChartSeries); var datapointStyle: TsChartDataPointStyle; - i: Integer; + style: TChartStyle; + i, j: Integer; c: TsColor; g: TsChartGradient; begin @@ -1037,7 +1084,6 @@ begin end; end; - {@@ ---------------------------------------------------------------------------- Setter method for the WorkbookSource -------------------------------------------------------------------------------} @@ -1242,12 +1288,18 @@ begin src.SetColorRange(ASeries.FillColorRange); src.SetTitleAddr(ASeries.TitleAddr); - {$ifdef DATAPOINT_STYLES} - UseDatapointStyles(ASeries, Result); - {$else} - // Copy individual data point colors to the chart series. - src.UseDataPointColors(ASeries); - {$endif} + // Send pie offsets to chart soruce... + src.CheckPieSeriesMode(ASeries); + // ... as well as datapoint styles/colors + CreateChartStylesFromDatapoints(ASeries, src.Styles); + {$if LCL_FullVersion >= 3990000} + if (Result is TPieSeries) then + TPieSeries(Result).Styles := src.Styles + else if (Result is TBubbleSeries) then + TBubbleSeries(Result).Styles := src.Styles + else if (Result is TBarSeries) then + TBarSeries(Result).Styles := src.Styles; + {$ifend} if stackable then begin calcSrc := TCalculatedChartSource.Create(self); @@ -2614,6 +2666,7 @@ begin if AWorkbookSeries is TsRingSeries then AChartSeries.InnerRadiusPercent := TsRingSeries(AWorkbookSeries).InnerRadiusPercent; {$IFEND} + AChartSeries.Exploded := true; FChart.BottomAxis.Visible := false; FChart.LeftAxis.Visible := false; @@ -2682,6 +2735,41 @@ begin UpdateChartSeriesTrendline(AWorkbookSeries, AChartSeries); end; +procedure TsWorkbookChartLink.CreateChartStylesFromDatapoints(AWorkbookSeries: TsChartSeries; + AChartStyles: TChartStyles); +var + style: TChartStyle; + datapointStyle: TsChartDatapointStyle; + i: Integer; +begin + AChartStyles.Styles.Clear; + + if AWorkbookSeries.DataPointStyles.Count = 0 then + exit; + if not ((AWorkbookSeries is TsPieSeries) or (AWorkbookSeries is TsBubbleSeries)) then + exit; + if (AWorkbookSeries is TsBarSeries) and (AWorkbookSeries.Chart.Series.Count > 1) then + exit; // TAChart cannot handle datapoint styles for layered bar series + + for i := 0 to AWorkbookSeries.Count-1 do + begin + style := AChartStyles.Add; + datapointStyle := AWorkbookSeries.DataPointStyles[i]; + if datapointstyle = nil then + begin + UpdateChartBrush(AWorkbookSeries.Chart, AWorkbookSeries.Fill, style.brush); + UpdateChartPen(AWorkbookSeries.Chart, AWorkbookSeries.Line, style.Pen); + end else + begin + UpdateChartBrush(AWorkbookSeries.Chart, datapointStyle.Background, style.Brush); + UpdateChartPen(AWorkbookSeries.Chart, datapointStyle.Border, style.Pen); + end; + end; +end; + + + + (* {$ifdef DATAPOINT_STYLES} procedure TsWorkbookChartLink.UseDatapointStyles(AWorkbookSeries: TsChartSeries; AChartSeries: TChartSeries); @@ -2708,7 +2796,8 @@ begin style := styles.Add; if dps = nil then begin - UpdateChartBrush(AWorkbookSeries.Chart, AWorkbookSeries.FILL, style.brush); + UpdateChartBrush(AWorkbookSeries.Chart, AWorkbookSeries.Fill, style.brush); + UpdateChartPen(AWorkbookSeries.Chart, AWorkbookSeries.Line, style.Pen); end else begin UpdateChartBrush(AWorkbookSeries.Chart, dps.Background, style.Brush); @@ -2724,7 +2813,7 @@ begin TBarSeries(AChartSeries).Styles := styles; end; {$endif} - + *) {$ENDIF} end.