Chart data logic separation
This commit is contained in:
@@ -12,7 +12,8 @@ uses
|
|||||||
DynamicFMXControl in 'DynamicFMXControl.pas',
|
DynamicFMXControl in 'DynamicFMXControl.pas',
|
||||||
FirstStrategy in 'FirstStrategy.pas',
|
FirstStrategy in 'FirstStrategy.pas',
|
||||||
Myc.Fmx.Chart in 'Myc.Fmx.Chart.pas',
|
Myc.Fmx.Chart in 'Myc.Fmx.Chart.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';
|
||||||
|
|
||||||
{$R *.res}
|
{$R *.res}
|
||||||
|
|
||||||
|
|||||||
@@ -141,6 +141,7 @@
|
|||||||
<DCCReference Include="FirstStrategy.pas"/>
|
<DCCReference Include="FirstStrategy.pas"/>
|
||||||
<DCCReference Include="Myc.Fmx.Chart.pas"/>
|
<DCCReference Include="Myc.Fmx.Chart.pas"/>
|
||||||
<DCCReference Include="..\Src\Myc.Trade.DataArray.pas"/>
|
<DCCReference Include="..\Src\Myc.Trade.DataArray.pas"/>
|
||||||
|
<DCCReference Include="..\Src\Myc.FMX.Chart.Series.pas"/>
|
||||||
<BuildConfiguration Include="Base">
|
<BuildConfiguration Include="Base">
|
||||||
<Key>Base</Key>
|
<Key>Base</Key>
|
||||||
</BuildConfiguration>
|
</BuildConfiguration>
|
||||||
|
|||||||
@@ -12,25 +12,6 @@ uses
|
|||||||
type
|
type
|
||||||
TTimeframe = (M1, M5, H1, D);
|
TTimeframe = (M1, M5, H1, D);
|
||||||
|
|
||||||
IMycConverter<S, T> = interface(IMycProcessor<S>)
|
|
||||||
function GetSender: IMycDataProvider<T>;
|
|
||||||
property Sender: IMycDataProvider<T> read GetSender;
|
|
||||||
end;
|
|
||||||
|
|
||||||
TMycConverter<S, T> = class abstract(TMycProcessor<S>, IMycConverter<S, T>)
|
|
||||||
private
|
|
||||||
FSender: TMycDataProvider<T>;
|
|
||||||
function GetSender: IMycDataProvider<T>;
|
|
||||||
protected
|
|
||||||
// Broadcasts the given data to all linked processors.
|
|
||||||
procedure Broadcast(const Value: T);
|
|
||||||
|
|
||||||
public
|
|
||||||
constructor Create;
|
|
||||||
destructor Destroy; override;
|
|
||||||
property Sender: IMycDataProvider<T> read GetSender;
|
|
||||||
end;
|
|
||||||
|
|
||||||
TMycGenericConverter<S, T> = class(TMycConverter<S, T>)
|
TMycGenericConverter<S, T> = class(TMycConverter<S, T>)
|
||||||
type
|
type
|
||||||
TConvertFunc = reference to function(const Value: S): T;
|
TConvertFunc = reference to function(const Value: S): T;
|
||||||
@@ -255,29 +236,4 @@ begin
|
|||||||
Broadcast(FFunc(Value));
|
Broadcast(FFunc(Value));
|
||||||
end;
|
end;
|
||||||
|
|
||||||
{ TMycConverter<S, T> }
|
|
||||||
|
|
||||||
constructor TMycConverter<S, T>.Create;
|
|
||||||
begin
|
|
||||||
inherited Create;
|
|
||||||
FSender := TMycDataProvider<T>.Create(Self);
|
|
||||||
end;
|
|
||||||
|
|
||||||
destructor TMycConverter<S, T>.Destroy;
|
|
||||||
begin
|
|
||||||
FSender.Free;
|
|
||||||
inherited Destroy;
|
|
||||||
end;
|
|
||||||
|
|
||||||
procedure TMycConverter<S, T>.Broadcast(const Value: T);
|
|
||||||
begin
|
|
||||||
var cValue := Value;
|
|
||||||
FSender.Notify(function(const Processor: IMycProcessor<T>): Boolean begin Result := Processor.ProcessData(cValue) end);
|
|
||||||
end;
|
|
||||||
|
|
||||||
function TMycConverter<S, T>.GetSender: IMycDataProvider<T>;
|
|
||||||
begin
|
|
||||||
Result := FSender;
|
|
||||||
end;
|
|
||||||
|
|
||||||
end.
|
end.
|
||||||
|
|||||||
@@ -165,6 +165,10 @@ begin
|
|||||||
|
|
||||||
var OhlcPoint: IMycConverter<TArray<TDataPoint<TAskBidItem>>, TDataPoint<TOhlcItem>> := TTicksToTimeframe.Create(M1);
|
var OhlcPoint: IMycConverter<TArray<TDataPoint<TAskBidItem>>, TDataPoint<TOhlcItem>> := TTicksToTimeframe.Create(M1);
|
||||||
|
|
||||||
|
var Timestamps: IMycConverter<TDataPoint<TOhlcItem>, TDateTime> :=
|
||||||
|
TMycGenericConverter<TDataPoint<TOhlcItem>, TDateTime>
|
||||||
|
.Create(function(const Ohlc: TDataPoint<TOhlcItem>): TDateTime begin Result := Ohlc.Time; end);
|
||||||
|
|
||||||
var Ohlc: IMycConverter<TDataPoint<TOhlcItem>, TOhlcItem> :=
|
var Ohlc: IMycConverter<TDataPoint<TOhlcItem>, TOhlcItem> :=
|
||||||
TMycGenericConverter<TDataPoint<TOhlcItem>, TOhlcItem>
|
TMycGenericConverter<TDataPoint<TOhlcItem>, TOhlcItem>
|
||||||
.Create(function(const Ohlc: TDataPoint<TOhlcItem>): TOhlcItem begin Result := Ohlc.Data; end);
|
.Create(function(const Ohlc: TDataPoint<TOhlcItem>): TOhlcItem begin Result := Ohlc.Data; end);
|
||||||
@@ -189,13 +193,14 @@ begin
|
|||||||
//
|
//
|
||||||
// chart.AddDoubleSeries(Hull.Sender);
|
// chart.AddDoubleSeries(Hull.Sender);
|
||||||
|
|
||||||
|
chart.SetXAxisSeries<TOhlcItem>(OHLC.Sender);
|
||||||
|
chart.AddOhlcSeries(Ohlc.Sender);
|
||||||
|
|
||||||
var done := ExecuteStrategy(Symbol, OhlcPoint);
|
var done := ExecuteStrategy(Symbol, OhlcPoint);
|
||||||
|
|
||||||
/////
|
/////
|
||||||
|
|
||||||
FProcessDone := TState.All([FProcessDone, done]);
|
FProcessDone := TState.All([FProcessDone, done]);
|
||||||
|
|
||||||
chart.AddOhlcSeries(OhlcPoint.Sender);
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TForm1.TreeViewDblClick(Sender: TObject);
|
procedure TForm1.TreeViewDblClick(Sender: TObject);
|
||||||
|
|||||||
@@ -0,0 +1,282 @@
|
|||||||
|
unit Myc.FMX.Chart.Series;
|
||||||
|
|
||||||
|
interface
|
||||||
|
|
||||||
|
uses
|
||||||
|
System.SysUtils,
|
||||||
|
System.SyncObjs,
|
||||||
|
System.UITypes,
|
||||||
|
FMX.Graphics,
|
||||||
|
Myc.Trade.DataArray,
|
||||||
|
Myc.Trade.DataPoint,
|
||||||
|
Myc.Lazy,
|
||||||
|
Myc.Fmx.Chart;
|
||||||
|
|
||||||
|
type
|
||||||
|
TChartSeriesCounter<T> = class(TMycConverter<T, Int64>)
|
||||||
|
private
|
||||||
|
FCount: Int64;
|
||||||
|
protected
|
||||||
|
function ProcessData(const Value: T): Boolean; override;
|
||||||
|
public
|
||||||
|
constructor Create;
|
||||||
|
end;
|
||||||
|
|
||||||
|
TChartSeriesReceiver<T> = class(TMycProcessor<T>)
|
||||||
|
strict private
|
||||||
|
FCurrData: TMycDataArray<T>;
|
||||||
|
FLookback: TWriteable<Int64>;
|
||||||
|
private
|
||||||
|
FData: TWriteable<TMycDataArray<T>>;
|
||||||
|
protected
|
||||||
|
function ProcessData(const Value: T): Boolean; override;
|
||||||
|
public
|
||||||
|
constructor Create(const ALookback: TWriteable<Int64>);
|
||||||
|
property Data: TWriteable<TMycDataArray<T>> read FData;
|
||||||
|
end;
|
||||||
|
|
||||||
|
TChartSeriesProcessor<T> = class(TMycChart.TSeries)
|
||||||
|
strict private
|
||||||
|
FDataSeries: TMycDataArray<T>;
|
||||||
|
FLock: TSpinLock;
|
||||||
|
private
|
||||||
|
FData: TMycDataArray<T>;
|
||||||
|
FDataProvider: IMycDataProvider<T>;
|
||||||
|
FReceiver: TChartSeriesReceiver<T>;
|
||||||
|
FReceiverTag: TTag;
|
||||||
|
protected
|
||||||
|
function GetCount: Int64; override;
|
||||||
|
function GetTotalCount: Int64; override;
|
||||||
|
procedure Update; override;
|
||||||
|
|
||||||
|
public
|
||||||
|
constructor Create(AOwner: TMycChart; const ADataProvider: IMycDataProvider<T>);
|
||||||
|
destructor Destroy; override;
|
||||||
|
property Data: TMycDataArray<T> read FData;
|
||||||
|
end;
|
||||||
|
|
||||||
|
{ TChartOhlcSeries }
|
||||||
|
TChartOhlcSeries = class(TChartSeriesProcessor<TOhlcItem>)
|
||||||
|
private
|
||||||
|
FUpColor: TAlphaColor;
|
||||||
|
FDownColor: TAlphaColor;
|
||||||
|
protected
|
||||||
|
function GetValueRange(First, Last: Int64; out MinValue, MaxValue: Double): Boolean; override;
|
||||||
|
procedure Paint(const Canvas: TCanvas; First, Last: Int64; const XForm, YForm: TFunc<Double, Single>); override;
|
||||||
|
public
|
||||||
|
constructor Create(AOwner: TMycChart; const ADataProvider: IMycDataProvider<TOhlcItem>; const AUpColor, ADownColor: TAlphaColor);
|
||||||
|
end;
|
||||||
|
|
||||||
|
{ TChartLineSeries }
|
||||||
|
TChartLineSeries = class(TChartSeriesProcessor<Double>)
|
||||||
|
private
|
||||||
|
FLineColor: TAlphaColor;
|
||||||
|
FLineWidth: Single;
|
||||||
|
protected
|
||||||
|
function GetValueRange(First, Last: Int64; out MinValue, MaxValue: Double): Boolean; override;
|
||||||
|
procedure Paint(const Canvas: TCanvas; First, Last: Int64; const XForm, YForm: TFunc<Double, Single>); override;
|
||||||
|
public
|
||||||
|
constructor Create(
|
||||||
|
AOwner: TMycChart;
|
||||||
|
const ADataProvider: IMycDataProvider<Double>;
|
||||||
|
const ALineColor: TAlphaColor;
|
||||||
|
ALineWidth: Single
|
||||||
|
);
|
||||||
|
end;
|
||||||
|
|
||||||
|
implementation
|
||||||
|
|
||||||
|
uses
|
||||||
|
System.Types,
|
||||||
|
System.Math,
|
||||||
|
FMX.Types;
|
||||||
|
|
||||||
|
constructor TChartSeriesCounter<T>.Create;
|
||||||
|
begin
|
||||||
|
inherited Create;
|
||||||
|
FCount := 0;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TChartSeriesCounter<T>.ProcessData(const Value: T): Boolean;
|
||||||
|
begin
|
||||||
|
Result := true;
|
||||||
|
Broadcast(FCount);
|
||||||
|
inc(FCount);
|
||||||
|
end;
|
||||||
|
|
||||||
|
{ TChartSeriesReceiver<T> }
|
||||||
|
|
||||||
|
constructor TChartSeriesReceiver<T>.Create(const ALookback: TWriteable<Int64>);
|
||||||
|
begin
|
||||||
|
inherited Create;
|
||||||
|
FLookback := ALookback;
|
||||||
|
FCurrData := TMycDataArray<T>.CreateEmpty;
|
||||||
|
FData := TWriteable<TMycDataArray<T>>.CreateWriteable(FCurrData).Protect;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TChartSeriesReceiver<T>.ProcessData(const Value: T): Boolean;
|
||||||
|
begin
|
||||||
|
Result := true;
|
||||||
|
FCurrData := FCurrData.Add(Value, FLookback.Value);
|
||||||
|
FData.Value := FCurrData;
|
||||||
|
end;
|
||||||
|
|
||||||
|
{ TChartSeriesProcessor<T> }
|
||||||
|
|
||||||
|
constructor TChartSeriesProcessor<T>.Create(AOwner: TMycChart; const ADataProvider: IMycDataProvider<T>);
|
||||||
|
begin
|
||||||
|
inherited Create(AOwner);
|
||||||
|
FLock := TSpinLock.Create(false);
|
||||||
|
FDataSeries := TMycDataArray<T>.CreateEmpty;
|
||||||
|
FData := FDataSeries;
|
||||||
|
FDataProvider := ADataProvider;
|
||||||
|
FReceiver := TChartSeriesReceiver<T>.Create(AOwner.Lookback);
|
||||||
|
FReceiverTag := FDataProvider.Link(FReceiver);
|
||||||
|
end;
|
||||||
|
|
||||||
|
destructor TChartSeriesProcessor<T>.Destroy;
|
||||||
|
begin
|
||||||
|
FDataProvider.Unlink(FReceiverTag);
|
||||||
|
inherited;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TChartSeriesProcessor<T>.GetCount: Int64;
|
||||||
|
begin
|
||||||
|
Result := FData.Count;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TChartSeriesProcessor<T>.GetTotalCount: Int64;
|
||||||
|
begin
|
||||||
|
Result := FData.TotalCount;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TChartSeriesProcessor<T>.Update;
|
||||||
|
begin
|
||||||
|
FData := FReceiver.Data.Value;
|
||||||
|
end;
|
||||||
|
|
||||||
|
{ TChartLineSeries }
|
||||||
|
|
||||||
|
constructor TChartLineSeries.Create(
|
||||||
|
AOwner: TMycChart;
|
||||||
|
const ADataProvider: IMycDataProvider<Double>;
|
||||||
|
const ALineColor: TAlphaColor;
|
||||||
|
ALineWidth: Single
|
||||||
|
);
|
||||||
|
begin
|
||||||
|
inherited Create(AOwner, ADataProvider);
|
||||||
|
FLineColor := ALineColor;
|
||||||
|
FLineWidth := ALineWidth;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TChartLineSeries.GetValueRange(First, Last: Int64; out MinValue, MaxValue: Double): Boolean;
|
||||||
|
var
|
||||||
|
i: Int64;
|
||||||
|
begin
|
||||||
|
MinValue := MaxDouble;
|
||||||
|
MaxValue := -MaxDouble;
|
||||||
|
for i := First to Last do
|
||||||
|
begin
|
||||||
|
var v := Data.Items[i];
|
||||||
|
if not IsNaN(v) then
|
||||||
|
begin
|
||||||
|
MinValue := Min(MinValue, v);
|
||||||
|
MaxValue := Max(MaxValue, v);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
Result := (MinValue <> MaxDouble);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TChartLineSeries.Paint(const Canvas: TCanvas; First, Last: Int64; const XForm, YForm: TFunc<Double, Single>);
|
||||||
|
var
|
||||||
|
points: TPathData;
|
||||||
|
i, n: Int64;
|
||||||
|
begin
|
||||||
|
points := TPathData.Create;
|
||||||
|
try
|
||||||
|
// Skip warmup data
|
||||||
|
n := First;
|
||||||
|
while (n <= Last) and (IsNaN(Data[n])) do
|
||||||
|
inc(n);
|
||||||
|
if (n > Last) then
|
||||||
|
exit;
|
||||||
|
|
||||||
|
points.MoveTo(TPointF.Create(XForm(n), YForm(Data[n])));
|
||||||
|
for i := n + 1 to Last do
|
||||||
|
if not IsNaN(Data[i]) then
|
||||||
|
points.LineTo(TPointF.Create(XForm(i), YForm(Data[i])));
|
||||||
|
|
||||||
|
Canvas.Stroke.Color := FLineColor;
|
||||||
|
Canvas.Stroke.Thickness := FLineWidth;
|
||||||
|
Canvas.DrawPath(points, 1);
|
||||||
|
finally
|
||||||
|
points.Free;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
{ TChartOhlcSeries }
|
||||||
|
|
||||||
|
constructor TChartOhlcSeries.Create(
|
||||||
|
AOwner: TMycChart;
|
||||||
|
const ADataProvider: IMycDataProvider<TOhlcItem>;
|
||||||
|
const AUpColor, ADownColor: TAlphaColor
|
||||||
|
);
|
||||||
|
begin
|
||||||
|
inherited Create(AOwner, ADataProvider);
|
||||||
|
FUpColor := AUpColor;
|
||||||
|
FDownColor := ADownColor;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TChartOhlcSeries.GetValueRange(First, Last: Int64; out MinValue, MaxValue: Double): Boolean;
|
||||||
|
var
|
||||||
|
i: Int64;
|
||||||
|
begin
|
||||||
|
MinValue := MaxDouble;
|
||||||
|
MaxValue := -MaxDouble;
|
||||||
|
|
||||||
|
for i := First to Last do
|
||||||
|
begin
|
||||||
|
var item := Data.Items[i];
|
||||||
|
MinValue := Min(MinValue, item.Low);
|
||||||
|
MaxValue := Max(MaxValue, item.High);
|
||||||
|
end;
|
||||||
|
Result := (MinValue <> MaxDouble);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TChartOhlcSeries.Paint(const Canvas: TCanvas; First, Last: Int64; const XForm, YForm: TFunc<Double, Single>);
|
||||||
|
var
|
||||||
|
i: Int64;
|
||||||
|
x, candleWidth: Single;
|
||||||
|
yOpen, yHigh, yLow, yClose: Single;
|
||||||
|
item: TOhlcItem;
|
||||||
|
isUp: boolean;
|
||||||
|
begin
|
||||||
|
candleWidth := Max(2, 0.8 * Abs((XForm(First + 1) - XForm(First))));
|
||||||
|
|
||||||
|
for i := First to Last do
|
||||||
|
begin
|
||||||
|
item := Data.Items[i];
|
||||||
|
x := XForm(i);
|
||||||
|
yOpen := YForm(item.Open);
|
||||||
|
yClose := YForm(item.Close);
|
||||||
|
yHigh := YForm(item.High);
|
||||||
|
yLow := YForm(item.Low);
|
||||||
|
isUp := item.Close >= item.Open;
|
||||||
|
|
||||||
|
if isUp then
|
||||||
|
Canvas.Stroke.Color := FUpColor
|
||||||
|
else
|
||||||
|
Canvas.Stroke.Color := FDownColor;
|
||||||
|
Canvas.Stroke.Thickness := 1.0;
|
||||||
|
|
||||||
|
Canvas.DrawLine(TPointF.Create(x, yHigh), TPointF.Create(x, yLow), 1);
|
||||||
|
|
||||||
|
Canvas.Fill.Color := Canvas.Stroke.Color;
|
||||||
|
if isUp then
|
||||||
|
Canvas.FillRect(TRectF.Create(x - candleWidth / 2, yClose, x + candleWidth / 2, yOpen), 0, 0, AllCorners, 1)
|
||||||
|
else
|
||||||
|
Canvas.FillRect(TRectF.Create(x - candleWidth / 2, yOpen, x + candleWidth / 2, yClose), 0, 0, AllCorners, 1);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
end.
|
||||||
@@ -16,7 +16,6 @@ uses
|
|||||||
FMX.Graphics,
|
FMX.Graphics,
|
||||||
FMX.Forms,
|
FMX.Forms,
|
||||||
Myc.Trade.DataPoint,
|
Myc.Trade.DataPoint,
|
||||||
Myc.Signals,
|
|
||||||
Myc.Lazy;
|
Myc.Lazy;
|
||||||
|
|
||||||
type
|
type
|
||||||
@@ -30,10 +29,10 @@ type
|
|||||||
protected
|
protected
|
||||||
function GetCount: Int64; virtual; abstract;
|
function GetCount: Int64; virtual; abstract;
|
||||||
function GetTotalCount: Int64; virtual; abstract;
|
function GetTotalCount: Int64; virtual; abstract;
|
||||||
function GetValueRange(StartIndex, Count: Int64; out Min, Max: Double): Boolean; virtual; abstract;
|
|
||||||
procedure Update; virtual; abstract;
|
procedure Update; virtual; abstract;
|
||||||
|
function GetValueRange(StartIndex, Count: Int64; out Min, Max: Double): Boolean; virtual;
|
||||||
// The signature is changed to support viewport painting with an index offset
|
// 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;
|
||||||
property MainSeries: TSeries read GetMainSeries;
|
property MainSeries: TSeries read GetMainSeries;
|
||||||
public
|
public
|
||||||
constructor Create(AOwner: TMycChart);
|
constructor Create(AOwner: TMycChart);
|
||||||
@@ -44,6 +43,7 @@ type
|
|||||||
|
|
||||||
private
|
private
|
||||||
FSeriesList: TList<TSeries>;
|
FSeriesList: TList<TSeries>;
|
||||||
|
FXAxisSeries: TSeries;
|
||||||
FLookback: TWriteable<Int64>;
|
FLookback: TWriteable<Int64>;
|
||||||
FIdleSubscrId: TMessageSubscriptionId;
|
FIdleSubscrId: TMessageSubscriptionId;
|
||||||
FViewStartIndex: Int64;
|
FViewStartIndex: Int64;
|
||||||
@@ -64,9 +64,14 @@ type
|
|||||||
constructor Create(AOwner: TComponent); override;
|
constructor Create(AOwner: TComponent); override;
|
||||||
destructor Destroy; override;
|
destructor Destroy; override;
|
||||||
|
|
||||||
// Creates an OHLC candlestick/bar series and returns it
|
// 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>): TSeries;
|
||||||
|
|
||||||
|
// 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(
|
function AddOhlcSeries(
|
||||||
const DataProvider: IMycDataProvider<TDataPoint<TOhlcItem>>;
|
const DataProvider: IMycDataProvider<TOhlcItem>;
|
||||||
const AUpColor: TAlphaColor = TAlphaColors.Green;
|
const AUpColor: TAlphaColor = TAlphaColors.Green;
|
||||||
const ADownColor: TAlphaColor = TAlphaColors.Red
|
const ADownColor: TAlphaColor = TAlphaColors.Red
|
||||||
): TSeries;
|
): TSeries;
|
||||||
@@ -85,78 +90,7 @@ implementation
|
|||||||
|
|
||||||
uses
|
uses
|
||||||
System.Math,
|
System.Math,
|
||||||
System.SyncObjs,
|
Myc.FMX.Chart.Series;
|
||||||
WinApi.Windows,
|
|
||||||
Myc.Trade.DataArray;
|
|
||||||
|
|
||||||
type
|
|
||||||
TChartSeriesReceiver<T> = class(TMycProcessor<T>)
|
|
||||||
strict private
|
|
||||||
FCurrData: TMycDataArray<T>;
|
|
||||||
FLookback: TWriteable<Int64>;
|
|
||||||
private
|
|
||||||
FData: TWriteable<TMycDataArray<T>>;
|
|
||||||
protected
|
|
||||||
function ProcessData(const Value: T): Boolean; override;
|
|
||||||
public
|
|
||||||
constructor Create(const ALookback: TWriteable<Int64>);
|
|
||||||
property Data: TWriteable<TMycDataArray<T>> read FData;
|
|
||||||
end;
|
|
||||||
|
|
||||||
TChartSeriesProcessor<T> = class(TMycChart.TSeries)
|
|
||||||
strict private
|
|
||||||
FDataSeries: TMycDataArray<T>;
|
|
||||||
FLock: TSpinLock;
|
|
||||||
private
|
|
||||||
FData: TMycDataArray<T>;
|
|
||||||
FDataProvider: IMycDataProvider<T>;
|
|
||||||
FReceiver: TChartSeriesReceiver<T>;
|
|
||||||
FReceiverTag: TTag;
|
|
||||||
protected
|
|
||||||
function GetCount: Int64; override;
|
|
||||||
function GetTotalCount: Int64; override;
|
|
||||||
procedure Update; override;
|
|
||||||
|
|
||||||
public
|
|
||||||
constructor Create(AOwner: TMycChart; const ADataProvider: IMycDataProvider<T>);
|
|
||||||
destructor Destroy; override;
|
|
||||||
property Data: TMycDataArray<T> read FData;
|
|
||||||
end;
|
|
||||||
|
|
||||||
{ TChartOhlcSeries }
|
|
||||||
TChartOhlcSeries = class(TChartSeriesProcessor<TDataPoint<TOhlcItem>>)
|
|
||||||
private
|
|
||||||
FUpColor: TAlphaColor;
|
|
||||||
FDownColor: TAlphaColor;
|
|
||||||
protected
|
|
||||||
function GetValueRange(First, Last: Int64; out MinValue, MaxValue: Double): Boolean; override;
|
|
||||||
procedure Paint(const Canvas: TCanvas; First, Last: Int64; const XForm, YForm: TFunc<Double, Single>); override;
|
|
||||||
public
|
|
||||||
constructor Create(
|
|
||||||
AOwner: TMycChart;
|
|
||||||
const ADataProvider: IMycDataProvider<TDataPoint<TOhlcItem>>;
|
|
||||||
const AUpColor, ADownColor: TAlphaColor
|
|
||||||
);
|
|
||||||
end;
|
|
||||||
|
|
||||||
{ TChartLineSeries }
|
|
||||||
TChartLineSeries = class(TChartSeriesProcessor<Double>)
|
|
||||||
private
|
|
||||||
FLineColor: TAlphaColor;
|
|
||||||
FLineWidth: Single;
|
|
||||||
protected
|
|
||||||
function GetValueRange(First, Last: Int64; out MinValue, MaxValue: Double): Boolean; override;
|
|
||||||
procedure Paint(const Canvas: TCanvas; First, Last: Int64; const XForm, YForm: TFunc<Double, Single>); override;
|
|
||||||
public
|
|
||||||
constructor Create(
|
|
||||||
AOwner: TMycChart;
|
|
||||||
const ADataProvider: IMycDataProvider<Double>;
|
|
||||||
const ALineColor: TAlphaColor;
|
|
||||||
ALineWidth: Single
|
|
||||||
);
|
|
||||||
end;
|
|
||||||
|
|
||||||
{ TMycChart.TSeries }
|
|
||||||
|
|
||||||
constructor TMycChart.TSeries.Create(AOwner: TMycChart);
|
constructor TMycChart.TSeries.Create(AOwner: TMycChart);
|
||||||
begin
|
begin
|
||||||
@@ -166,9 +100,17 @@ end;
|
|||||||
|
|
||||||
function TMycChart.TSeries.GetMainSeries: TSeries;
|
function TMycChart.TSeries.GetMainSeries: TSeries;
|
||||||
begin
|
begin
|
||||||
Result := nil;
|
Result := Owner.FXAxisSeries;
|
||||||
if (Owner.FSeriesList.Count > 0) then
|
end;
|
||||||
Result := Owner.FSeriesList[0];
|
|
||||||
|
function TMycChart.TSeries.GetValueRange(StartIndex, Count: Int64; out Min, Max: Double): Boolean;
|
||||||
|
begin
|
||||||
|
Result := false;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TMycChart.TSeries.Paint(const Canvas: TCanvas; First, Last: Int64; const XForm, YForm: TFunc<Double, Single>);
|
||||||
|
begin
|
||||||
|
// nothing
|
||||||
end;
|
end;
|
||||||
|
|
||||||
{ TMycChart }
|
{ TMycChart }
|
||||||
@@ -177,6 +119,7 @@ constructor TMycChart.Create(AOwner: TComponent);
|
|||||||
begin
|
begin
|
||||||
inherited Create(AOwner);
|
inherited Create(AOwner);
|
||||||
FSeriesList := TObjectList<TSeries>.Create(true);
|
FSeriesList := TObjectList<TSeries>.Create(true);
|
||||||
|
FXAxisSeries := nil;
|
||||||
|
|
||||||
FLookback := TWriteable<Int64>.CreateWriteable(1000); // Load more data for panning
|
FLookback := TWriteable<Int64>.CreateWriteable(1000); // Load more data for panning
|
||||||
FViewStartIndex := 0;
|
FViewStartIndex := 0;
|
||||||
@@ -193,10 +136,24 @@ destructor TMycChart.Destroy;
|
|||||||
begin
|
begin
|
||||||
TMessageManager.DefaultManager.Unsubscribe(TIdleMessage, FIdleSubscrId);
|
TMessageManager.DefaultManager.Unsubscribe(TIdleMessage, FIdleSubscrId);
|
||||||
|
|
||||||
|
FXAxisSeries.Free;
|
||||||
FSeriesList.Free;
|
FSeriesList.Free;
|
||||||
inherited;
|
inherited;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
function TMycChart.SetXAxisSeries<T>(const DataProvider: IMycDataProvider<T>): TSeries;
|
||||||
|
begin
|
||||||
|
// Free the old one if it exists, and create the new master series
|
||||||
|
FXAxisSeries.Free;
|
||||||
|
|
||||||
|
var counter := TChartSeriesCounter<T>.Create;
|
||||||
|
|
||||||
|
DataProvider.Link(counter);
|
||||||
|
|
||||||
|
FXAxisSeries := TChartSeriesProcessor<Int64>.Create(Self, counter.Sender);
|
||||||
|
Result := FXAxisSeries;
|
||||||
|
end;
|
||||||
|
|
||||||
function TMycChart.AddDoubleSeries(
|
function TMycChart.AddDoubleSeries(
|
||||||
const DataProvider: IMycDataProvider<Double>;
|
const DataProvider: IMycDataProvider<Double>;
|
||||||
const ALineColor: TAlphaColor = TAlphaColors.Cornflowerblue;
|
const ALineColor: TAlphaColor = TAlphaColors.Cornflowerblue;
|
||||||
@@ -208,7 +165,7 @@ begin
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
function TMycChart.AddOhlcSeries(
|
function TMycChart.AddOhlcSeries(
|
||||||
const DataProvider: IMycDataProvider<TDataPoint<TOhlcItem>>;
|
const DataProvider: IMycDataProvider<TOhlcItem>;
|
||||||
const AUpColor: TAlphaColor = TAlphaColors.Green;
|
const AUpColor: TAlphaColor = TAlphaColors.Green;
|
||||||
const ADownColor: TAlphaColor = TAlphaColors.Red
|
const ADownColor: TAlphaColor = TAlphaColors.Red
|
||||||
): TSeries;
|
): TSeries;
|
||||||
@@ -219,18 +176,26 @@ end;
|
|||||||
|
|
||||||
procedure TMycChart.DoIdle;
|
procedure TMycChart.DoIdle;
|
||||||
begin
|
begin
|
||||||
if (FSeriesList.Count = 0) then
|
// An X-axis series is required for any operation
|
||||||
|
if not Assigned(FXAxisSeries) then
|
||||||
exit;
|
exit;
|
||||||
|
|
||||||
var prevTotalCount := FSeriesList[0].TotalCount;
|
// First update the master series
|
||||||
|
var prevTotalCount := FXAxisSeries.TotalCount;
|
||||||
|
FXAxisSeries.Update;
|
||||||
|
|
||||||
|
// Check if the master series itself has changed
|
||||||
|
var newTotalCount := FXAxisSeries.TotalCount;
|
||||||
|
var seriesChanged := prevTotalCount <> newTotalCount;
|
||||||
|
|
||||||
|
// Repaint if the last bar is visible, so we display live data
|
||||||
|
var seriesRepaint := FViewStartIndex = 0;
|
||||||
|
|
||||||
var seriesRepaint := false;
|
|
||||||
var seriesChanged := false;
|
|
||||||
for var series in FSeriesList do
|
for var series in FSeriesList do
|
||||||
begin
|
begin
|
||||||
var seriesCount := series.TotalCount;
|
var seriesCount := series.TotalCount;
|
||||||
|
|
||||||
// Check if this series may need a repaint
|
// Check if this series may need a repaint (if it lags behind the master series and these lags are currently visible)
|
||||||
if FViewStartIndex <= prevTotalCount - seriesCount then
|
if FViewStartIndex <= prevTotalCount - seriesCount then
|
||||||
seriesRepaint := true;
|
seriesRepaint := true;
|
||||||
|
|
||||||
@@ -241,16 +206,15 @@ begin
|
|||||||
seriesChanged := true;
|
seriesChanged := true;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// Don't move with live data, if the last bar isn't visible
|
// Don't move chart, if the last bar isn't visible
|
||||||
if FViewStartIndex > 0 then
|
if FViewStartIndex > 0 then
|
||||||
begin
|
begin
|
||||||
var newTotalCount := FSeriesList[0].TotalCount;
|
|
||||||
var countDelta := newTotalCount - prevTotalCount;
|
var 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
|
// 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;
|
||||||
@@ -339,7 +303,8 @@ begin
|
|||||||
exit;
|
exit;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
if (FSeriesList.Count = 0) or (FViewCount <= 0) then
|
// An X-axis series is required for panning
|
||||||
|
if not Assigned(FXAxisSeries) or (FViewCount <= 0) then
|
||||||
begin
|
begin
|
||||||
exit;
|
exit;
|
||||||
end;
|
end;
|
||||||
@@ -352,7 +317,7 @@ begin
|
|||||||
FViewStartIndex := FViewStartIndex + indexDelta;
|
FViewStartIndex := FViewStartIndex + indexDelta;
|
||||||
|
|
||||||
// Clamp values
|
// Clamp values
|
||||||
mainSeries := FSeriesList[0];
|
mainSeries := FXAxisSeries;
|
||||||
maxIndex := mainSeries.Count - FViewCount;
|
maxIndex := mainSeries.Count - FViewCount;
|
||||||
if (maxIndex < 0) then
|
if (maxIndex < 0) then
|
||||||
maxIndex := 0;
|
maxIndex := 0;
|
||||||
@@ -380,9 +345,11 @@ var
|
|||||||
begin
|
begin
|
||||||
Handled := true;
|
Handled := true;
|
||||||
|
|
||||||
if (FSeriesList.Count = 0) then
|
// An X-axis series is required for zooming
|
||||||
|
if not Assigned(FXAxisSeries) then
|
||||||
exit;
|
exit;
|
||||||
mainSeries := FSeriesList[0];
|
|
||||||
|
mainSeries := FXAxisSeries;
|
||||||
if (mainSeries.Count = 0) or (Width <= 0) then
|
if (mainSeries.Count = 0) or (Width <= 0) then
|
||||||
exit;
|
exit;
|
||||||
|
|
||||||
@@ -456,7 +423,8 @@ begin
|
|||||||
inherited;
|
inherited;
|
||||||
rect := Self.LocalRect;
|
rect := Self.LocalRect;
|
||||||
|
|
||||||
if (FSeriesList.Count = 0) or (FViewCount <= 1) then
|
// The chart requires an X-axis series to be set before it can draw anything.
|
||||||
|
if not Assigned(FXAxisSeries) or (FXAxisSeries.Count <= 1) or (FViewCount <= 1) then
|
||||||
begin
|
begin
|
||||||
Canvas.Fill.Color := TAlphaColors.Gray;
|
Canvas.Fill.Color := TAlphaColors.Gray;
|
||||||
Canvas.FillText(rect, 'No Data', false, 1, [], TTextAlign.Center, TTextAlign.Center);
|
Canvas.FillText(rect, 'No Data', false, 1, [], TTextAlign.Center, TTextAlign.Center);
|
||||||
@@ -464,7 +432,7 @@ begin
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
rangeInitialized := false;
|
rangeInitialized := false;
|
||||||
mainSeries := FSeriesList[0];
|
mainSeries := FXAxisSeries;
|
||||||
|
|
||||||
masterTotalCount := mainSeries.TotalCount;
|
masterTotalCount := mainSeries.TotalCount;
|
||||||
for series in FSeriesList do
|
for series in FSeriesList do
|
||||||
@@ -566,179 +534,4 @@ begin
|
|||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
{ TChartSeriesReceiver<T> }
|
|
||||||
|
|
||||||
constructor TChartSeriesReceiver<T>.Create(const ALookback: TWriteable<Int64>);
|
|
||||||
begin
|
|
||||||
inherited Create;
|
|
||||||
FLookback := ALookback;
|
|
||||||
FCurrData := TMycDataArray<T>.CreateEmpty;
|
|
||||||
FData := TWriteable<TMycDataArray<T>>.CreateWriteable(FCurrData).Protect;
|
|
||||||
end;
|
|
||||||
|
|
||||||
function TChartSeriesReceiver<T>.ProcessData(const Value: T): Boolean;
|
|
||||||
begin
|
|
||||||
Result := true;
|
|
||||||
FCurrData := FCurrData.Add(Value, FLookback.Value);
|
|
||||||
FData.Value := FCurrData;
|
|
||||||
end;
|
|
||||||
|
|
||||||
{ TChartSeriesProcessor<T> }
|
|
||||||
|
|
||||||
constructor TChartSeriesProcessor<T>.Create(AOwner: TMycChart; const ADataProvider: IMycDataProvider<T>);
|
|
||||||
begin
|
|
||||||
inherited Create(AOwner);
|
|
||||||
FLock := TSpinLock.Create(false);
|
|
||||||
FDataSeries := TMycDataArray<T>.CreateEmpty;
|
|
||||||
FData := FDataSeries;
|
|
||||||
FDataProvider := ADataProvider;
|
|
||||||
FReceiver := TChartSeriesReceiver<T>.Create(AOwner.FLookback);
|
|
||||||
FReceiverTag := FDataProvider.Link(FReceiver);
|
|
||||||
end;
|
|
||||||
|
|
||||||
destructor TChartSeriesProcessor<T>.Destroy;
|
|
||||||
begin
|
|
||||||
FDataProvider.Unlink(FReceiverTag);
|
|
||||||
inherited;
|
|
||||||
end;
|
|
||||||
|
|
||||||
function TChartSeriesProcessor<T>.GetCount: Int64;
|
|
||||||
begin
|
|
||||||
Result := FData.Count;
|
|
||||||
end;
|
|
||||||
|
|
||||||
function TChartSeriesProcessor<T>.GetTotalCount: Int64;
|
|
||||||
begin
|
|
||||||
Result := FData.TotalCount;
|
|
||||||
end;
|
|
||||||
|
|
||||||
procedure TChartSeriesProcessor<T>.Update;
|
|
||||||
begin
|
|
||||||
FData := FReceiver.Data.Value;
|
|
||||||
end;
|
|
||||||
|
|
||||||
{ TChartOhlcSeries }
|
|
||||||
|
|
||||||
constructor TChartOhlcSeries.Create(
|
|
||||||
AOwner: TMycChart;
|
|
||||||
const ADataProvider: IMycDataProvider<TDataPoint<TOhlcItem>>;
|
|
||||||
const AUpColor, ADownColor: TAlphaColor
|
|
||||||
);
|
|
||||||
begin
|
|
||||||
inherited Create(AOwner, ADataProvider);
|
|
||||||
FUpColor := AUpColor;
|
|
||||||
FDownColor := ADownColor;
|
|
||||||
end;
|
|
||||||
|
|
||||||
function TChartOhlcSeries.GetValueRange(First, Last: Int64; out MinValue, MaxValue: Double): Boolean;
|
|
||||||
var
|
|
||||||
i: Int64;
|
|
||||||
begin
|
|
||||||
MinValue := MaxDouble;
|
|
||||||
MaxValue := -MaxDouble;
|
|
||||||
|
|
||||||
for i := First to Last do
|
|
||||||
begin
|
|
||||||
var dp := Data.Items[i];
|
|
||||||
MinValue := Min(MinValue, dp.Data.Low);
|
|
||||||
MaxValue := Max(MaxValue, dp.Data.High);
|
|
||||||
end;
|
|
||||||
Result := (MinValue <> MaxDouble);
|
|
||||||
end;
|
|
||||||
|
|
||||||
procedure TChartOhlcSeries.Paint(const Canvas: TCanvas; First, Last: Int64; const XForm, YForm: TFunc<Double, Single>);
|
|
||||||
var
|
|
||||||
i: Int64;
|
|
||||||
x, candleWidth: Single;
|
|
||||||
yOpen, yHigh, yLow, yClose: Single;
|
|
||||||
item: TDataPoint<TOhlcItem>;
|
|
||||||
isUp: boolean;
|
|
||||||
begin
|
|
||||||
candleWidth := Max(2, 0.8 * Abs((XForm(First + 1) - XForm(First))));
|
|
||||||
|
|
||||||
for i := First to Last do
|
|
||||||
begin
|
|
||||||
item := Data.Items[i];
|
|
||||||
x := XForm(i);
|
|
||||||
yOpen := YForm(item.Data.Open);
|
|
||||||
yClose := YForm(item.Data.Close);
|
|
||||||
yHigh := YForm(item.Data.High);
|
|
||||||
yLow := YForm(item.Data.Low);
|
|
||||||
isUp := item.Data.Close >= item.Data.Open;
|
|
||||||
|
|
||||||
if isUp then
|
|
||||||
Canvas.Stroke.Color := FUpColor
|
|
||||||
else
|
|
||||||
Canvas.Stroke.Color := FDownColor;
|
|
||||||
Canvas.Stroke.Thickness := 1.0;
|
|
||||||
|
|
||||||
Canvas.DrawLine(TPointF.Create(x, yHigh), TPointF.Create(x, yLow), 1);
|
|
||||||
|
|
||||||
Canvas.Fill.Color := Canvas.Stroke.Color;
|
|
||||||
if isUp then
|
|
||||||
Canvas.FillRect(TRectF.Create(x - candleWidth / 2, yClose, x + candleWidth / 2, yOpen), 0, 0, AllCorners, 1)
|
|
||||||
else
|
|
||||||
Canvas.FillRect(TRectF.Create(x - candleWidth / 2, yOpen, x + candleWidth / 2, yClose), 0, 0, AllCorners, 1);
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
|
|
||||||
{ TChartLineSeries }
|
|
||||||
|
|
||||||
constructor TChartLineSeries.Create(
|
|
||||||
AOwner: TMycChart;
|
|
||||||
const ADataProvider: IMycDataProvider<Double>;
|
|
||||||
const ALineColor: TAlphaColor;
|
|
||||||
ALineWidth: Single
|
|
||||||
);
|
|
||||||
begin
|
|
||||||
inherited Create(AOwner, ADataProvider);
|
|
||||||
FLineColor := ALineColor;
|
|
||||||
FLineWidth := ALineWidth;
|
|
||||||
end;
|
|
||||||
|
|
||||||
function TChartLineSeries.GetValueRange(First, Last: Int64; out MinValue, MaxValue: Double): Boolean;
|
|
||||||
var
|
|
||||||
i: Int64;
|
|
||||||
begin
|
|
||||||
MinValue := MaxDouble;
|
|
||||||
MaxValue := -MaxDouble;
|
|
||||||
for i := First to Last do
|
|
||||||
begin
|
|
||||||
var v := Data.Items[i];
|
|
||||||
if not IsNaN(v) then
|
|
||||||
begin
|
|
||||||
MinValue := Min(MinValue, v);
|
|
||||||
MaxValue := Max(MaxValue, v);
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
Result := (MinValue <> MaxDouble);
|
|
||||||
end;
|
|
||||||
|
|
||||||
procedure TChartLineSeries.Paint(const Canvas: TCanvas; First, Last: Int64; const XForm, YForm: TFunc<Double, Single>);
|
|
||||||
var
|
|
||||||
points: TPathData;
|
|
||||||
i, n: Int64;
|
|
||||||
begin
|
|
||||||
points := TPathData.Create;
|
|
||||||
try
|
|
||||||
// Skip warmup data
|
|
||||||
n := First;
|
|
||||||
while (n <= Last) and (IsNaN(Data[n])) do
|
|
||||||
inc(n);
|
|
||||||
if (n > Last) then
|
|
||||||
exit;
|
|
||||||
|
|
||||||
points.MoveTo(TPointF.Create(XForm(n), YForm(Data[n])));
|
|
||||||
for i := n + 1 to Last do
|
|
||||||
if not IsNaN(Data[i]) then
|
|
||||||
points.LineTo(TPointF.Create(XForm(i), YForm(Data[i])));
|
|
||||||
|
|
||||||
Canvas.Stroke.Color := FLineColor;
|
|
||||||
Canvas.Stroke.Thickness := FLineWidth;
|
|
||||||
Canvas.DrawPath(points, 1);
|
|
||||||
finally
|
|
||||||
points.Free;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
|
|
||||||
end.
|
end.
|
||||||
@@ -66,7 +66,7 @@ type
|
|||||||
private
|
private
|
||||||
FProc: TProc;
|
FProc: TProc;
|
||||||
protected
|
protected
|
||||||
function ProcessData(const Value: T): Boolean; override;
|
function ProcessData(const Value: T): Boolean; override; final;
|
||||||
public
|
public
|
||||||
constructor Create(const AProc: TProc);
|
constructor Create(const AProc: TProc);
|
||||||
end;
|
end;
|
||||||
@@ -81,6 +81,27 @@ type
|
|||||||
constructor Create(const Controller: IInterface; const AProc: TProc);
|
constructor Create(const Controller: IInterface; const AProc: TProc);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
IMycConverter<S, T> = interface(IMycProcessor<S>)
|
||||||
|
function GetSender: IMycDataProvider<T>;
|
||||||
|
property Sender: IMycDataProvider<T> read GetSender;
|
||||||
|
end;
|
||||||
|
|
||||||
|
TMycConverter<S, T> = class abstract(TMycProcessor<S>, IMycConverter<S, T>)
|
||||||
|
private
|
||||||
|
FSender: TMycDataProvider<T>;
|
||||||
|
function GetSender: IMycDataProvider<T>;
|
||||||
|
protected
|
||||||
|
function ProcessData(const Value: S): Boolean; override; abstract;
|
||||||
|
|
||||||
|
// Broadcasts the given data to all linked processors.
|
||||||
|
procedure Broadcast(const Value: T);
|
||||||
|
|
||||||
|
public
|
||||||
|
constructor Create;
|
||||||
|
destructor Destroy; override;
|
||||||
|
property Sender: IMycDataProvider<T> read GetSender;
|
||||||
|
end;
|
||||||
|
|
||||||
// An immutable time-ordered series of data points.
|
// An immutable time-ordered series of data points.
|
||||||
// The most recent element has the logical index 0.
|
// The most recent element has the logical index 0.
|
||||||
IDataSeries<T> = interface
|
IDataSeries<T> = interface
|
||||||
@@ -508,4 +529,29 @@ begin
|
|||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
{ TMycConverter<S, T> }
|
||||||
|
|
||||||
|
constructor TMycConverter<S, T>.Create;
|
||||||
|
begin
|
||||||
|
inherited Create;
|
||||||
|
FSender := TMycDataProvider<T>.Create(Self);
|
||||||
|
end;
|
||||||
|
|
||||||
|
destructor TMycConverter<S, T>.Destroy;
|
||||||
|
begin
|
||||||
|
FSender.Free;
|
||||||
|
inherited Destroy;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TMycConverter<S, T>.Broadcast(const Value: T);
|
||||||
|
begin
|
||||||
|
var cValue := Value;
|
||||||
|
FSender.Notify(function(const Processor: IMycProcessor<T>): Boolean begin Result := Processor.ProcessData(cValue) end);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TMycConverter<S, T>.GetSender: IMycDataProvider<T>;
|
||||||
|
begin
|
||||||
|
Result := FSender;
|
||||||
|
end;
|
||||||
|
|
||||||
end.
|
end.
|
||||||
|
|||||||
Reference in New Issue
Block a user