diff --git a/components/tachart/tacustomseries.pas b/components/tachart/tacustomseries.pas index fda310e775..eb4e788459 100644 --- a/components/tachart/tacustomseries.pas +++ b/components/tachart/tacustomseries.pas @@ -22,6 +22,7 @@ uses const DEF_AXIS_INDEX = -1; + DEF_ERR_ENDLENGTH = 5; type TNearestPointParams = record @@ -257,9 +258,12 @@ type TBasicPointSeries = class(TChartSeries) strict private FMarkPositions: TLinearMarkPositions; + FErrorBars: array[0..1] of TChartErrorBar; FOnCustomDrawPointer: TSeriesPointerCustomDrawEvent; FOnGetPointerStyle: TSeriesPointerStyleEvent; + function GetErrorBars(AIndex: Integer): TChartErrorBar; function GetLabelDirection(AIndex: Integer): TLabelDirection; + procedure SetErrorBars(AIndex: Integer; AValue: TChartErrorBar); procedure SetMarkPositions(AValue: TLinearMarkPositions); procedure SetPointer(AValue: TSeriesPointer); procedure SetStacked(AValue: Boolean); @@ -276,11 +280,16 @@ type procedure AfterDrawPointer( ADrawer: IChartDrawer; AIndex: Integer; const APos: TPoint); virtual; + procedure DrawErrorBars(ADrawer: IChartDrawer); procedure DrawLabels(ADrawer: IChartDrawer); procedure DrawPointers(ADrawer: IChartDrawer; AStyleIndex: Integer = 0; UseDataColors: Boolean = false); procedure FindExtentInterval( const AExtent: TDoubleRect; AFilterByExtent: Boolean); + { + function GetErrorBars(APointIndex: Integer; IsXError: Boolean; + out AGraphPointPos, AGraphPointNeg: Double): Boolean; + } function GetLabelDataPoint(AIndex: Integer): TDoublePoint; virtual; procedure GetLegendItemsRect(AItems: TChartLegendItems; ABrush: TBrush); function GetXRange(AX: Double; AIndex: Integer): Double; @@ -296,9 +305,13 @@ type property Pointer: TSeriesPointer read FPointer write SetPointer; property Stacked: Boolean read FStacked write SetStacked; + protected procedure AfterAdd; override; procedure UpdateMargins(ADrawer: IChartDrawer; var AMargins: TRect); override; + + property XErrorBars: TChartErrorBar index 0 read GetErrorBars write SetErrorBars; + property YErrorBars: TChartErrorBar index 1 read GetErrorBars write SetErrorBars; property OnCustomDrawPointer: TSeriesPointerCustomDrawEvent read FOnCustomDrawPointer write FOnCustomDrawPointer; property OnGetPointerStyle: TSeriesPointerStyleEvent @@ -1076,10 +1089,15 @@ end; { TBasicPointSeries } procedure TBasicPointSeries.AfterAdd; +var + i: Integer; begin inherited AfterAdd; if Pointer <> nil then Pointer.SetOwner(ParentChart); + for i := 0 to 1 do + if FErrorBars[i] <> nil then + FErrorBars[i].SetOwner(ParentChart); end; procedure TBasicPointSeries.AfterDrawPointer( @@ -1105,16 +1123,96 @@ end; constructor TBasicPointSeries.Create(AOwner: TComponent); begin inherited; + FErrorBars[0] := TChartErrorBar.Create(FChart); + FErrorBars[1] := TChartErrorBar.Create(FChart); FOptimizeX := true; ToolTargets := [nptPoint, nptYList]; end; destructor TBasicPointSeries.Destroy; begin + FreeAndNil(FErrorBars[0]); + FreeAndNil(FErrorBars[1]); FreeAndNil(FPointer); inherited; end; +procedure TBasicPointSeries.DrawErrorBars(ADrawer: IChartDrawer); + + procedure EndBar(p: TPoint; w: Integer; IsHorBar: Boolean); + begin + if IsHorBar then + ADrawer.Line(Point(p.x, p.y-w), Point(p.x, p.y+w)) + else + ADrawer.Line(Point(p.x-w, p.y), Point(p.x+w, p.y)); + end; + + procedure DrawErrorBar(p: TDoublePoint; vp, vn: Double; w: Integer; + IsXError: Boolean); + var + p1, p2: TDoublePoint; + imgPt1, imgPt2: TPoint; + isHorBar: Boolean; + begin + isHorBar := (IsXError and not IsRotated) or (IsRotated and not IsXError); + + if IsHorBar then begin + p1 := DoublePoint(vp, p.Y); + p2 := DoublePoint(vn, p.Y); + end else begin + p1 := DoublePoint(p.X, vp); + p2 := DoublePoint(p.X, vn); + end; + imgPt1 := ParentChart.GraphToImage(p1); + imgPt2 := ParentChart.GraphToImage(p2); + ADrawer.Line(imgPt1, imgPt2); + + EndBar(imgPt1, w, isHorBar); + EndBar(imgPt2, w, isHorBar); + end; + + procedure InternalDrawErrorBars(IsXError: Boolean); + var + i: Integer; + p: TDoublePoint; + vp, vn: Double; + w, w0: Integer; + errbar: TChartErrorBar; + begin + if Assigned(Pointer) then + w0 := IfThen(IsXError, Pointer.VertSize, Pointer.HorizSize) + else + w0 := DEF_ERR_ENDLENGTH; + errbar := TChartErrorBar(IfThen(IsXError, XErrorBars, YErrorBars)); + w := ADrawer.Scale(IfThen(errBar.Width = -1, w0, errBar.Width)); + + for i := FLoBound to FUpBound do begin + p := FGraphPoints[i - FLoBound]; + if not ParentChart.IsPointInViewPort(p) then continue; + if IsXError then begin + if Source.GetXErrorBarLimits(i, vp, vn) then + DrawErrorBar(p, AxisToGraphX(vp), AxisToGraphX(vn), w, true); + end else begin + if Source.GetYErrorBarLimits(i, vp, vn) then + DrawErrorBar(p, AxisTographY(vp), AxisToGraphY(vn), w, false); + end; + end; + end; + +begin + if Assigned(YErrorBars) and YErrorBars.Visible and Source.HasYErrorBars then + begin + ADrawer.Pen := YErrorBars.Pen; + InternalDrawErrorBars(false); + end; + + if Assigned(XErrorBars) and XErrorBars.Visible and Source.HasXErrorBars then + begin + ADrawer.pen := XErrorBars.Pen; + InternalDrawErrorBars(true); + end; +end; + procedure TBasicPointSeries.DrawLabels(ADrawer: IChartDrawer); var prevLabelPoly: TPointArray; @@ -1272,6 +1370,11 @@ begin end; end; +function TBasicPointSeries.GetErrorBars(AIndex: Integer): TChartErrorBar; +begin + Result := FErrorBars[AIndex]; +end; + function TBasicPointSeries.GetLabelDataPoint(AIndex: Integer): TDoublePoint; begin Result := GetGraphPoint(AIndex); @@ -1518,6 +1621,13 @@ begin FGraphPoints[i - FLoBound] := GetGraphPoint(i); end; +procedure TBasicPointSeries.SetErrorBars(AIndex: Integer; + AValue: TChartErrorBar); +begin + FErrorBars[AIndex] := AValue; + UpdateParentChart; +end; + procedure TBasicPointSeries.SetMarkPositions(AValue: TLinearMarkPositions); begin if FMarkPositions = AValue then exit; diff --git a/components/tachart/tacustomsource.pas b/components/tachart/tacustomsource.pas index 3d2593fb07..ef1e9b8120 100644 --- a/components/tachart/tacustomsource.pas +++ b/components/tachart/tacustomsource.pas @@ -149,8 +149,32 @@ type property Current: PChartDataItem read GetCurrent; end; + TChartErrorBarKind = (ebkNone, ebkConst, ebkPercent, ebkChartSource); + + TChartErrorBarData = class(TPersistent) + private + FKind: TChartErrorBarKind; + FValue: array[0..1] of Double; // 0 = positive, 1 = negative + FOnChange: TNotifyEvent; + function GetValue(AIndex: Integer): Double; + procedure SetKind(AValue: TChartErrorbarKind); + procedure SetValue(AIndex: Integer; AValue: Double); + public + constructor Create; + procedure Assign(ASource: TPersistent); override; + property OnChange: TNotifyEvent read FOnChange write FOnChange; + published + property Kind: TChartErrorBarKind read FKind write SetKind default ebkNone; + property NegDelta: Double index 1 read GetValue write SetValue; + property PosDelta: Double index 0 read GetValue write SetValue; + end; + TCustomChartSource = class(TBasicChartSource) strict private + FErrorBarData: array[0..1] of TChartErrorBarData; + procedure ChangeErrorBarHandler(Sender: TObject); + function GetErrorBarData(AIndex: Integer): TChartErrorBarData; + procedure SetErrorBarData(AIndex: Integer; AValue: TChartErrorBarData); procedure SortValuesInRange( var AValues: TChartValueTextArray; AStart, AEnd: Integer); strict protected @@ -160,14 +184,21 @@ type FValuesTotalIsValid: Boolean; FXCount: Cardinal; FYCount: Cardinal; - function GetCount: Integer; virtual; abstract; + function GetErrorBarLimits(APointIndex: Integer; Which: Integer; + out AUpperLimit, ALowerLimit: Double): Boolean; + function GetHasErrorBars(Which: Integer): Boolean; function GetItem(AIndex: Integer): PChartDataItem; virtual; abstract; procedure InvalidateCaches; procedure SetXCount(AValue: Cardinal); virtual; abstract; procedure SetYCount(AValue: Cardinal); virtual; abstract; + property XErrorBarData: TChartErrorBarData index 0 read GetErrorBarData + write SetErrorBarData; + property YErrorBarData: TChartErrorBarData index 1 read GetErrorBarData + write SetErrorBarData; public constructor Create(AOwner: TComponent); override; + destructor Destroy; override; public procedure AfterDraw; virtual; procedure BeforeDraw; virtual; @@ -183,6 +214,14 @@ type function FormatItemXYText( const AFormat: String; AX, AY: Double; AText: String): String; function GetEnumerator: TCustomChartSourceEnumerator; + function GetXErrorBarLimits(APointIndex: Integer; + out AUpperLimit, ALowerLimit: Double): Boolean; + function GetYErrorBarLimits(APointIndex: Integer; + out AUpperLimit, ALowerLimit: Double): Boolean; + function HasXErrorBars: Boolean; + function HasYErrorBars: Boolean; + function IsXErrorIndex(AXIndex: Integer): Boolean; + function IsYErrorIndex(AYIndex: Integer): Boolean; function IsSorted: Boolean; virtual; procedure ValuesInRange( AParams: TValuesInRangeParams; var AValues: TChartValueTextArray); virtual; @@ -474,7 +513,8 @@ begin YList[AIndex - 1] := AValue; end; -{ TBaisChartSource } + +{ TBasicChartSource } constructor TBasicChartSource.Create(AOwner: TComponent); begin @@ -647,6 +687,45 @@ begin FIndex := 0; end; + +{ TChartErrorBarData } + +constructor TChartErrorBarData.Create; +begin + inherited; + FValue[0] := -1; + FValue[1] := -1; + FKind := ebkNone; +end; + +procedure TChartErrorBarData.Assign(ASource: TPersistent); +begin + if ASource is TChartErrorBarData then begin + FValue := TChartErrorBarData(ASource).FValue; + FKind := TChartErrorBarData(ASource).Kind; + end; + inherited; +end; + +function TChartErrorBarData.GetValue(AIndex: Integer): Double; +begin + Result := FValue[AIndex]; +end; + +procedure TChartErrorBarData.SetKind(AValue: TChartErrorBarKind); +begin + if FKind = AValue then exit; + FKind := AValue; + if Assigned(FOnChange) then FOnChange(self); +end; + +procedure TChartErrorBarData.SetValue(AIndex: Integer; AValue: Double); +begin + FValue[AIndex] := AValue; + if Assigned(FOnChange) then FOnChange(self); +end; + + { TCustomChartSource } procedure TCustomChartSource.AfterDraw; @@ -665,10 +744,29 @@ begin end; constructor TCustomChartSource.Create(AOwner: TComponent); +var + i: Integer; begin inherited Create(AOwner); FXCount := 1; FYCount := 1; + for i:=0 to 1 do begin + FErrorBarData[i] := TChartErrorBarData.Create; + FErrorBarData[i].OnChange := @ChangeErrorBarHandler; + end; +end; + +destructor TCustomChartSource.Destroy; +begin + FErrorBarData[0].Free; + FErrorBarData[1].Free; + inherited; +end; + +procedure TCustomChartSource.ChangeErrorBarHandler(Sender: TObject); +begin + Unused(Sender); + Notify; end; procedure TCustomChartSource.EndUpdate; @@ -683,46 +781,97 @@ end; function TCustomChartSource.Extent: TDoubleRect; var i: Integer; + vhi, vlo: Double; begin if FExtentIsValid then exit(FExtent); FExtent := EmptyExtent; - for i := 0 to Count - 1 do - with Item[i]^ do begin - UpdateMinMax(X, FExtent.a.X, FExtent.b.X); - UpdateMinMax(Y, FExtent.a.Y, FExtent.b.Y); - end; + + if HasXErrorBars then + for i := 0 to Count - 1 do begin + GetXErrorBarLimits(i, vhi, vlo); + UpdateMinMax(vhi, FExtent.a.X, FExtent.b.X); + UpdateMinMax(vlo, FExtent.a.X, FExtent.b.X); + end + else + for i:=0 to Count - 1 do + UpdateMinMax(Item[i]^.X, FExtent.a.X, FExtent.b.X); + + if HasYErrorBars then + for i := 0 to Count - 1 do begin + GetYErrorBarLimits(i, vhi, vlo); + UpdateMinMax(vhi, FExtent.a.Y, FExtent.b.Y); + UpdateMinMax(vlo, FExtent.a.Y, FExtent.b.Y); + end + else + for i:=0 to Count - 1 do + UpdateMinMax(Item[i]^.Y, FExtent.a.Y, FExtent.b.Y); + FExtentIsValid := true; Result := FExtent; end; +{ Calculates the extent of multiple y values stacked onto each other. } function TCustomChartSource.ExtentCumulative: TDoubleRect; var h: Double; i, j: Integer; + jyp: Integer = -1; + jyn: Integer = -1; begin Result := Extent; if YCount < 2 then exit; + + // Skip the y values used for error bars in calculating the cumulative sum. + if YErrorBarData.Kind = ebkChartSource then begin + jyp := round(YErrorBarData.PosDelta) - 1; // -1 because YList index is offset by 1 + jyn := round(YErrorBarData.NegDelta) - 1; + end; + for i := 0 to Count - 1 do begin h := NumberOr(Item[i]^.Y); - for j := 0 to YCount - 2 do begin - h += NumberOr(Item[i]^.YList[j]); - // If some of Y values are negative, h may be non-monotonic. - UpdateMinMax(h, Result.a.Y, Result.b.Y); - end; + for j := 0 to YCount - 2 do + if (j <> jyp) and (j <> jyn) then begin + h += NumberOr(Item[i]^.YList[j]); + // If some of the Y values are negative, h may be non-monotonic. + UpdateMinMax(h, Result.a.Y, Result.b.Y); + end; end; end; +{ Calculates the extent including multiple x and y values (non-stacked) } function TCustomChartSource.ExtentList: TDoubleRect; var i, j: Integer; + jxp, jxn: Integer; + jyp, jyn: Integer; begin Result := Extent; + if (YCount < 2) and (XCount < 2) then exit; + + // Skip then x and y values used for error bars when calculating the list extent. + if XErrorBarData.Kind = ebkChartSource then begin + jxp := round(XErrorBarData.PosDelta) - 1; // -1 because XList is offset by 1 + jxn := round(XErrorBarData.NegDelta) - 1; + end else begin + jxp := -1; + jyp := -1; + end; + if YErrorBarData.Kind = ebkChartSource then begin + jyp := round(YErrorbarData.PosDelta) - 1; // -1 because YList is offset by 1 + jyn := round(YErrorBarData.NegDelta) - 1; + end else begin + jyp := -1; + jyn := -1; + end; + for i := 0 to Count - 1 do with Item[i]^ do begin for j := 0 to High(XList) do - UpdateMinMax(XList[j], Result.a.X, Result.b.X); + if (j <> jxp) and (j <> jxn) then + UpdateMinMax(XList[j], Result.a.X, Result.b.X); for j := 0 to High(YList) do - UpdateMinMax(YList[j], Result.a.Y, Result.b.Y); + if (j <> jyp) and (j <> jyn) then + UpdateMinMax(YList[j], Result.a.Y, Result.b.Y); end; end; @@ -805,17 +954,140 @@ begin Result := TCustomChartSourceEnumerator.Create(Self); end; +function TCustomChartSource.GetErrorbarData(AIndex: Integer): TChartErrorBarData; +begin + Result := FErrorBarData[AIndex]; +end; + +{ Returns the error bar limits in positive and negative direction for the + x (which = 0) or y (which = 1) coordinates of the data point at the specified + index. The result is false if there is no error bar. } +function TCustomChartSource.GetErrorBarLimits(APointIndex: Integer; + Which: Integer; out AUpperLimit, ALowerLimit: Double): Boolean; +var + v: Double; + deltaP, deltaN: Double; +begin + Result := false; + + if Which = 0 then + v := Item[APointIndex]^.X + else + v := Item[APointIndex]^.Y; + + AUpperLimit := v; + ALowerLimit := v; + + if IsNaN(v) then + exit; + + if Assigned(FErrorBarData[Which]) then begin + case FErrorBarData[Which].Kind of + ebkNone: + exit; + ebkConst: + begin + deltaP := FErrorBarData[Which].PosDelta; + if FErrorBarData[Which].NegDelta = -1 then + deltaN := deltaP + else + deltaN := FErrorBarData[Which].NegDelta; + end; + ebkPercent: + begin + deltaP := v * FErrorBarData[Which].PosDelta * PERCENT; + if FErrorBarData[Which].NegDelta = -1 then + deltaN := deltaP + else + deltaN := v * FErrorBarData[Which].NegDelta * PERCENT; + end; + ebkChartSource: + begin + if Which = 0 then + deltaP := Item[APointIndex]^.GetX(round(FErrorbarData[0].PosDelta)) + else + deltaP := Item[APointIndex]^.GetY(round(FErrorBarData[1].PosDelta)); + if FErrorBarData[Which].NegDelta = -1 then + deltaN := deltaP + else + if Which = 0 then + deltaN := Item[APointIndex]^.GetX(round(FErrorBarData[Which].NegDelta)) + else + deltaN := Item[APointIndex]^.GetY(round(FErrorBarData[Which].NegDelta)); + end; + end; + AUpperLimit := v + abs(deltaP); + ALowerLimit := v - abs(deltaN); + Result := true; + end; +end; + +function TCustomChartSource.GetXErrorBarLimits(APointIndex: Integer; + out AUpperLimit, ALowerLimit: Double): Boolean; +begin + Result := GetErrorBarLimits(APointIndex, 0, AUpperLimit, ALowerLimit); +end; + +function TCustomChartSource.GetYErrorBarLimits(APointIndex: Integer; + out AUpperLimit, ALowerLimit: Double): Boolean; +begin + Result := GetErrorBarLimits(APointIndex, 1, AUpperLimit, ALowerLimit); +end; + +function TCustomChartSource.GetHasErrorBars(Which: Integer): Boolean; +begin + Result := false; + if Assigned(FErrorBarData[Which]) then + case FErrorBarData[Which].Kind of + ebkNone: + ; + ebkConst, ebkPercent: + Result := (FErrorBarData[Which].PosDelta > 0); + ebkChartSource: + Result := (FErrorBarData[Which].PosDelta > -1); + end; +end; + +function TCustomChartSource.HasXErrorBars: Boolean; +begin + Result := GetHasErrorBars(0); +end; + +function TCustomChartSource.HasYErrorBars: Boolean; +begin + Result := GetHasErrorBars(1); +end; + procedure TCustomChartSource.InvalidateCaches; begin FExtentIsValid := false; FValuesTotalIsValid := false; end; +function TCustomChartSource.IsXErrorIndex(AXIndex: Integer): Boolean; +begin + Result := (XErrorBarData.Kind = ebkChartSource) and + ((round(XErrorBarData.PosDelta) = AXIndex) or (round(XErrorBarData.NegDelta) = AXIndex)); +end; + +function TCustomChartSource.IsYErrorIndex(AYIndex: Integer): Boolean; +begin + Result := (YErrorBarData.Kind = ebkChartSource) and + ((round(YErrorBarData.PosDelta) = AYIndex) or (round(YErrorBarData.NegDelta) = AYIndex)); +end; + function TCustomChartSource.IsSorted: Boolean; begin Result := false; end; +procedure TCustomChartSource.SetErrorbarData(AIndex: Integer; + AValue: TChartErrorBarData); +begin + FErrorbarData[AIndex] := AValue; + Notify; +end; + procedure TCustomChartSource.SortValuesInRange( var AValues: TChartValueTextArray; AStart, AEnd: Integer); var diff --git a/components/tachart/tafuncseries.pas b/components/tachart/tafuncseries.pas index c1b5fda882..3828a2c8e3 100644 --- a/components/tachart/tafuncseries.pas +++ b/components/tachart/tafuncseries.pas @@ -183,6 +183,8 @@ type property Step: TFuncSeriesStep read FStep write SetStep default DEF_SPLINE_STEP; property ToolTargets default [nptPoint, nptCustom]; + property XErrorBars; + property YErrorBars; property OnCustomDrawPointer; property OnGetPointerStyle; end; @@ -271,6 +273,8 @@ type read FSplineType write SetSplineType default cstNatural; property Step: TFuncSeriesStep read FStep write SetStep default DEF_SPLINE_STEP; + property XErrorBars; + property YErrorBars; end; TFitParamsState = (fpsUnknown, fpsInvalid, fpsValid); @@ -338,8 +342,10 @@ type property Pen: TChartPen read FPen write SetPen; property Pointer; property Source; - property ToolTargets default [nptPoint, nptCustom]; property Step: TFuncSeriesStep read FStep write SetStep default DEF_FIT_STEP; + property ToolTargets default [nptPoint, nptCustom]; + property XErrorBars; + property YErrorBars; property OnCalcGoodnessOfFit: TCalcGoodnessOfFitEvent read FOnCalcGoodnessOfFit write FOnCalcGoodnessOfFit; property OnCustomDrawPointer; @@ -1041,6 +1047,7 @@ begin for startIndex := splineStart to splineEnd + Degree - 1 do SplineSegment(0.0, 1.0, SplinePoint(0.0), SplinePoint(1.0)); end; + DrawErrorBars(ADrawer); DrawLabels(ADrawer); DrawPointers(ADrawer); end; @@ -1290,6 +1297,7 @@ begin if not s.IsFewPoints then DrawSpline(s); + DrawErrorBars(ADrawer); DrawLabels(ADrawer); DrawPointers(ADrawer); end; @@ -1570,6 +1578,7 @@ begin Free; end; DrawLabels(ADrawer); + DrawErrorBars(ADrawer); DrawPointers(ADrawer); finally de.Free; diff --git a/components/tachart/taseries.pas b/components/tachart/taseries.pas index c91784f651..fa173bcc41 100644 --- a/components/tachart/taseries.pas +++ b/components/tachart/taseries.pas @@ -243,6 +243,8 @@ type property Styles; property ToolTargets; property UseReticule default true; + property XErrorBars; + property YErrorBars; // Events property OnDrawPointer: TSeriesPointerDrawEvent read FOnDrawPointer write FOnDrawPointer; deprecated 'Use OnCustomDrawPointer'; @@ -426,6 +428,7 @@ begin PrepareGraphPoints(ext, LineType <> ltFromOrigin); DrawSingleLineInStack(ADrawer, 0); for i := 0 to Source.YCount - 2 do begin + if Source.IsYErrorIndex(i+1) then Continue; UpdateGraphPoints(i, FStacked); DrawSingleLineInStack(ADrawer, i + 1); end; @@ -663,6 +666,7 @@ begin else DrawColoredLines; end; + DrawErrorBars(ADrawer); DrawLabels(ADrawer); if ShowPoints then DrawPointers(ADrawer, AIndex, FColorEach in [cePoint, cePointAndLineBefore, cePointAndLineAfter]); diff --git a/components/tachart/tasources.pas b/components/tachart/tasources.pas index 3f62f7cceb..a3c80bd987 100644 --- a/components/tachart/tasources.pas +++ b/components/tachart/tasources.pas @@ -68,6 +68,8 @@ type published property DataPoints: TStrings read FDataPoints write SetDataPoints; property Sorted: Boolean read FSorted write SetSorted default false; + property XErrorBarData; + property YErrorBarData; property YCount; end; @@ -135,6 +137,8 @@ type property YMax: Double read FYMax write SetYMax; property YMin: Double read FYMin write SetYMin; property YNanPercent: TPercent read FYNanPercent write SetYNanPercent default 0; + property XErrorBarData; + property YErrorBarData; end; TUserDefinedChartSource = class; diff --git a/components/tachart/tatypes.pas b/components/tachart/tatypes.pas index 1276306221..45c50f4b43 100644 --- a/components/tachart/tatypes.pas +++ b/components/tachart/tatypes.pas @@ -268,6 +268,22 @@ type property Visible default false; end; + TChartErrorBar = class(TChartElement) + private + FWidth: Integer; + FPen: TPen; + procedure SetPen(const AValue: TPen); + procedure SetWidth(const AValue: Integer); + public + constructor Create(AOwner: TCustomChart); + destructor Destroy; override; + procedure Assign(ASource: TPersistent); override; + published + property Pen: TPen read FPen write SetPen; + property Visible default false; + property Width: Integer read FWidth write SetWidth default -1; + end; + implementation uses @@ -823,5 +839,44 @@ begin StyleChanged(Self); end; +{ TChartErrorBar } + +constructor TChartErrorBar.Create(AOwner: TCustomChart); +begin + inherited Create(AOwner); + FWidth := -1; // -1 = same width as series pointer + FPen := TPen.Create; + FPen.OnChange := @StyleChanged; + FVisible := false; +end; + +destructor TChartErrorBar.Destroy; +begin + FPen.Free; + inherited Destroy; +end; + +procedure TChartErrorBar.Assign(ASource: TPersistent); +begin + if ASource is TChartErrorBar then begin + FPen.Assign(TChartErrorBar(ASource).Pen); + FWidth := TChartErrorBar(ASource).Width; + end; + inherited; +end; + +procedure TChartErrorBar.SetPen(const AValue: TPen); +begin + FPen.Assign(AValue); + StyleChanged(Self); +end; + +procedure TChartErrorBar.SetWidth(const AValue: Integer); +begin + if FWidth = AValue then exit; + FWidth := AValue; + StyleChanged(self); +end; + end.