Chart panels
This commit is contained in:
@@ -13,7 +13,8 @@ uses
|
|||||||
FirstStrategy in 'FirstStrategy.pas',
|
FirstStrategy in 'FirstStrategy.pas',
|
||||||
Myc.Trade.DataArray in '..\Src\Myc.Trade.DataArray.pas',
|
Myc.Trade.DataArray in '..\Src\Myc.Trade.DataArray.pas',
|
||||||
Myc.FMX.Chart.Series in '..\Src\Myc.FMX.Chart.Series.pas',
|
Myc.FMX.Chart.Series in '..\Src\Myc.FMX.Chart.Series.pas',
|
||||||
Myc.Trade.Indicators in '..\Src\Myc.Trade.Indicators.pas';
|
Myc.Trade.Indicators in '..\Src\Myc.Trade.Indicators.pas',
|
||||||
|
Myc.Trade.Types in '..\Src\Myc.Trade.Types.pas';
|
||||||
|
|
||||||
{$R *.res}
|
{$R *.res}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<ProjectVersion>20.3</ProjectVersion>
|
<ProjectVersion>20.3</ProjectVersion>
|
||||||
<FrameworkType>FMX</FrameworkType>
|
<FrameworkType>FMX</FrameworkType>
|
||||||
<Base>True</Base>
|
<Base>True</Base>
|
||||||
<Config Condition="'$(Config)'==''">Release</Config>
|
<Config Condition="'$(Config)'==''">Debug</Config>
|
||||||
<Platform Condition="'$(Platform)'==''">Win64</Platform>
|
<Platform Condition="'$(Platform)'==''">Win64</Platform>
|
||||||
<ProjectName Condition="'$(ProjectName)'==''">AuraTrader</ProjectName>
|
<ProjectName Condition="'$(ProjectName)'==''">AuraTrader</ProjectName>
|
||||||
<TargetedPlatforms>3</TargetedPlatforms>
|
<TargetedPlatforms>3</TargetedPlatforms>
|
||||||
@@ -142,6 +142,7 @@
|
|||||||
<DCCReference Include="..\Src\Myc.Trade.DataArray.pas"/>
|
<DCCReference Include="..\Src\Myc.Trade.DataArray.pas"/>
|
||||||
<DCCReference Include="..\Src\Myc.FMX.Chart.Series.pas"/>
|
<DCCReference Include="..\Src\Myc.FMX.Chart.Series.pas"/>
|
||||||
<DCCReference Include="..\Src\Myc.Trade.Indicators.pas"/>
|
<DCCReference Include="..\Src\Myc.Trade.Indicators.pas"/>
|
||||||
|
<DCCReference Include="..\Src\Myc.Trade.Types.pas"/>
|
||||||
<BuildConfiguration Include="Base">
|
<BuildConfiguration Include="Base">
|
||||||
<Key>Base</Key>
|
<Key>Base</Key>
|
||||||
</BuildConfiguration>
|
</BuildConfiguration>
|
||||||
@@ -185,6 +186,12 @@
|
|||||||
<Overwrite>true</Overwrite>
|
<Overwrite>true</Overwrite>
|
||||||
</Platform>
|
</Platform>
|
||||||
</DeployFile>
|
</DeployFile>
|
||||||
|
<DeployFile LocalName="Win64\Debug\AuraTrader.rsm" Configuration="Debug" Class="DebugSymbols">
|
||||||
|
<Platform Name="Win64">
|
||||||
|
<RemoteName>AuraTrader.rsm</RemoteName>
|
||||||
|
<Overwrite>true</Overwrite>
|
||||||
|
</Platform>
|
||||||
|
</DeployFile>
|
||||||
<DeployFile LocalName="Win64\Release\AuraTrader.exe" Configuration="Release" Class="ProjectOutput">
|
<DeployFile LocalName="Win64\Release\AuraTrader.exe" Configuration="Release" Class="ProjectOutput">
|
||||||
<Platform Name="Win64">
|
<Platform Name="Win64">
|
||||||
<RemoteName>AuraTrader.exe</RemoteName>
|
<RemoteName>AuraTrader.exe</RemoteName>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ uses
|
|||||||
Myc.Signals,
|
Myc.Signals,
|
||||||
Myc.Lazy,
|
Myc.Lazy,
|
||||||
Myc.TaskManager,
|
Myc.TaskManager,
|
||||||
|
Myc.Trade.Types,
|
||||||
Myc.Trade.DataPoint,
|
Myc.Trade.DataPoint,
|
||||||
Myc.Trade.DataArray;
|
Myc.Trade.DataArray;
|
||||||
|
|
||||||
|
|||||||
+10
-13
@@ -27,6 +27,7 @@ uses
|
|||||||
FMX.Memo,
|
FMX.Memo,
|
||||||
FMX.Objects,
|
FMX.Objects,
|
||||||
Myc.Futures,
|
Myc.Futures,
|
||||||
|
Myc.Trade.Types,
|
||||||
Myc.Trade.DataStream,
|
Myc.Trade.DataStream,
|
||||||
Myc.Trade.DataPoint,
|
Myc.Trade.DataPoint,
|
||||||
Myc.Trade.DataProvider,
|
Myc.Trade.DataProvider,
|
||||||
@@ -183,17 +184,17 @@ begin
|
|||||||
|
|
||||||
var Hull: IMycConverter<Double, Double> := TGenericIndicator<Double, Double>.Create(TIndicators.CreateHMA(150));
|
var Hull: IMycConverter<Double, Double> := TGenericIndicator<Double, Double>.Create(TIndicators.CreateHMA(150));
|
||||||
Closes.Sender.Link(Hull);
|
Closes.Sender.Link(Hull);
|
||||||
chart.AddDoubleSeries(Hull.Sender, TAlphaColors.Aliceblue);
|
chart.Panels[0].AddDoubleSeries(Hull.Sender, TAlphaColors.Aliceblue);
|
||||||
|
|
||||||
// Add SMA (Simple Moving Average)
|
// Add SMA (Simple Moving Average)
|
||||||
var Sma: IMycConverter<Double, Double> := TGenericIndicator<Double, Double>.Create(TIndicators.CreateSMA(50));
|
var Sma: IMycConverter<Double, Double> := TGenericIndicator<Double, Double>.Create(TIndicators.CreateSMA(50));
|
||||||
Closes.Sender.Link(Sma);
|
Closes.Sender.Link(Sma);
|
||||||
chart.AddDoubleSeries(Sma.Sender, TAlphaColors.Yellow);
|
chart.Panels[0].AddDoubleSeries(Sma.Sender, TAlphaColors.Yellow);
|
||||||
|
|
||||||
// Add EMA (Exponential Moving Average)
|
// Add EMA (Exponential Moving Average)
|
||||||
var Ema: IMycConverter<Double, Double> := TGenericIndicator<Double, Double>.Create(TIndicators.CreateEMA(21));
|
var Ema: IMycConverter<Double, Double> := TGenericIndicator<Double, Double>.Create(TIndicators.CreateEMA(21));
|
||||||
Closes.Sender.Link(Ema);
|
Closes.Sender.Link(Ema);
|
||||||
chart.AddDoubleSeries(Ema.Sender, TAlphaColors.Aqua);
|
chart.Panels[0].AddDoubleSeries(Ema.Sender, TAlphaColors.Aqua);
|
||||||
|
|
||||||
// Add Bollinger Bands (20, 2.0)
|
// Add Bollinger Bands (20, 2.0)
|
||||||
var Boli: IMycConverter<Double, TBollingerBandsResult> :=
|
var Boli: IMycConverter<Double, TBollingerBandsResult> :=
|
||||||
@@ -204,30 +205,26 @@ begin
|
|||||||
TMycGenericConverter<TBollingerBandsResult, Double>
|
TMycGenericConverter<TBollingerBandsResult, Double>
|
||||||
.Create(function(const Item: TBollingerBandsResult): Double begin Result := Item.UpperBand; end);
|
.Create(function(const Item: TBollingerBandsResult): Double begin Result := Item.UpperBand; end);
|
||||||
Boli.Sender.Link(BoliUpper);
|
Boli.Sender.Link(BoliUpper);
|
||||||
chart.AddDoubleSeries(BoliUpper.Sender, TAlphaColors.Gray);
|
chart.Panels[0].AddDoubleSeries(BoliUpper.Sender, TAlphaColors.Gray);
|
||||||
|
|
||||||
var BoliMiddle: IMycConverter<TBollingerBandsResult, Double> :=
|
var BoliMiddle: IMycConverter<TBollingerBandsResult, Double> :=
|
||||||
TMycGenericConverter<TBollingerBandsResult, Double>
|
TMycGenericConverter<TBollingerBandsResult, Double>
|
||||||
.Create(function(const Item: TBollingerBandsResult): Double begin Result := Item.MiddleBand; end);
|
.Create(function(const Item: TBollingerBandsResult): Double begin Result := Item.MiddleBand; end);
|
||||||
Boli.Sender.Link(BoliMiddle);
|
Boli.Sender.Link(BoliMiddle);
|
||||||
chart.AddDoubleSeries(BoliMiddle.Sender, TAlphaColors.Darkgray, 1.0);
|
chart.Panels[0].AddDoubleSeries(BoliMiddle.Sender, TAlphaColors.Darkgray, 1.0);
|
||||||
|
|
||||||
var BoliLower: IMycConverter<TBollingerBandsResult, Double> :=
|
var BoliLower: IMycConverter<TBollingerBandsResult, Double> :=
|
||||||
TMycGenericConverter<TBollingerBandsResult, Double>
|
TMycGenericConverter<TBollingerBandsResult, Double>
|
||||||
.Create(function(const Item: TBollingerBandsResult): Double begin Result := Item.LowerBand; end);
|
.Create(function(const Item: TBollingerBandsResult): Double begin Result := Item.LowerBand; end);
|
||||||
Boli.Sender.Link(BoliLower);
|
Boli.Sender.Link(BoliLower);
|
||||||
chart.AddDoubleSeries(BoliLower.Sender, TAlphaColors.Gray);
|
chart.Panels[0].AddDoubleSeries(BoliLower.Sender, TAlphaColors.Gray);
|
||||||
|
|
||||||
var rsiChart := TMycChart.Create(Self);
|
chart.AddPanel;
|
||||||
AlignControl(rsiChart);
|
|
||||||
rsiChart.Height := Layout.ChildrenRect.Width * 9 / 32;
|
|
||||||
rsiChart.Lookback.Value := 50000;
|
|
||||||
rsiChart.SetXAxisSeries<TDateTime>(Timestamps.Sender);
|
|
||||||
|
|
||||||
// Add RSI (Relative Strength Index)
|
// Add RSI (Relative Strength Index)
|
||||||
var Rsi: IMycConverter<Double, Double> := TGenericIndicator<Double, Double>.Create(TIndicators.CreateRSI(14));
|
var Rsi: IMycConverter<Double, Double> := TGenericIndicator<Double, Double>.Create(TIndicators.CreateRSI(14));
|
||||||
Closes.Sender.Link(Rsi);
|
Closes.Sender.Link(Rsi);
|
||||||
rsiChart.AddDoubleSeries(Rsi.Sender, TAlphaColors.Fuchsia);
|
chart.Panels[1].AddDoubleSeries(Rsi.Sender, TAlphaColors.Fuchsia);
|
||||||
{
|
{
|
||||||
// Add MACD (12, 26, 9)
|
// Add MACD (12, 26, 9)
|
||||||
var Macd: IMycConverter<Double, TMacdResult> := TGenericIndicator<Double, TMacdResult>.Create(TIndicators.CreateMACD(12, 26, 9));
|
var Macd: IMycConverter<Double, TMacdResult> := TGenericIndicator<Double, TMacdResult>.Create(TIndicators.CreateMACD(12, 26, 9));
|
||||||
@@ -260,7 +257,7 @@ begin
|
|||||||
OhlcPoint.Sender.Link(TimeStamps);
|
OhlcPoint.Sender.Link(TimeStamps);
|
||||||
|
|
||||||
chart.SetXAxisSeries<TDateTime>(Timestamps.Sender);
|
chart.SetXAxisSeries<TDateTime>(Timestamps.Sender);
|
||||||
chart.AddOhlcSeries(Ohlc.Sender);
|
chart.Panels[0].AddOhlcSeries(Ohlc.Sender);
|
||||||
|
|
||||||
var done := ExecuteStrategy(Symbol, OhlcPoint);
|
var done := ExecuteStrategy(Symbol, OhlcPoint);
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ uses
|
|||||||
FMX.Graphics,
|
FMX.Graphics,
|
||||||
Myc.Signals,
|
Myc.Signals,
|
||||||
Myc.Lazy,
|
Myc.Lazy,
|
||||||
|
Myc.Trade.Types,
|
||||||
Myc.Trade.DataArray,
|
Myc.Trade.DataArray,
|
||||||
Myc.Trade.DataPoint,
|
Myc.Trade.DataPoint,
|
||||||
Myc.Fmx.Chart;
|
Myc.Fmx.Chart;
|
||||||
@@ -66,7 +67,7 @@ type
|
|||||||
procedure Update; override;
|
procedure Update; override;
|
||||||
procedure Paint(const Canvas: TCanvas; First, Last: Int64; const XForm, YForm: TFunc<Double, Single>); override;
|
procedure Paint(const Canvas: TCanvas; First, Last: Int64; const XForm, YForm: TFunc<Double, Single>); override;
|
||||||
public
|
public
|
||||||
constructor Create(AOwner: TMycChart; const ADataProvider: IMycDataProvider<T>);
|
constructor Create(AParent: TMycChart.TPanel; const ADataProvider: IMycDataProvider<T>);
|
||||||
destructor Destroy; override;
|
destructor Destroy; override;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -80,7 +81,7 @@ type
|
|||||||
procedure Paint(const Canvas: TCanvas; First, Last: Int64; const XForm, YForm: TFunc<Double, Single>); override;
|
procedure Paint(const Canvas: TCanvas; First, Last: Int64; const XForm, YForm: TFunc<Double, Single>); override;
|
||||||
public
|
public
|
||||||
constructor Create(
|
constructor Create(
|
||||||
AOwner: TMycChart;
|
AParent: TMycChart.TPanel;
|
||||||
const ADataProvider: IMycDataProvider<TOhlcItem>;
|
const ADataProvider: IMycDataProvider<TOhlcItem>;
|
||||||
const AUpColor, ADownColor: TMutable<TAlphaColor>
|
const AUpColor, ADownColor: TMutable<TAlphaColor>
|
||||||
);
|
);
|
||||||
@@ -96,7 +97,7 @@ type
|
|||||||
procedure Paint(const Canvas: TCanvas; First, Last: Int64; const XForm, YForm: TFunc<Double, Single>); override;
|
procedure Paint(const Canvas: TCanvas; First, Last: Int64; const XForm, YForm: TFunc<Double, Single>); override;
|
||||||
public
|
public
|
||||||
constructor Create(
|
constructor Create(
|
||||||
AOwner: TMycChart;
|
AParent: TMycChart.TPanel;
|
||||||
const ADataProvider: IMycDataProvider<Double>;
|
const ADataProvider: IMycDataProvider<Double>;
|
||||||
const ALineColor: TAlphaColor;
|
const ALineColor: TAlphaColor;
|
||||||
ALineWidth: Single
|
ALineWidth: Single
|
||||||
@@ -176,13 +177,13 @@ end;
|
|||||||
{ TChartLineLayer }
|
{ TChartLineLayer }
|
||||||
|
|
||||||
constructor TChartLineLayer.Create(
|
constructor TChartLineLayer.Create(
|
||||||
AOwner: TMycChart;
|
AParent: TMycChart.TPanel;
|
||||||
const ADataProvider: IMycDataProvider<Double>;
|
const ADataProvider: IMycDataProvider<Double>;
|
||||||
const ALineColor: TAlphaColor;
|
const ALineColor: TAlphaColor;
|
||||||
ALineWidth: Single
|
ALineWidth: Single
|
||||||
);
|
);
|
||||||
begin
|
begin
|
||||||
inherited Create(AOwner, ADataProvider);
|
inherited Create(AParent, ADataProvider);
|
||||||
FLineColor := ALineColor;
|
FLineColor := ALineColor;
|
||||||
FLineWidth := ALineWidth;
|
FLineWidth := ALineWidth;
|
||||||
end;
|
end;
|
||||||
@@ -235,12 +236,12 @@ end;
|
|||||||
{ TChartOhlcLayer }
|
{ TChartOhlcLayer }
|
||||||
|
|
||||||
constructor TChartOhlcLayer.Create(
|
constructor TChartOhlcLayer.Create(
|
||||||
AOwner: TMycChart;
|
AParent: TMycChart.TPanel;
|
||||||
const ADataProvider: IMycDataProvider<TOhlcItem>;
|
const ADataProvider: IMycDataProvider<TOhlcItem>;
|
||||||
const AUpColor, ADownColor: TMutable<TAlphaColor>
|
const AUpColor, ADownColor: TMutable<TAlphaColor>
|
||||||
);
|
);
|
||||||
begin
|
begin
|
||||||
inherited Create(AOwner, ADataProvider);
|
inherited Create(AParent, ADataProvider);
|
||||||
FUpColor := AUpColor;
|
FUpColor := AUpColor;
|
||||||
FDownColor := ADownColor;
|
FDownColor := ADownColor;
|
||||||
|
|
||||||
@@ -302,10 +303,10 @@ end;
|
|||||||
|
|
||||||
{ TChartCustomLayer }
|
{ TChartCustomLayer }
|
||||||
|
|
||||||
constructor TChartCustomLayer<T>.Create(AOwner: TMycChart; const ADataProvider: IMycDataProvider<T>);
|
constructor TChartCustomLayer<T>.Create(AParent: TMycChart.TPanel; const ADataProvider: IMycDataProvider<T>);
|
||||||
begin
|
begin
|
||||||
inherited Create(AOwner);
|
inherited Create(AParent);
|
||||||
FSeries := TChartSeriesProcessor<T>.Create(ADataProvider, AOwner.Lookback.AsMutable);
|
FSeries := TChartSeriesProcessor<T>.Create(ADataProvider, AParent.Owner.Lookback.AsMutable);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
destructor TChartCustomLayer<T>.Destroy;
|
destructor TChartCustomLayer<T>.Destroy;
|
||||||
|
|||||||
+279
-187
@@ -15,6 +15,7 @@ uses
|
|||||||
FMX.Controls,
|
FMX.Controls,
|
||||||
FMX.Graphics,
|
FMX.Graphics,
|
||||||
FMX.Forms,
|
FMX.Forms,
|
||||||
|
Myc.Trade.Types,
|
||||||
Myc.Trade.DataPoint,
|
Myc.Trade.DataPoint,
|
||||||
Myc.Signals,
|
Myc.Signals,
|
||||||
Myc.Lazy;
|
Myc.Lazy;
|
||||||
@@ -23,6 +24,9 @@ type
|
|||||||
TMycChart = class(TStyledControl)
|
TMycChart = class(TStyledControl)
|
||||||
public
|
public
|
||||||
type
|
type
|
||||||
|
TPanel = class;
|
||||||
|
|
||||||
|
// Abstract base for a data series within the chart.
|
||||||
TSeries = class abstract(TObject)
|
TSeries = class abstract(TObject)
|
||||||
protected
|
protected
|
||||||
function GetCount: Int64; virtual; abstract;
|
function GetCount: Int64; virtual; abstract;
|
||||||
@@ -32,25 +36,54 @@ type
|
|||||||
property TotalCount: Int64 read GetTotalCount;
|
property TotalCount: Int64 read GetTotalCount;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
// Abstract base for a drawable layer in a panel.
|
||||||
TLayer = class abstract(TObject)
|
TLayer = class abstract(TObject)
|
||||||
private
|
private
|
||||||
FOwner: TMycChart;
|
FParent: TPanel;
|
||||||
|
function GetOwner: TMycChart;
|
||||||
protected
|
protected
|
||||||
function GetSeries: TSeries; virtual; abstract;
|
function GetSeries: TSeries; virtual; abstract;
|
||||||
procedure Update; virtual; abstract;
|
procedure Update; virtual; abstract;
|
||||||
function GetValueRange(StartIndex, Count: Int64; out Min, Max: Double): Boolean; virtual; abstract;
|
function GetValueRange(First, Last: Int64; out Min, Max: Double): Boolean; virtual; abstract;
|
||||||
// The signature is changed to support viewport painting with an index offset
|
|
||||||
procedure Paint(const Canvas: TCanvas; First, Last: Int64; const XForm, YForm: TFunc<Double, Single>); virtual; abstract;
|
procedure Paint(const Canvas: TCanvas; First, Last: Int64; const XForm, YForm: TFunc<Double, Single>); virtual; abstract;
|
||||||
public
|
public
|
||||||
constructor Create(AOwner: TMycChart);
|
constructor Create(AOwner: TPanel);
|
||||||
procedure Repaint;
|
procedure Repaint;
|
||||||
property Owner: TMycChart read FOwner;
|
property Owner: TMycChart read GetOwner;
|
||||||
property Series: TSeries read GetSeries;
|
property Series: TSeries read GetSeries;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
// A panel is a rectangular area in the chart with its own Y-axis, which can contain multiple layers.
|
||||||
|
TPanel = class(TObject)
|
||||||
|
private
|
||||||
|
FOwner: TMycChart;
|
||||||
|
FSeriesList: TObjectList<TMycChart.TLayer>;
|
||||||
|
protected
|
||||||
|
procedure Paint(const Canvas: TCanvas; const Viewport: TRectF; MasterTotalCount: Int64; ViewStartIndex, ViewCount: Int64);
|
||||||
|
procedure UpdateAndCheckChanges(MasterTotalCount: Int64; ViewStartIndex: Int64; out SeriesChanged, SeriesRepaint: Boolean);
|
||||||
|
|
||||||
|
public
|
||||||
|
constructor Create(AOwner: TMycChart);
|
||||||
|
destructor Destroy; override;
|
||||||
|
|
||||||
|
function AddOhlcSeries(
|
||||||
|
const DataProvider: IMycDataProvider<TOhlcItem>;
|
||||||
|
const AUpColor: TAlphaColor = TAlphaColors.Green;
|
||||||
|
const ADownColor: TAlphaColor = TAlphaColors.Red
|
||||||
|
): TMycChart.TLayer;
|
||||||
|
|
||||||
|
function AddDoubleSeries(
|
||||||
|
const DataProvider: IMycDataProvider<Double>;
|
||||||
|
const ALineColor: TAlphaColor = TAlphaColors.Cornflowerblue;
|
||||||
|
const ALineWidth: Single = 1.5
|
||||||
|
): TMycChart.TLayer;
|
||||||
|
|
||||||
|
property Owner: TMycChart read FOwner;
|
||||||
|
end;
|
||||||
|
|
||||||
private
|
private
|
||||||
FSeriesList: TList<TLayer>;
|
FPanelList: TObjectList<TPanel>;
|
||||||
FXAxisSeries: TLayer;
|
FXAxisSeries: TMycChart.TLayer;
|
||||||
FLookback: TWriteable<Int64>;
|
FLookback: TWriteable<Int64>;
|
||||||
FIdleSubscrId: TMessageSubscriptionId;
|
FIdleSubscrId: TMessageSubscriptionId;
|
||||||
FViewStartIndex: Int64;
|
FViewStartIndex: Int64;
|
||||||
@@ -61,6 +94,8 @@ type
|
|||||||
FJumpButtonHot: Boolean;
|
FJumpButtonHot: Boolean;
|
||||||
FJumpButtonPressed: Boolean;
|
FJumpButtonPressed: Boolean;
|
||||||
FNeedRepaint: TFlag;
|
FNeedRepaint: TFlag;
|
||||||
|
function GetPanel(Index: Integer): TPanel;
|
||||||
|
function GetPanelCount: Integer;
|
||||||
protected
|
protected
|
||||||
procedure Paint; override;
|
procedure Paint; override;
|
||||||
procedure DoIdle;
|
procedure DoIdle;
|
||||||
@@ -72,27 +107,16 @@ type
|
|||||||
constructor Create(AOwner: TComponent); override;
|
constructor Create(AOwner: TComponent); override;
|
||||||
destructor Destroy; override;
|
destructor Destroy; override;
|
||||||
|
|
||||||
|
// Adds a new, empty panel to the bottom of the chart.
|
||||||
|
function AddPanel: TPanel;
|
||||||
|
|
||||||
// Sets the master series that defines the time scale (X-axis). This series is not drawn.
|
// Sets the master series that defines the time scale (X-axis). This series is not drawn.
|
||||||
// The chart will not render any data until the X-axis series is set.
|
function SetXAxisSeries<T>(const DataProvider: IMycDataProvider<T>): TMycChart.TLayer;
|
||||||
function SetXAxisSeries<T>(const DataProvider: IMycDataProvider<T>): TLayer;
|
|
||||||
|
|
||||||
// Creates an OHLC candlestick/bar series and returns it.
|
|
||||||
// The data provider directly supplies OHLC items, as timestamps are managed by the data series internally.
|
|
||||||
function AddOhlcSeries(
|
|
||||||
const DataProvider: IMycDataProvider<TOhlcItem>;
|
|
||||||
const AUpColor: TAlphaColor = TAlphaColors.Green;
|
|
||||||
const ADownColor: TAlphaColor = TAlphaColors.Red
|
|
||||||
): TLayer;
|
|
||||||
|
|
||||||
// Creates a simple line series for double values and returns it
|
|
||||||
function AddDoubleSeries(
|
|
||||||
const DataProvider: IMycDataProvider<Double>;
|
|
||||||
const ALineColor: TAlphaColor = TAlphaColors.Cornflowerblue;
|
|
||||||
const ALineWidth: Single = 1.5
|
|
||||||
): TLayer;
|
|
||||||
|
|
||||||
property Lookback: TWriteable<Int64> read FLookback write FLookback;
|
property Lookback: TWriteable<Int64> read FLookback write FLookback;
|
||||||
property NeedRepaint: TFlag read FNeedRepaint;
|
property NeedRepaint: TFlag read FNeedRepaint;
|
||||||
|
property PanelCount: Integer read GetPanelCount;
|
||||||
|
property Panels[Index: Integer]: TPanel read GetPanel; default;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
implementation
|
implementation
|
||||||
@@ -106,18 +130,21 @@ uses
|
|||||||
constructor TMycChart.Create(AOwner: TComponent);
|
constructor TMycChart.Create(AOwner: TComponent);
|
||||||
begin
|
begin
|
||||||
inherited Create(AOwner);
|
inherited Create(AOwner);
|
||||||
FSeriesList := TObjectList<TLayer>.Create(true);
|
FPanelList := TObjectList<TPanel>.Create(true);
|
||||||
FXAxisSeries := nil;
|
FXAxisSeries := nil;
|
||||||
|
|
||||||
FLookback := TWriteable<Int64>.CreateWriteable(1000); // Load more data for panning
|
FLookback := TWriteable<Int64>.CreateWriteable(1000);
|
||||||
FViewStartIndex := 0;
|
FViewStartIndex := 0;
|
||||||
FViewCount := 100; // Show 100 items by default
|
FViewCount := 100;
|
||||||
FIsDragging := false;
|
FIsDragging := false;
|
||||||
|
|
||||||
FIdleSubscrId :=
|
FIdleSubscrId :=
|
||||||
TMessageManager
|
TMessageManager
|
||||||
.DefaultManager
|
.DefaultManager
|
||||||
.SubscribeToMessage(TIdleMessage, procedure(const Sender: TObject; const M: TMessage) begin DoIdle; end);
|
.SubscribeToMessage(TIdleMessage, procedure(const Sender: TObject; const M: TMessage) begin DoIdle; end);
|
||||||
|
|
||||||
|
// Create the default panel.
|
||||||
|
AddPanel;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
destructor TMycChart.Destroy;
|
destructor TMycChart.Destroy;
|
||||||
@@ -125,97 +152,159 @@ begin
|
|||||||
TMessageManager.DefaultManager.Unsubscribe(TIdleMessage, FIdleSubscrId);
|
TMessageManager.DefaultManager.Unsubscribe(TIdleMessage, FIdleSubscrId);
|
||||||
|
|
||||||
FXAxisSeries.Free;
|
FXAxisSeries.Free;
|
||||||
FSeriesList.Free;
|
FPanelList.Free;
|
||||||
inherited;
|
inherited;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TMycChart.SetXAxisSeries<T>(const DataProvider: IMycDataProvider<T>): TLayer;
|
function TMycChart.AddPanel: TPanel;
|
||||||
|
begin
|
||||||
|
Result := TPanel.Create(Self);
|
||||||
|
FPanelList.Add(Result);
|
||||||
|
Repaint;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TMycChart.GetPanel(Index: Integer): TPanel;
|
||||||
|
begin
|
||||||
|
Result := FPanelList[Index];
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TMycChart.GetPanelCount: Integer;
|
||||||
|
begin
|
||||||
|
Result := FPanelList.Count;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TMycChart.SetXAxisSeries<T>(const DataProvider: IMycDataProvider<T>): TMycChart.TLayer;
|
||||||
begin
|
begin
|
||||||
// Free the old one if it exists, and create the new master series
|
|
||||||
FXAxisSeries.Free;
|
FXAxisSeries.Free;
|
||||||
|
// Note: TChartCustomLayer needs its owner to be a TPanel. We use the first panel.
|
||||||
var counter := TChartSeriesCounter<T>.Create;
|
var counter := TChartSeriesCounter<T>.Create;
|
||||||
|
|
||||||
DataProvider.Link(counter);
|
DataProvider.Link(counter);
|
||||||
|
FXAxisSeries := TChartCustomLayer<Int64>.Create(Self.Panels[0], counter.Sender);
|
||||||
FXAxisSeries := TChartCustomLayer<Int64>.Create(Self, counter.Sender);
|
|
||||||
Result := FXAxisSeries;
|
Result := FXAxisSeries;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TMycChart.AddDoubleSeries(
|
|
||||||
const DataProvider: IMycDataProvider<Double>;
|
|
||||||
const ALineColor: TAlphaColor = TAlphaColors.Cornflowerblue;
|
|
||||||
const ALineWidth: Single = 1.5
|
|
||||||
): TLayer;
|
|
||||||
begin
|
|
||||||
Result := TChartLineLayer.Create(Self, DataProvider, ALineColor, ALineWidth);
|
|
||||||
FSeriesList.Add(Result);
|
|
||||||
end;
|
|
||||||
|
|
||||||
function TMycChart.AddOhlcSeries(
|
|
||||||
const DataProvider: IMycDataProvider<TOhlcItem>;
|
|
||||||
const AUpColor: TAlphaColor = TAlphaColors.Green;
|
|
||||||
const ADownColor: TAlphaColor = TAlphaColors.Red
|
|
||||||
): TLayer;
|
|
||||||
begin
|
|
||||||
Result :=
|
|
||||||
TChartOhlcLayer.Create(Self, DataProvider, TMutable<TAlphaColor>.Constant(AUpColor), TMutable<TAlphaColor>.Constant(ADownColor));
|
|
||||||
FSeriesList.Add(Result);
|
|
||||||
end;
|
|
||||||
|
|
||||||
procedure TMycChart.DoIdle;
|
procedure TMycChart.DoIdle;
|
||||||
|
var
|
||||||
|
prevTotalCount, newTotalCount: Int64;
|
||||||
|
seriesChanged, seriesRepaint, repaintSignal: Boolean;
|
||||||
|
countDelta: Int64;
|
||||||
begin
|
begin
|
||||||
// An X-axis series is required for any operation
|
|
||||||
if not Assigned(FXAxisSeries) then
|
if not Assigned(FXAxisSeries) then
|
||||||
exit;
|
exit;
|
||||||
|
|
||||||
// First update the master series
|
prevTotalCount := FXAxisSeries.Series.TotalCount;
|
||||||
var prevTotalCount := FXAxisSeries.Series.TotalCount;
|
|
||||||
FXAxisSeries.Update;
|
FXAxisSeries.Update;
|
||||||
|
newTotalCount := FXAxisSeries.Series.TotalCount;
|
||||||
|
|
||||||
// Check if the master series itself has changed
|
seriesChanged := prevTotalCount <> newTotalCount;
|
||||||
var newTotalCount := FXAxisSeries.Series.TotalCount;
|
seriesRepaint := (FViewStartIndex = 0);
|
||||||
var seriesChanged := prevTotalCount <> newTotalCount;
|
|
||||||
|
|
||||||
// Repaint if the last bar is visible, so we display live data
|
for var panel in FPanelList do
|
||||||
var seriesRepaint := FViewStartIndex = 0;
|
|
||||||
|
|
||||||
for var series in FSeriesList do
|
|
||||||
begin
|
begin
|
||||||
var seriesCount := series.Series.TotalCount;
|
var panelChanged, panelRepaint: Boolean;
|
||||||
|
panel.UpdateAndCheckChanges(prevTotalCount, FViewStartIndex, panelChanged, panelRepaint);
|
||||||
// Check if this series may need a repaint (if it lags behind the master series and these lags are currently visible)
|
seriesChanged := seriesChanged or panelChanged;
|
||||||
if FViewStartIndex <= prevTotalCount - seriesCount then
|
seriesRepaint := seriesRepaint or panelRepaint;
|
||||||
seriesRepaint := true;
|
|
||||||
|
|
||||||
// Update the series
|
|
||||||
series.Update;
|
|
||||||
|
|
||||||
if seriesCount <> series.Series.TotalCount then
|
|
||||||
seriesChanged := true;
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// Don't move chart, if the last bar isn't visible
|
if (FViewStartIndex > 0) then
|
||||||
if FViewStartIndex > 0 then
|
|
||||||
begin
|
begin
|
||||||
var countDelta := newTotalCount - prevTotalCount;
|
countDelta := newTotalCount - prevTotalCount;
|
||||||
if countDelta > 0 then
|
if (countDelta > 0) then
|
||||||
begin
|
begin
|
||||||
// Adjust the start index based on the absolute change in total data points
|
|
||||||
FViewStartIndex := FViewStartIndex + countDelta;
|
FViewStartIndex := FViewStartIndex + countDelta;
|
||||||
// Clamp to beginning of dataset
|
if (FViewStartIndex + FViewCount > newTotalCount) then
|
||||||
if FViewStartIndex + FViewCount > newTotalCount then
|
|
||||||
FViewStartIndex := newTotalCount - FViewCount;
|
FViewStartIndex := newTotalCount - FViewCount;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// finally, check the repaint signal
|
repaintSignal := FNeedRepaint.Reset;
|
||||||
var repaintSignal := false; // FNeedRepaint.Reset;
|
|
||||||
|
|
||||||
if repaintSignal or (seriesRepaint and seriesChanged) then
|
if repaintSignal or (seriesRepaint and seriesChanged) then
|
||||||
Repaint;
|
Repaint;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure TMycChart.Paint;
|
||||||
|
var
|
||||||
|
rect, panelRect: TRectF;
|
||||||
|
isButtonVisible: Boolean;
|
||||||
|
buttonColor: TAlphaColor;
|
||||||
|
path: TPathData;
|
||||||
|
masterTotalCount: Int64;
|
||||||
|
panelHeight: Single;
|
||||||
|
begin
|
||||||
|
inherited;
|
||||||
|
rect := Self.LocalRect;
|
||||||
|
|
||||||
|
if (not Assigned(FXAxisSeries)) or (FXAxisSeries.Series.Count <= 1) or (FViewCount <= 1) or (PanelCount = 0) then
|
||||||
|
begin
|
||||||
|
Canvas.Fill.Color := TAlphaColors.Gray;
|
||||||
|
Canvas.FillText(rect, 'No Data', false, 1, [], TTextAlign.Center, TTextAlign.Center);
|
||||||
|
Exit;
|
||||||
|
end;
|
||||||
|
|
||||||
|
masterTotalCount := FXAxisSeries.Series.TotalCount;
|
||||||
|
panelHeight := rect.Height / PanelCount;
|
||||||
|
|
||||||
|
for var i := 0 to PanelCount - 1 do
|
||||||
|
begin
|
||||||
|
var panel := Panels[i];
|
||||||
|
panelRect := TRectF.Create(rect.Left, rect.Top + (i * panelHeight), rect.Right, rect.Top + ((i + 1) * panelHeight));
|
||||||
|
|
||||||
|
// Draw panel content
|
||||||
|
panel.Paint(Self.Canvas, panelRect, masterTotalCount, FViewStartIndex, FViewCount);
|
||||||
|
|
||||||
|
// Draw separator line
|
||||||
|
if (i > 0) then
|
||||||
|
begin
|
||||||
|
Canvas.Stroke.Color := TAlphaColors.Gray;
|
||||||
|
Canvas.Stroke.Thickness := 1;
|
||||||
|
Canvas.DrawLine(PointF(panelRect.Left, panelRect.Top), PointF(panelRect.Right, panelRect.Top), 1);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
// --- Draw the "jump to latest" button (code unchanged) ---
|
||||||
|
isButtonVisible := (FViewStartIndex > 0);
|
||||||
|
|
||||||
|
if isButtonVisible then
|
||||||
|
begin
|
||||||
|
FJumpButtonRect := TRectF.Create(Self.Width - 44, Self.Height - 44, Self.Width - 10, Self.Height - 10);
|
||||||
|
|
||||||
|
if FJumpButtonPressed then
|
||||||
|
buttonColor := $FF707070
|
||||||
|
else if FJumpButtonHot then
|
||||||
|
buttonColor := $FF505050
|
||||||
|
else
|
||||||
|
buttonColor := $FF303030;
|
||||||
|
|
||||||
|
Canvas.Fill.Color := buttonColor;
|
||||||
|
Canvas.FillRect(FJumpButtonRect, 4, 4, AllCorners, 0.7);
|
||||||
|
|
||||||
|
Canvas.Stroke.Color := TAlphaColors.White;
|
||||||
|
Canvas.Stroke.Thickness := 1.5;
|
||||||
|
|
||||||
|
path := TPathData.Create;
|
||||||
|
try
|
||||||
|
var cx := FJumpButtonRect.CenterPoint.X;
|
||||||
|
var cy := FJumpButtonRect.CenterPoint.Y;
|
||||||
|
path.MoveTo(PointF(cx - 5, cy - 6));
|
||||||
|
path.LineTo(PointF(cx, cy));
|
||||||
|
path.LineTo(PointF(cx - 5, cy + 6));
|
||||||
|
path.MoveTo(PointF(cx + 2, cy - 6));
|
||||||
|
path.LineTo(PointF(cx + 7, cy));
|
||||||
|
path.LineTo(PointF(cx + 2, cy + 6));
|
||||||
|
Canvas.DrawPath(path, 1);
|
||||||
|
finally
|
||||||
|
path.Free;
|
||||||
|
end;
|
||||||
|
end
|
||||||
|
else
|
||||||
|
begin
|
||||||
|
FJumpButtonRect := TRectF.Empty;
|
||||||
|
FJumpButtonPressed := false;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
procedure TMycChart.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Single);
|
procedure TMycChart.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Single);
|
||||||
begin
|
begin
|
||||||
// Check for button press first
|
// Check for button press first
|
||||||
@@ -382,153 +471,156 @@ begin
|
|||||||
Repaint;
|
Repaint;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TMycChart.Paint;
|
{ TMycChart.TLayer }
|
||||||
|
|
||||||
function CalcView(Series: TSeries; MasterCount: Int64; out First: Int64; out Last: Int64): Boolean;
|
constructor TMycChart.TLayer.Create(AOwner: TPanel);
|
||||||
|
begin
|
||||||
|
inherited Create;
|
||||||
|
FParent := AOwner;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TMycChart.TLayer.GetOwner: TMycChart;
|
||||||
|
begin
|
||||||
|
Result := FParent.Owner;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TMycChart.TLayer.Repaint;
|
||||||
|
begin
|
||||||
|
FParent.Owner.Repaint;
|
||||||
|
end;
|
||||||
|
|
||||||
|
{ TMycChart.TPanel }
|
||||||
|
|
||||||
|
constructor TMycChart.TPanel.Create(AOwner: TMycChart);
|
||||||
|
begin
|
||||||
|
inherited Create;
|
||||||
|
FOwner := AOwner;
|
||||||
|
FSeriesList := TObjectList<TMycChart.TLayer>.Create(true);
|
||||||
|
end;
|
||||||
|
|
||||||
|
destructor TMycChart.TPanel.Destroy;
|
||||||
|
begin
|
||||||
|
FSeriesList.Free;
|
||||||
|
inherited Destroy;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TMycChart.TPanel.AddDoubleSeries(
|
||||||
|
const DataProvider: IMycDataProvider<Double>;
|
||||||
|
const ALineColor: TAlphaColor;
|
||||||
|
const ALineWidth: Single
|
||||||
|
): TMycChart.TLayer;
|
||||||
|
begin
|
||||||
|
Result := TChartLineLayer.Create(Self, DataProvider, ALineColor, ALineWidth);
|
||||||
|
FSeriesList.Add(Result);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TMycChart.TPanel.AddOhlcSeries(
|
||||||
|
const DataProvider: IMycDataProvider<TOhlcItem>;
|
||||||
|
const AUpColor, ADownColor: TAlphaColor
|
||||||
|
): TMycChart.TLayer;
|
||||||
|
begin
|
||||||
|
Result :=
|
||||||
|
TChartOhlcLayer.Create(Self, DataProvider, TMutable<TAlphaColor>.Constant(AUpColor), TMutable<TAlphaColor>.Constant(ADownColor));
|
||||||
|
FSeriesList.Add(Result);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TMycChart.TPanel.Paint(const Canvas: TCanvas; const Viewport: TRectF; MasterTotalCount, ViewStartIndex, ViewCount: Int64);
|
||||||
|
var
|
||||||
|
localMin, localMax, seriesMin, seriesMax, padding: Double;
|
||||||
|
rangeInitialized: Boolean;
|
||||||
|
seriesFirst, seriesLast: Int64;
|
||||||
|
xTransform, yTransform: TFunc<Double, Single>;
|
||||||
|
|
||||||
|
function CalcView(Series: TMycChart.TSeries; out First, Last: Int64): Boolean;
|
||||||
|
var
|
||||||
|
seriesTotalCount, offset, startIdx: Int64;
|
||||||
begin
|
begin
|
||||||
var seriesTotalCount := series.TotalCount;
|
seriesTotalCount := series.TotalCount;
|
||||||
var offset := MasterCount - seriesTotalCount;
|
offset := MasterTotalCount - seriesTotalCount;
|
||||||
var startIdx := FViewStartIndex - offset;
|
startIdx := ViewStartIndex - offset;
|
||||||
|
|
||||||
First := Max(0, startIdx);
|
First := Max(0, startIdx);
|
||||||
Last := Min(series.Count, startIdx + FViewCount) - 1;
|
Last := Min(series.Count, startIdx + ViewCount) - 1;
|
||||||
|
Result := First <= Last;
|
||||||
Result := First <= Last - 1;
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
var
|
|
||||||
rect: TRectF;
|
|
||||||
globalMin, globalMax, seriesMin, seriesMax, padding: Double;
|
|
||||||
rangeInitialized: Boolean;
|
|
||||||
xTransform: TFunc<Double, Single>;
|
|
||||||
yTransform: TFunc<Double, Single>;
|
|
||||||
isButtonVisible: Boolean;
|
|
||||||
buttonColor: TAlphaColor;
|
|
||||||
path: TPathData;
|
|
||||||
masterTotalCount, seriesFirst, seriesLast: Int64;
|
|
||||||
begin
|
begin
|
||||||
inherited;
|
|
||||||
rect := Self.LocalRect;
|
|
||||||
|
|
||||||
// The chart requires an X-axis series to be set before it can draw anything.
|
|
||||||
if not Assigned(FXAxisSeries) or (FXAxisSeries.Series.Count <= 1) or (FViewCount <= 1) then
|
|
||||||
begin
|
|
||||||
Canvas.Fill.Color := TAlphaColors.Gray;
|
|
||||||
Canvas.FillText(rect, 'No Data', false, 1, [], TTextAlign.Center, TTextAlign.Center);
|
|
||||||
Exit;
|
|
||||||
end;
|
|
||||||
|
|
||||||
rangeInitialized := false;
|
rangeInitialized := false;
|
||||||
|
|
||||||
masterTotalCount := FXAxisSeries.Series.TotalCount;
|
// 1. Calculate value range (Y-Axis) for this panel only
|
||||||
for var series in FSeriesList do
|
for var series in FSeriesList do
|
||||||
begin
|
begin
|
||||||
if CalcView(series.Series, masterTotalCount, seriesFirst, seriesLast) then
|
if CalcView(series.Series, seriesFirst, seriesLast) then
|
||||||
begin
|
begin
|
||||||
if series.GetValueRange(seriesFirst, seriesLast, seriesMin, seriesMax) then
|
if series.GetValueRange(seriesFirst, seriesLast, seriesMin, seriesMax) then
|
||||||
begin
|
begin
|
||||||
if not rangeInitialized then
|
if not rangeInitialized then
|
||||||
begin
|
begin
|
||||||
globalMin := seriesMin;
|
localMin := seriesMin;
|
||||||
globalMax := seriesMax;
|
localMax := seriesMax;
|
||||||
rangeInitialized := true;
|
rangeInitialized := true;
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
begin
|
begin
|
||||||
globalMin := Min(globalMin, seriesMin);
|
localMin := Min(localMin, seriesMin);
|
||||||
globalMax := Max(globalMax, seriesMax);
|
localMax := Max(localMax, seriesMax);
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
if not rangeInitialized then
|
if not rangeInitialized then
|
||||||
Exit;
|
Exit; // Nothing to draw in this panel
|
||||||
|
|
||||||
padding := (globalMax - globalMin) * 0.1;
|
padding := (localMax - localMin) * 0.1;
|
||||||
globalMin := globalMin - padding;
|
localMin := localMin - padding;
|
||||||
globalMax := globalMax + padding;
|
localMax := localMax + padding;
|
||||||
|
|
||||||
if (globalMax - globalMin) = 0 then
|
if (localMax - localMin) = 0 then
|
||||||
exit;
|
localMax := localMin + 1; // Avoid division by zero
|
||||||
|
|
||||||
// Base transform maps an index from the viewport to a screen coordinate
|
// 2. Create transforms for this panel
|
||||||
yTransform :=
|
yTransform :=
|
||||||
function(value: Double): Single begin Result := rect.Top + (1 - (value - globalMin) / (globalMax - globalMin)) * rect.Height; end;
|
function(value: Double): Single
|
||||||
|
begin
|
||||||
|
Result := Viewport.Top + (1 - (value - localMin) / (localMax - localMin)) * Viewport.Height;
|
||||||
|
end;
|
||||||
|
|
||||||
// --- Draw Series with Offset Compensation ---
|
// 3. Paint all layers in this panel
|
||||||
// The masterTotalCount is already calculated from the range finding loop
|
|
||||||
for var series in FSeriesList do
|
for var series in FSeriesList do
|
||||||
begin
|
begin
|
||||||
if CalcView(series.Series, masterTotalCount, seriesFirst, seriesLast) then
|
if CalcView(series.Series, seriesFirst, seriesLast) then
|
||||||
begin
|
begin
|
||||||
var offset := masterTotalCount - series.Series.TotalCount;
|
var offset := MasterTotalCount - series.Series.TotalCount;
|
||||||
xTransform :=
|
xTransform :=
|
||||||
function(index: Double): Single
|
function(index: Double): Single
|
||||||
begin
|
begin
|
||||||
Result := rect.Right - (((index + offset - FViewStartIndex) / (FViewCount - 1)) * rect.Width);
|
Result := Viewport.Right - (((index + offset - ViewStartIndex) / (ViewCount - 1)) * Viewport.Width);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
series.Paint(Self.Canvas, seriesFirst, seriesLast, xTransform, yTransform);
|
series.Paint(Canvas, seriesFirst, seriesLast, xTransform, yTransform);
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// --- Draw the "jump to latest" button ---
|
|
||||||
isButtonVisible := (FViewStartIndex > 0);
|
|
||||||
|
|
||||||
if isButtonVisible then
|
|
||||||
begin
|
|
||||||
// Define button position and size
|
|
||||||
FJumpButtonRect := TRectF.Create(Self.Width - 44, Self.Height - 44, Self.Width - 10, Self.Height - 10);
|
|
||||||
|
|
||||||
// Determine color based on state
|
|
||||||
if FJumpButtonPressed then
|
|
||||||
buttonColor := $FF707070 // Pressed color
|
|
||||||
else if FJumpButtonHot then
|
|
||||||
buttonColor := $FF505050 // Hover color
|
|
||||||
else
|
|
||||||
buttonColor := $FF303030; // Default color
|
|
||||||
|
|
||||||
// Draw button background
|
|
||||||
Canvas.Fill.Color := buttonColor;
|
|
||||||
Canvas.FillRect(FJumpButtonRect, 4, 4, AllCorners, 0.7);
|
|
||||||
|
|
||||||
// Draw an icon
|
|
||||||
Canvas.Stroke.Color := TAlphaColors.White;
|
|
||||||
Canvas.Stroke.Thickness := 1.5;
|
|
||||||
|
|
||||||
path := TPathData.Create;
|
|
||||||
try
|
|
||||||
var cx := FJumpButtonRect.CenterPoint.X;
|
|
||||||
var cy := FJumpButtonRect.CenterPoint.Y;
|
|
||||||
path.MoveTo(PointF(cx - 5, cy - 6));
|
|
||||||
path.LineTo(PointF(cx, cy));
|
|
||||||
path.LineTo(PointF(cx - 5, cy + 6));
|
|
||||||
path.MoveTo(PointF(cx + 2, cy - 6));
|
|
||||||
path.LineTo(PointF(cx + 7, cy));
|
|
||||||
path.LineTo(PointF(cx + 2, cy + 6));
|
|
||||||
Canvas.DrawPath(path, 1);
|
|
||||||
finally
|
|
||||||
path.Free;
|
|
||||||
end;
|
|
||||||
end
|
|
||||||
else
|
|
||||||
begin
|
|
||||||
// Reset button state when not visible
|
|
||||||
FJumpButtonRect := TRectF.Empty;
|
|
||||||
FJumpButtonPressed := false;
|
|
||||||
end;
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
constructor TMycChart.TLayer.Create(AOwner: TMycChart);
|
procedure TMycChart.TPanel.UpdateAndCheckChanges(MasterTotalCount, ViewStartIndex: Int64; out SeriesChanged, SeriesRepaint: Boolean);
|
||||||
|
var
|
||||||
|
seriesCount: Int64;
|
||||||
begin
|
begin
|
||||||
inherited Create;
|
SeriesChanged := false;
|
||||||
FOwner := AOwner;
|
SeriesRepaint := false;
|
||||||
end;
|
for var series in FSeriesList do
|
||||||
|
begin
|
||||||
|
seriesCount := series.Series.TotalCount;
|
||||||
|
|
||||||
procedure TMycChart.TLayer.Repaint;
|
if (ViewStartIndex <= MasterTotalCount - seriesCount) then
|
||||||
begin
|
SeriesRepaint := true;
|
||||||
FOwner.Repaint;
|
|
||||||
|
series.Update;
|
||||||
|
|
||||||
|
if (seriesCount <> series.Series.TotalCount) then
|
||||||
|
SeriesChanged := true;
|
||||||
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
end.
|
end.
|
||||||
|
|||||||
@@ -5,25 +5,10 @@ interface
|
|||||||
uses
|
uses
|
||||||
System.TimeSpan,
|
System.TimeSpan,
|
||||||
Myc.Signals,
|
Myc.Signals,
|
||||||
Myc.Core.Notifier;
|
Myc.Core.Notifier,
|
||||||
|
Myc.Trade.Types;
|
||||||
|
|
||||||
type
|
type
|
||||||
// A data record for an Ask/Bid price pair.
|
|
||||||
TAskBidItem = packed record
|
|
||||||
Ask: Double;
|
|
||||||
Bid: Double;
|
|
||||||
constructor Create(AAsk, ABid: Double);
|
|
||||||
end;
|
|
||||||
|
|
||||||
TOhlcItem = record
|
|
||||||
Open: Double;
|
|
||||||
High: Double;
|
|
||||||
Low: Double;
|
|
||||||
Close: Double;
|
|
||||||
Volume: Double;
|
|
||||||
constructor Create(AOpen, AHigh, ALow, AClose, AVolume: Double);
|
|
||||||
end;
|
|
||||||
|
|
||||||
// Represents a time-stamped data point in a series.
|
// Represents a time-stamped data point in a series.
|
||||||
TDataPoint<T> = record
|
TDataPoint<T> = record
|
||||||
Time: TDateTime;
|
Time: TDateTime;
|
||||||
@@ -295,25 +280,6 @@ begin
|
|||||||
Result := Self.Convert<Single>(function(const Ohlc: TDataPoint<TOhlcItem>): Single begin Result := Ohlc.Data.Volume; end);
|
Result := Self.Convert<Single>(function(const Ohlc: TDataPoint<TOhlcItem>): Single begin Result := Ohlc.Data.Volume; end);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
{ TAskBidItem }
|
|
||||||
|
|
||||||
constructor TAskBidItem.Create(AAsk, ABid: Double);
|
|
||||||
begin
|
|
||||||
Ask := AAsk;
|
|
||||||
Bid := ABid;
|
|
||||||
end;
|
|
||||||
|
|
||||||
{ TOhlcItem }
|
|
||||||
|
|
||||||
constructor TOhlcItem.Create(AOpen, AHigh, ALow, AClose, AVolume: Double);
|
|
||||||
begin
|
|
||||||
Open := AOpen;
|
|
||||||
High := AHigh;
|
|
||||||
Low := ALow;
|
|
||||||
Close := AClose;
|
|
||||||
Volume := AVolume;
|
|
||||||
end;
|
|
||||||
|
|
||||||
{ TDataPoint<T> }
|
{ TDataPoint<T> }
|
||||||
|
|
||||||
constructor TDataPoint<T>.Create(ATime: TDateTime; const AData: T);
|
constructor TDataPoint<T>.Create(ATime: TDateTime; const AData: T);
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ interface
|
|||||||
uses
|
uses
|
||||||
System.SysUtils,
|
System.SysUtils,
|
||||||
System.Math,
|
System.Math,
|
||||||
Myc.Trade.DataArray,
|
Myc.Trade.Types,
|
||||||
Myc.Trade.DataPoint;
|
Myc.Trade.DataArray;
|
||||||
|
|
||||||
type
|
type
|
||||||
// Result for the Moving Average Convergence Divergence (MACD) indicator.
|
// Result for the Moving Average Convergence Divergence (MACD) indicator.
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
unit Myc.Trade.Types;
|
||||||
|
|
||||||
|
interface
|
||||||
|
|
||||||
|
type
|
||||||
|
// A data record for an Ask/Bid price pair.
|
||||||
|
TAskBidItem = packed record
|
||||||
|
Ask: Double;
|
||||||
|
Bid: Double;
|
||||||
|
constructor Create(AAsk, ABid: Double);
|
||||||
|
end;
|
||||||
|
|
||||||
|
TOhlcItem = record
|
||||||
|
Open: Double;
|
||||||
|
High: Double;
|
||||||
|
Low: Double;
|
||||||
|
Close: Double;
|
||||||
|
Volume: Double;
|
||||||
|
constructor Create(AOpen, AHigh, ALow, AClose, AVolume: Double);
|
||||||
|
end;
|
||||||
|
|
||||||
|
implementation
|
||||||
|
|
||||||
|
{ TAskBidItem }
|
||||||
|
|
||||||
|
constructor TAskBidItem.Create(AAsk, ABid: Double);
|
||||||
|
begin
|
||||||
|
Ask := AAsk;
|
||||||
|
Bid := ABid;
|
||||||
|
end;
|
||||||
|
|
||||||
|
{ TOhlcItem }
|
||||||
|
|
||||||
|
constructor TOhlcItem.Create(AOpen, AHigh, ALow, AClose, AVolume: Double);
|
||||||
|
begin
|
||||||
|
Open := AOpen;
|
||||||
|
High := AHigh;
|
||||||
|
Low := ALow;
|
||||||
|
Close := AClose;
|
||||||
|
Volume := AVolume;
|
||||||
|
end;
|
||||||
|
|
||||||
|
end.
|
||||||
Reference in New Issue
Block a user