unit main; {$mode objfpc}{$H+} interface uses Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, ComCtrls, ExtCtrls, StdCtrls, Types, TAGraph, TASeries, TATools, TAStyles, TASources, TAChartUtils, TAMultiSeries, TARadialSeries, TAFuncSeries, TACustomSeries; type { TMainForm } TMainForm = class(TForm) Chart: TChart; BarSeries: TBarSeries; AreaSeries: TAreaSeries; CbCandleStick: TCheckBox; BoxWhiskerSeries: TBoxAndWhiskerSeries; BubbleSeries: TBubbleSeries; BSplineSeries: TBSplineSeries; PieSeriesDatapointDragTool: TDataPointDragTool; PieSeries: TPieSeries; FitSeries: TFitSeries; FieldSeries: TFieldSeries; FuncSeries: TFuncSeries; ConstantLineSeries: TConstantLine; CubicSplineSeries: TCubicSplineSeries; ClickTool: TDataPointClickTool; FieldSeriesDatapointDragTool: TDataPointDragTool; PolarSeries: TPolarSeries; OHLCSeries: TOpenHighLowCloseSeries; ChartStyles: TChartStyles; CbDragXY: TCheckBox; DatapointInfo: TLabel; LblNOTE: TLabel; LblUSAGE: TLabel; ListChartSource: TListChartSource; CrosshairTool: TDataPointCrosshairTool; HintTool: TDataPointHintTool; BubbleSeriesDatapointDragTool: TDataPointDragTool; Panel1: TPanel; Toolset: TChartToolset; LineSeries: TLineSeries; DataPointDragTool: TDataPointDragTool; TabControl: TTabControl; procedure CbCandleStickChange(Sender: TObject); procedure CbDragXYChange(Sender: TObject); procedure ClickToolPointClick(ATool: TChartTool; {%H-}APoint: TPoint); procedure CrosshairToolAfterKeyUp(ATool: TChartTool; {%H-}APoint: TPoint); procedure CrosshairToolAfterMouseUp(ATool: TChartTool; {%H-}APoint: TPoint); procedure CrosshairToolDraw(ASender: TDataPointDrawTool); procedure FormCreate(Sender: TObject); procedure DataPointDragToolDragXY(ASender: TDataPointDragTool; var AGraphPoint: TDoublePoint); procedure DataPointDragToolDragY(ASender: TDataPointDragTool; var AGraphPoint: TDoublePoint); procedure FuncSeriesCalculate(const AX: Double; out AY: Double); procedure HintToolHint(ATool: TDataPointHintTool; const {%H-}APoint: TPoint; var AHint: String); procedure TabControlChange(Sender: TObject); private function GetDatapointInfo(ATool: TDatapointTool): String; public end; var MainForm: TMainForm; implementation {$R *.lfm} uses TALegend; { TMainForm } procedure TMainForm.CbCandleStickChange(Sender: TObject); begin if CbCandleStick.Checked then OHLCSeries.Mode := mCandleStick else OHLCSeries.Mode := mOHLC; end; procedure TMainForm.CbDragXYChange(Sender: TObject); begin if CbDragXY.Checked then begin DataPointDragTool.OnDrag := @DatapointDragToolDragXY; BubbleSeriesDataPointDragTool.OnDrag := nil; end else begin DataPointDragTool.OnDrag := @DatapointDragToolDragY; BubbleSeriesdataPointDragTool.OnDrag := @DatapointDragToolDragY; end; end; procedure TMainForm.ClickToolPointClick(ATool: TChartTool; APoint: TPoint); begin DatapointInfo.Caption := ''; ShowMessage(GetDatapointInfo(TDatapointTool(ATool))); end; procedure TMainForm.CrosshairToolAfterKeyUp(ATool: TChartTool; APoint: TPoint); begin TDatapointCrosshairTool(ATool).Hide; end; procedure TMainForm.CrosshairToolAfterMouseUp(ATool: TChartTool; APoint: TPoint); begin TDatapointCrosshairTool(ATool).Hide; end; procedure TMainForm.CrosshairToolDraw(ASender: TDataPointDrawTool); begin DatapointInfo.Caption := GetDatapointInfo(ASender); end; // Avoids changing of the x value while dragging a data point procedure TMainForm.DataPointDragToolDragY(ASender: TDataPointDragTool; var AGraphPoint: TDoublePoint); begin AGraphPoint.X := ASender.Origin.X; end; // Modifies the x value if one of the levels of a multi-y-value series is dragged. // e.g, stacked series, ohlc, box-whisker. procedure TMainForm.DataPointDragToolDragXY(ASender: TDataPointDragTool; var AGraphPoint: TDoublePoint); var ser: TChartSeries; begin if (ASender.YIndex > 0) and (ASender.Series is TChartSeries) then begin ser := TChartSeries(ASender.Series); ser.XValue[ASender.PointIndex] := AGraphPoint.X; end; end; procedure TMainForm.FormCreate(Sender: TObject); const N = 10; var i: Integer; x, x2, y1, y2, y3,yO, yH, yL, yC: Double; begin ListChartSource.YCount := 2; ListChartSource.Sorted := true; for i:=0 to N-1 do begin x := i + (Random - 0.5) * 0.4; y1 := x + Random*2; y2 := y1 + Random*2; ListChartSource.AddXYList(x, [y1, y2]); end; ListChartSource.Sorted := false; OHLCSeries.ListSource.YCount := 4; OHLCSeries.ListSource.Sorted := true; for i:=0 to N-1 do begin x := i + Random; yL := x + Random; yH := yL + Random + 5; yO := yL + Random * 5; yC := yL + Random * 5; OHLCSeries.AddXOHLC(x, yO, yH, yL, yC); end; OHLCSeries.ListSource.Sorted := false; BoxWhiskerSeries.ListSource.YCount := 5; BoxWhiskerSeries.ListSource.Sorted := true; for i:=0 to N-1 do begin x := i + (Random-0.5)*0.4; yL := x * Random; y1 := yL + Random; y2 := y1 + Random; y3 := y2 + Random; yH := y3 + Random; BoxWhiskerSeries.AddXY(x, yL, y1, y2, y3, yH); end; BoxWhiskerSeries.ListSource.Sorted := true; BubbleSeries.ListSource.YCount := 3; for i:=0 to N-1 do begin x := (Random - 0.5) * 2.0; y1 := (Random - 0.5) * 2.0; y2 := Random*0.3; BubbleSeries.AddXY(x, y1, y2); end; FitSeries.Clear; for i:= 0 to N - 1 do begin x := Random * 10; y1 := x + random * 1; FitSeries.AddXY(x, y1); end; FieldSeries.ListSource.XCount := 2; FieldSeries.ListSource.YCount := 2; for i:=0 to N-1 do begin x := Random * 10; x2 := x + Random; y1 := Random * 10; y2 := y1 + Random; FieldSeries.AddVector(x, y1, x2, y2); end; PieSeries.ListSource.Clear; for i:=0 to 4 do begin y1 := Random*10; PieSeries.AddXY(0, y1, 'Item ' + IntToStr(i+1)); end; PieSeries.Legend.Multiplicity := lmPoint; PieSeries.Marks.Style := smsLabel; PieSeries.Exploded := true; LineSeries.Index := 0; BarSeries.Index := 1; AreaSeries.Index := 2; PolarSeries.Index := 3; CubicSplineSeries.Index := 4; BSplineSeries.Index := 5; FitSeries.Index := 6; OHLCSeries.Index := 7; BoxWhiskerSeries.Index := 8; BubbleSeries.Index := 9; FieldSeries.Index := 10; FuncSeries.Index := 11; ConstantLineSeries.Index := 12; PieSeries.Index := 13; TabControlChange(nil); DatapointInfo.Caption := ''; end; procedure TMainForm.FuncSeriesCalculate(const AX: Double; out AY: Double); begin AY := sin(AX); end; function TMainForm.GetDataPointInfo(ATool: TDataPointTool): String; var ser: TChartSeries; begin Result := ''; if ATool.Series is TConstantLine then Result := Format('"%s": Position %.2f', [ TConstantLine(ATool.Series).Title, TConstantLine(ATool.Series).Position ]) else if ATool.Series is TFuncSeries then Result := Format('"%s": x = %.2f, y = %,2f', [ TFuncSeries(ATool.Series).Title, ATool.NearestGraphPoint.X, ATool.NearestGraphPoint.Y ]) else if ATool.Series is TChartSeries then begin ser := TChartSeries(ATool.Series); if ser <> nil then begin if (ATool.YIndex = -1) and ((ser is TFitSeries) or (ser is TCubicSplineSeries) or (ser is TBSplineSeries)) then Result := Format('"%s": x = %.2f, y = %.2f', [ ser.Title, ATool.NearestGraphPoint.X, ATool.NearestGraphPoint.Y ]) else if (ATool.PointIndex > -1) and (ser.Source.YCount = 1) then Result := Format('"%s": Point index %d, x = %.2f, y = %.2f', [ ser.Title, ATool.PointIndex, ser.XValue[ATool.PointIndex], ser.YValue[ATool.PointIndex] ]) else if (ATool.PointIndex > -1) and (ser.Source.YCount > 1) and (ATool.YIndex > -1) then Result := Format('"%s": Point index %d, x = %.2f, y = %.2f (y index %d)', [ ser.Title, ATool.PointIndex, ser.XValue[ATool.PointIndex], ser.YValues[ATool.PointIndex, ATool.YIndex], ATool.YIndex ]) else if (ATool.PointIndex = -1) and (ser.Source.YCount > 1) and (ATool.YIndex > -1) then Result := Format('"%s": on segment, x = %.2f, y = %.2f (y index %d)', [ ser.Title, ser.GraphToAxisX(ATool.NearestGraphPoint.X), ser.GraphToAxisY(ATool.NearestGraphPoint.Y), ATool.YIndex ]); end; end; end; procedure TMainForm.HintToolHint(ATool: TDataPointHintTool; const APoint: TPoint; var AHint: String); begin AHint := GetDatapointInfo(ATool); DatapointInfo.Caption := ''; end; procedure TMainForm.TabControlChange(Sender: TObject); var ser: TBasicChartSeries; s: String; begin // Show only the series of the corresponding tab for ser in Chart.Series do ser.Active := ser.Index = TabControl.TabIndex; // Avoid tools react on hidden series s := IntToStr(TabControl.TabIndex); CrosshairTool.AffectedSeries := s; HintTool.AffectedSeries := s; BubbleSeriesDatapointDragTool.Enabled := TabControl.TabIndex = BubbleSeries.Index; FieldSeriesDatapointDragTool.Enabled := TabControl.TabIndex = FieldSeries.Index; PieSeriesDatapointDragTool.Enabled := TabControl.TabIndex = PieSeries.Index; DataPointDragTool.Enabled := not (BubbleSeriesDatapointDragTool.Enabled or FieldSeriesDatapointDragtool.Enabled); if BubbleSeriesDatapointDragTool.Enabled then BubbleSeriesDatapointDragTool.AffectedSeries := s else if FieldSeriesDatapointDragTool.Enabled then FieldSeriesDatapointDragTool.AffectedSeries := s else if PieSeriesDatapointDragTool.Enabled then PieSeriesDatapointDragtool.AffectedSeries := s else DatapointDragtool.AffectedSeries := s; // Treatment of special requirements of particular series CbCandleStick.Visible := OHLCSeries.Active; LblNOTE.Visible := LineSeries.Active or BarSeries.Active or AreaSeries.Active; Chart.LeftAxis.Range.UseMax := ConstantLineSeries.Active; Chart.LeftAxis.Range.UseMin := ConstantLineSeries.Active; if PieSeries.Active then LblUsage.Caption := StringReplace(LblUsage.Caption, 'datapoint', 'pie', []) else LblUsage.Caption := StringReplace(LblUsage.Caption, 'pie', 'datapoint', []); s := ''; if LineSeries.Active or BarSeries.Active or AreaSeries.Active then begin s := 'These are stacked series, i.e. they share the same x ' + 'value defined by the red series --> When you drag a datapoint of any ' + 'series the x value of the other series will follow.'; if BarSeries.Active then s := s + LineEnding + 'Grab the bars at their upper ends.'; end else if FieldSeries.Active then s := 'Rotate an arrow by grabbing it at its ends.' + LineEnding + 'Shift it by grabbing it in the middle.' + LineEnding + 'The checkbox "Drag both x and y" is not operating here.' else if BubbleSeries.Active then s := 'Grab a bubble at its perimeter to change the radius.' + LineEnding + 'Grab it in the center to change its location.' + LineEnding + 'Overlapping bubbles may be detected erroneously.'; if s <> '' then LblNOTE.Caption := 'NOTE:' + LineEnding + s; LblNOTE.Visible := (s <> ''); CbDragXY.Enabled := not PieSeries.Active; end; end.