diff --git a/components/fpspreadsheet/examples/other/chart/write_chart_demo.lpr b/components/fpspreadsheet/examples/other/chart/write_chart_demo.lpr index 5dc2a3749..14a0fae72 100644 --- a/components/fpspreadsheet/examples/other/chart/write_chart_demo.lpr +++ b/components/fpspreadsheet/examples/other/chart/write_chart_demo.lpr @@ -5,7 +5,7 @@ program write_chart_demo; uses SysUtils, fpspreadsheet, fpstypes, fpschart, xlsxooxml, fpsopendocument; const - SERIES_CLASS: TsChartSeriesClass = TsScatterSeries; + SERIES_CLASS: TsChartSeriesClass = TsBubbleSeries; //TsScatterSeries; r1 = 1; r2 = 8; var @@ -21,18 +21,21 @@ begin sheet1 := b.AddWorksheet('test1'); sheet1.WriteText(0, 1, '1+sin(x)'); sheet1.WriteText(0, 2, '1+sin(x/2)'); + sheet1.WriteText(0, 3, 'Bubble'); for i := r1 to r2-1 do begin sheet1.WriteNumber(i, 0, i-1); sheet1.WriteNumber(i, 1, 1+sin(i-1)); sheet1.WriteNumber(i, 2, 1+sin((i-1)/2)); + sheet1.WriteNumber(i, 3, i*i); // Bubble series radii end; sheet1.WriteNumber(r2, 0, 9); sheet1.WriteNumber(r2, 1, 2); sheet1.WriteNumber(r2, 2, 2.5); + sheet1.WriteNumber(r2, 3, r2*r2); // Create chart - ch := b.AddChart(sheet1, 4, 4, 160, 100); + ch := b.AddChart(sheet1, 4, 6, 160, 100); // Add first series (type depending on SERIES_CLASS) ser := SERIES_CLASS.Create(ch); @@ -47,16 +50,25 @@ begin TsLineSeries(ser).ShowSymbols := true; TsLineSeries(ser).Symbol := cssCircle; end; + if (ser is TsBubbleSeries) then + begin + TsBubbleSeries(ser).SetXRange(r1, 0, r2, 0); + TsBubbleSeries(ser).SetYRange(r1, 2, r2, 2); + TsBubbleSeries(ser).SetBubbleRange(r1, 3, r2, 3); + end; - // Add second series - ser := SERIES_CLASS.Create(ch); -// ser := TsBarSeries.Create(ch); - ser.SetTitleAddr(0, 2); - ser.SetLabelRange(r1, 0, r2, 0); - ser.SetXRange(r1, 0, r2, 0); - ser.SetYRange(r1, 2, r2, 2); - ser.Line.Color := scRed; - ser.Fill.FgColor := scRed; + if SERIES_CLASS <> TsBubbleSeries then + begin + // Add second series + ser := SERIES_CLASS.Create(ch); + // ser := TsBarSeries.Create(ch); + ser.SetTitleAddr(0, 2); + ser.SetLabelRange(r1, 0, r2, 0); + ser.SetXRange(r1, 0, r2, 0); + ser.SetYRange(r1, 2, r2, 2); + ser.Line.Color := scRed; + ser.Fill.FgColor := scRed; + end; {$IFDEF DARK_MODE} ch.Background.FgColor := scBlack; @@ -71,7 +83,7 @@ begin ch.Background.Style := fsSolidFill; ch.Border.Style := clsSolid; ch.PlotArea.Background.Style := fsSolidFill; - //ch.RotatedAxes := true; + ch.RotatedAxes := true; ch.StackMode := csmStackedPercentage; //ch.Interpolation := ciCubicSpline; diff --git a/components/fpspreadsheet/source/common/fpschart.pas b/components/fpspreadsheet/source/common/fpschart.pas index c7cf1cb9a..97a840be8 100644 --- a/components/fpspreadsheet/source/common/fpschart.pas +++ b/components/fpspreadsheet/source/common/fpschart.pas @@ -111,7 +111,7 @@ type end; TsChartAxisPosition = (capStart, capEnd, capValue); - TsChartType = (ctEmpty, ctBar, ctLine, ctArea, ctBarLine, ctScatter); + TsChartType = (ctEmpty, ctBar, ctLine, ctArea, ctBarLine, ctScatter, ctBubble); TsChartAxis = class(TsChartFillElement) private @@ -239,6 +239,15 @@ type constructor Create(AChart: TsChart); override; end; + TsBubbleSeries = class(TsChartSeries) + private + FBubbleRange: TsCellRange; + public + constructor Create(AChart: TsChart); override; + procedure SetBubbleRange(ARow1, ACol1, ARow2, ACol2: Cardinal); + property BubbleRange: TsCellRange read FBubbleRange; + end; + TsChartSeriesSymbol = ( cssRect, cssDiamond, cssTriangle, cssTriangleDown, cssTriangleLeft, cssTriangleRight, cssCircle, cssStar, cssX, cssPlus, cssAsterisk @@ -368,7 +377,7 @@ type { Connecting line between data points (for line and scatter series) } property Interpolation: TsChartInterpolation read FInterpolation write FInterpolation; - { x and y axes exchanged (for bar series) } + { x and y axes exchanged (mainly for bar series, but works also for scatter and bubble series) } property RotatedAxes: Boolean read FRotatedAxes write FRotatedAxes; { Stacking of series (for bar and area series ) } property StackMode: TsChartStackMode read FStackMode write FStackMode; @@ -716,6 +725,23 @@ begin FChartType := ctBar; end; +{ TsBubbleSeries } + +constructor TsBubbleSeries.Create(AChart: TsChart); +begin + inherited; + FChartType := ctBubble; +end; + +procedure TsBubbleSeries.SetBubbleRange(ARow1, ACol1, ARow2, ACol2: Cardinal); +begin + if (ARow1 <> ARow2) and (ACol1 <> ACol2) then + raise Exception.Create('Series bubble values can only be located in a single column or row.'); + FBubbleRange.Row1 := ARow1; + FBubbleRange.Col1 := ACol1; + FBubbleRange.Row2 := ARow2; + FBubbleRange.Col2 := ACol2; +end; { TsLineSeries } diff --git a/components/fpspreadsheet/source/common/fpsopendocument.pas b/components/fpspreadsheet/source/common/fpsopendocument.pas index 51598d5cd..20f8fbf6e 100644 --- a/components/fpspreadsheet/source/common/fpsopendocument.pas +++ b/components/fpspreadsheet/source/common/fpsopendocument.pas @@ -538,7 +538,7 @@ const ); CHART_TYPE_NAMES: array[TsChartType] of string = ( - '', 'bar', 'line', 'area', 'barLine', 'scatter' + '', 'bar', 'line', 'area', 'barLine', 'scatter', 'bubble' ); CHART_SYMBOL_NAMES: array[TsChartSeriesSymbol] of String = ( @@ -7142,7 +7142,9 @@ var sheet: TsWorksheet; series: TsChartSeries; valuesRange: String; - domainRange: String = ''; + domainRangeX: String = ''; + domainRangeY: String = ''; + rangeStr: String = ''; titleAddr: String; count: Integer; begin @@ -7151,10 +7153,10 @@ begin series := AChart.Series[ASeriesIndex]; sheet := TsWorkbook(FWorkbook).GetWorksheetByIndex(AChart.sheetIndex); - // These are the x values of a scatter plot. - if series is TsScatterSeries then + // These are the x values of a scatter or bubble plot. + if (series is TsScatterSeries) or (series is TsBubbleSeries) then begin - domainRange := GetSheetCellRangeString_ODS( + domainRangeX := GetSheetCellRangeString_ODS( sheet.Name, sheet.Name, series.XRange.Row1, series.XRange.Col1, series.XRange.Row2, series.XRange.Col2, @@ -7162,20 +7164,37 @@ begin ); end; - // These are the y values - valuesRange := GetSheetCellRangeString_ODS( - sheet.Name, sheet.Name, - series.YRange.Row1, series.YRange.Col1, - series.YRange.Row2, series.YRange.Col2, - rfAllRel, false - ); + if series is TsBubbleSeries then + begin + domainRangeY := GetSheetCellRangeString_ODS( + sheet.Name, sheet.Name, + series.YRange.Row1, series.YRange.Col1, + series.YRange.Row2, series.YRange.Col2, + rfAllRel, false + ); + // These are the bubble radii + valuesRange := GetSheetCellRangeString_ODS( + sheet.Name, sheet.Name, + TsBubbleSeries(series).BubbleRange.Row1, TsBubbleSeries(series).BubbleRange.Col1, + TsBubbleSeries(series).BubbleRange.Row2, TsBubbleSeries(series).BubbleRange.Col2, + rfAllRel, false + ); + end else + // These are the y values of the non-bubble series + valuesRange := GetSheetCellRangeString_ODS( + sheet.Name, sheet.Name, + series.YRange.Row1, series.YRange.Col1, + series.YRange.Row2, series.YRange.Col2, + rfAllRel, false + ); // And these are the data point labels. titleAddr := GetSheetCellRangeString_ODS( sheet.Name, sheet.Name, series.TitleAddr.Row, series.TitleAddr.Col, series.TitleAddr.Row, series.TitleAddr.Col, - rfAllRel, false); + rfAllRel, false + ); count := series.YRange.Row2 - series.YRange.Row1 + 1; // Store the series properties @@ -7186,11 +7205,17 @@ begin 'chart:class="chart:%s">' + LE, [ AStyleID, valuesRange, titleAddr, CHART_TYPE_NAMES[series.ChartType] ] )); - if domainRange <> '' then + if domainRangeY <> '' then AppendToStream(AChartStream, Format( indent + '' + LE, - [ domainRange ] + [ domainRangeY ] )); + if domainRangeX <> '' then + AppendToStream(AChartStream, Format( + indent + '' + LE, + [ domainRangeX ] + )); + AppendToStream(AChartStream, Format( indent + ' ' + LE, [ count ] @@ -7478,7 +7503,7 @@ begin end; Result := Format( - indent + ' ' + LE + + indent + ' ', [ AStyleID ]) + LE + indent + ' ' + LE + - indent + ' ' + LE, - [ AStyleID ] - ); + indent + ' ' + LE; end; {