Chart: Mutable parameters
This commit is contained in:
@@ -11,7 +11,6 @@ uses
|
||||
TestModule in 'TestModule.pas',
|
||||
DynamicFMXControl in 'DynamicFMXControl.pas',
|
||||
FirstStrategy in 'FirstStrategy.pas',
|
||||
Myc.Fmx.Chart in 'Myc.Fmx.Chart.pas',
|
||||
Myc.Trade.DataArray in '..\Src\Myc.Trade.DataArray.pas',
|
||||
Myc.FMX.Chart.Series in '..\Src\Myc.FMX.Chart.Series.pas';
|
||||
|
||||
|
||||
@@ -139,7 +139,6 @@
|
||||
<DCCReference Include="TestModule.pas"/>
|
||||
<DCCReference Include="DynamicFMXControl.pas"/>
|
||||
<DCCReference Include="FirstStrategy.pas"/>
|
||||
<DCCReference Include="Myc.Fmx.Chart.pas"/>
|
||||
<DCCReference Include="..\Src\Myc.Trade.DataArray.pas"/>
|
||||
<DCCReference Include="..\Src\Myc.FMX.Chart.Series.pas"/>
|
||||
<BuildConfiguration Include="Base">
|
||||
|
||||
+10
-88
@@ -12,8 +12,11 @@ uses
|
||||
type
|
||||
TMycNullMutable<T> = class(TInterfacedObject, TMutable<T>.IMutable)
|
||||
private
|
||||
FValue: T;
|
||||
function GetChanged: TSignal;
|
||||
function GetValue: T;
|
||||
public
|
||||
constructor Create(AValue: T);
|
||||
end;
|
||||
|
||||
TMycMutableBase<T> = class(TInterfacedObject, TMutable<T>.IMutable)
|
||||
@@ -50,35 +53,6 @@ type
|
||||
procedure SetValue(const Value: T);
|
||||
end;
|
||||
|
||||
TMycNullLazy<T> = class(TInterfacedObject, TLazy<T>.ILazy)
|
||||
protected
|
||||
function GetChanged: TState;
|
||||
public
|
||||
function Pop(out Res: T): Boolean;
|
||||
end;
|
||||
|
||||
TMycLazyBase<T> = class(TInterfacedObject, TLazy<T>.ILazy)
|
||||
strict private
|
||||
FChanged: TFlag;
|
||||
FChangeState: TSignal.TSubscription;
|
||||
function GetChanged: TState;
|
||||
protected
|
||||
function GetValue: T; virtual; abstract;
|
||||
public
|
||||
constructor Create(const AChanged: TSignal);
|
||||
destructor Destroy; override;
|
||||
function Pop(out Res: T): Boolean;
|
||||
end;
|
||||
|
||||
TMycFuncLazy<T> = class(TMycLazyBase<T>)
|
||||
private
|
||||
FProc: TFunc<T>;
|
||||
protected
|
||||
function GetValue: T; override;
|
||||
public
|
||||
constructor Create(const AChanged: TSignal.ISignal; const AProc: TFunc<T>);
|
||||
end;
|
||||
|
||||
TMycProtectedMutable<T> = class(TInterfacedObject, TMutable<T>.IMutable, TWriteable<T>.IWriteable)
|
||||
private
|
||||
FWriteable: TWriteable<T>.IWriteable;
|
||||
@@ -97,6 +71,12 @@ implementation
|
||||
uses
|
||||
System.Generics.Defaults;
|
||||
|
||||
constructor TMycNullMutable<T>.Create(AValue: T);
|
||||
begin
|
||||
inherited Create;
|
||||
FValue := AValue;
|
||||
end;
|
||||
|
||||
{ TMycNullMutable<T> }
|
||||
|
||||
function TMycNullMutable<T>.GetChanged: TSignal;
|
||||
@@ -106,7 +86,7 @@ end;
|
||||
|
||||
function TMycNullMutable<T>.GetValue: T;
|
||||
begin
|
||||
Result := Default(T);
|
||||
Result := FValue;
|
||||
end;
|
||||
|
||||
{ TMycMutableBase<T> }
|
||||
@@ -163,64 +143,6 @@ begin
|
||||
end;
|
||||
end;
|
||||
|
||||
{ TMycNullLazy<T> }
|
||||
|
||||
function TMycNullLazy<T>.GetChanged: TState;
|
||||
begin
|
||||
Result := TState.Null;
|
||||
end;
|
||||
|
||||
function TMycNullLazy<T>.Pop(out Res: T): Boolean;
|
||||
begin
|
||||
Res := Default(T);
|
||||
Result := true;
|
||||
end;
|
||||
|
||||
{ TMycLazyBase<T> }
|
||||
|
||||
constructor TMycLazyBase<T>.Create(const AChanged: TSignal);
|
||||
begin
|
||||
inherited Create;
|
||||
FChanged := TFlag.CreateFlag;
|
||||
FChanged.Notify;
|
||||
FChangeState := AChanged.Subscribe(FChanged);
|
||||
end;
|
||||
|
||||
destructor TMycLazyBase<T>.Destroy;
|
||||
begin
|
||||
FChangeState.Unsubscribe;
|
||||
inherited;
|
||||
end;
|
||||
|
||||
function TMycLazyBase<T>.GetChanged: TState;
|
||||
begin
|
||||
Result := FChanged.State;
|
||||
end;
|
||||
|
||||
function TMycLazyBase<T>.Pop(out Res: T): Boolean;
|
||||
begin
|
||||
Result := FChanged.State.IsSet;
|
||||
if Result then
|
||||
begin
|
||||
Res := GetValue;
|
||||
FChanged.Reset;
|
||||
end;
|
||||
end;
|
||||
|
||||
{ TMycFuncLazy<T> }
|
||||
|
||||
constructor TMycFuncLazy<T>.Create(const AChanged: TSignal.ISignal; const AProc: TFunc<T>);
|
||||
begin
|
||||
inherited Create(AChanged);
|
||||
FProc := AProc;
|
||||
Assert(Assigned(FProc));
|
||||
end;
|
||||
|
||||
function TMycFuncLazy<T>.GetValue: T;
|
||||
begin
|
||||
Result := FProc();
|
||||
end;
|
||||
|
||||
constructor TMycFuncMutable<T>.Create(const AChanged: TSignal; const AProc: TFunc<T>);
|
||||
begin
|
||||
inherited Create(AChanged);
|
||||
|
||||
@@ -7,9 +7,10 @@ uses
|
||||
System.SyncObjs,
|
||||
System.UITypes,
|
||||
FMX.Graphics,
|
||||
Myc.Signals,
|
||||
Myc.Lazy,
|
||||
Myc.Trade.DataArray,
|
||||
Myc.Trade.DataPoint,
|
||||
Myc.Lazy,
|
||||
Myc.Fmx.Chart;
|
||||
|
||||
type
|
||||
@@ -25,13 +26,13 @@ type
|
||||
TChartSeriesReceiver<T> = class(TMycProcessor<T>)
|
||||
strict private
|
||||
FCurrData: TMycDataArray<T>;
|
||||
FLookback: TWriteable<Int64>;
|
||||
FLookback: TMutable<Int64>;
|
||||
private
|
||||
FData: TWriteable<TMycDataArray<T>>;
|
||||
protected
|
||||
function ProcessData(const Value: T): Boolean; override;
|
||||
public
|
||||
constructor Create(const ALookback: TWriteable<Int64>);
|
||||
constructor Create(const ALookback: TMutable<Int64>);
|
||||
property Data: TWriteable<TMycDataArray<T>> read FData;
|
||||
end;
|
||||
|
||||
@@ -47,28 +48,46 @@ type
|
||||
protected
|
||||
function GetCount: Int64; override;
|
||||
function GetTotalCount: Int64; override;
|
||||
procedure Update; override;
|
||||
procedure Update;
|
||||
|
||||
public
|
||||
constructor Create(AOwner: TMycChart; const ADataProvider: IMycDataProvider<T>);
|
||||
constructor Create(const ADataProvider: IMycDataProvider<T>; const ALookback: TMutable<Int64>);
|
||||
destructor Destroy; override;
|
||||
property Data: TMycDataArray<T> read FData;
|
||||
end;
|
||||
|
||||
{ TChartOhlcSeries }
|
||||
TChartOhlcSeries = class(TChartSeriesProcessor<TOhlcItem>)
|
||||
{ TChartCustomLayer }
|
||||
TChartCustomLayer<T> = class(TMycChart.TLayer)
|
||||
private
|
||||
FUpColor: TAlphaColor;
|
||||
FDownColor: TAlphaColor;
|
||||
FSeries: TChartSeriesProcessor<T>;
|
||||
protected
|
||||
function GetSeries: TMycChart.TSeries; override;
|
||||
function GetValueRange(First, Last: Int64; out MinValue, MaxValue: Double): Boolean; override;
|
||||
procedure Update; override;
|
||||
procedure Paint(const Canvas: TCanvas; First, Last: Int64; const XForm, YForm: TFunc<Double, Single>); override;
|
||||
public
|
||||
constructor Create(AOwner: TMycChart; const ADataProvider: IMycDataProvider<T>);
|
||||
destructor Destroy; override;
|
||||
end;
|
||||
|
||||
{ TChartOhlcLayer }
|
||||
TChartOhlcLayer = class(TChartCustomLayer<TOhlcItem>)
|
||||
private
|
||||
FUpColor: TMutable<TAlphaColor>;
|
||||
FDownColor: TMutable<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);
|
||||
constructor Create(
|
||||
AOwner: TMycChart;
|
||||
const ADataProvider: IMycDataProvider<TOhlcItem>;
|
||||
const AUpColor, ADownColor: TMutable<TAlphaColor>
|
||||
);
|
||||
end;
|
||||
|
||||
{ TChartLineSeries }
|
||||
TChartLineSeries = class(TChartSeriesProcessor<Double>)
|
||||
{ TChartLineLayer }
|
||||
TChartLineLayer = class(TChartCustomLayer<Double>)
|
||||
private
|
||||
FLineColor: TAlphaColor;
|
||||
FLineWidth: Single;
|
||||
@@ -106,7 +125,7 @@ end;
|
||||
|
||||
{ TChartSeriesReceiver<T> }
|
||||
|
||||
constructor TChartSeriesReceiver<T>.Create(const ALookback: TWriteable<Int64>);
|
||||
constructor TChartSeriesReceiver<T>.Create(const ALookback: TMutable<Int64>);
|
||||
begin
|
||||
inherited Create;
|
||||
FLookback := ALookback;
|
||||
@@ -123,14 +142,14 @@ end;
|
||||
|
||||
{ TChartSeriesProcessor<T> }
|
||||
|
||||
constructor TChartSeriesProcessor<T>.Create(AOwner: TMycChart; const ADataProvider: IMycDataProvider<T>);
|
||||
constructor TChartSeriesProcessor<T>.Create(const ADataProvider: IMycDataProvider<T>; const ALookback: TMutable<Int64>);
|
||||
begin
|
||||
inherited Create(AOwner);
|
||||
inherited Create;
|
||||
FDataProvider := ADataProvider;
|
||||
FLock := TSpinLock.Create(false);
|
||||
FDataSeries := TMycDataArray<T>.CreateEmpty;
|
||||
FData := FDataSeries;
|
||||
FDataProvider := ADataProvider;
|
||||
FReceiver := TChartSeriesReceiver<T>.Create(AOwner.Lookback);
|
||||
FReceiver := TChartSeriesReceiver<T>.Create(ALookback);
|
||||
FReceiverTag := FDataProvider.Link(FReceiver);
|
||||
end;
|
||||
|
||||
@@ -155,9 +174,9 @@ begin
|
||||
FData := FReceiver.Data.Value;
|
||||
end;
|
||||
|
||||
{ TChartLineSeries }
|
||||
{ TChartLineLayer }
|
||||
|
||||
constructor TChartLineSeries.Create(
|
||||
constructor TChartLineLayer.Create(
|
||||
AOwner: TMycChart;
|
||||
const ADataProvider: IMycDataProvider<Double>;
|
||||
const ALineColor: TAlphaColor;
|
||||
@@ -169,7 +188,7 @@ begin
|
||||
FLineWidth := ALineWidth;
|
||||
end;
|
||||
|
||||
function TChartLineSeries.GetValueRange(First, Last: Int64; out MinValue, MaxValue: Double): Boolean;
|
||||
function TChartLineLayer.GetValueRange(First, Last: Int64; out MinValue, MaxValue: Double): Boolean;
|
||||
var
|
||||
i: Int64;
|
||||
begin
|
||||
@@ -177,7 +196,7 @@ begin
|
||||
MaxValue := -MaxDouble;
|
||||
for i := First to Last do
|
||||
begin
|
||||
var v := Data.Items[i];
|
||||
var v := FSeries.Data.Items[i];
|
||||
if not IsNaN(v) then
|
||||
begin
|
||||
MinValue := Min(MinValue, v);
|
||||
@@ -187,7 +206,7 @@ begin
|
||||
Result := (MinValue <> MaxDouble);
|
||||
end;
|
||||
|
||||
procedure TChartLineSeries.Paint(const Canvas: TCanvas; First, Last: Int64; const XForm, YForm: TFunc<Double, Single>);
|
||||
procedure TChartLineLayer.Paint(const Canvas: TCanvas; First, Last: Int64; const XForm, YForm: TFunc<Double, Single>);
|
||||
var
|
||||
points: TPathData;
|
||||
i, n: Int64;
|
||||
@@ -196,15 +215,15 @@ begin
|
||||
try
|
||||
// Skip warmup data
|
||||
n := First;
|
||||
while (n <= Last) and (IsNaN(Data[n])) do
|
||||
while (n <= Last) and (IsNaN(FSeries.Data[n])) do
|
||||
inc(n);
|
||||
if (n > Last) then
|
||||
exit;
|
||||
|
||||
points.MoveTo(TPointF.Create(XForm(n), YForm(Data[n])));
|
||||
points.MoveTo(TPointF.Create(XForm(n), YForm(FSeries.Data[n])));
|
||||
for i := n + 1 to Last do
|
||||
if not IsNaN(Data[i]) then
|
||||
points.LineTo(TPointF.Create(XForm(i), YForm(Data[i])));
|
||||
if not IsNaN(FSeries.Data[i]) then
|
||||
points.LineTo(TPointF.Create(XForm(i), YForm(FSeries.Data[i])));
|
||||
|
||||
Canvas.Stroke.Color := FLineColor;
|
||||
Canvas.Stroke.Thickness := FLineWidth;
|
||||
@@ -214,20 +233,23 @@ begin
|
||||
end;
|
||||
end;
|
||||
|
||||
{ TChartOhlcSeries }
|
||||
{ TChartOhlcLayer }
|
||||
|
||||
constructor TChartOhlcSeries.Create(
|
||||
constructor TChartOhlcLayer.Create(
|
||||
AOwner: TMycChart;
|
||||
const ADataProvider: IMycDataProvider<TOhlcItem>;
|
||||
const AUpColor, ADownColor: TAlphaColor
|
||||
const AUpColor, ADownColor: TMutable<TAlphaColor>
|
||||
);
|
||||
begin
|
||||
inherited Create(AOwner, ADataProvider);
|
||||
FUpColor := AUpColor;
|
||||
FDownColor := ADownColor;
|
||||
|
||||
FUpColor.Changed.Subscribe(Owner.NeedRepaint);
|
||||
FDownColor.Changed.Subscribe(Owner.NeedRepaint);
|
||||
end;
|
||||
|
||||
function TChartOhlcSeries.GetValueRange(First, Last: Int64; out MinValue, MaxValue: Double): Boolean;
|
||||
function TChartOhlcLayer.GetValueRange(First, Last: Int64; out MinValue, MaxValue: Double): Boolean;
|
||||
var
|
||||
i: Int64;
|
||||
begin
|
||||
@@ -236,14 +258,14 @@ begin
|
||||
|
||||
for i := First to Last do
|
||||
begin
|
||||
var item := Data.Items[i];
|
||||
var item := FSeries.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>);
|
||||
procedure TChartOhlcLayer.Paint(const Canvas: TCanvas; First, Last: Int64; const XForm, YForm: TFunc<Double, Single>);
|
||||
var
|
||||
i: Int64;
|
||||
x, candleWidth: Single;
|
||||
@@ -255,7 +277,7 @@ begin
|
||||
|
||||
for i := First to Last do
|
||||
begin
|
||||
item := Data.Items[i];
|
||||
item := FSeries.Data.Items[i];
|
||||
x := XForm(i);
|
||||
yOpen := YForm(item.Open);
|
||||
yClose := YForm(item.Close);
|
||||
@@ -264,9 +286,9 @@ begin
|
||||
isUp := item.Close >= item.Open;
|
||||
|
||||
if isUp then
|
||||
Canvas.Stroke.Color := FUpColor
|
||||
Canvas.Stroke.Color := FUpColor.Value
|
||||
else
|
||||
Canvas.Stroke.Color := FDownColor;
|
||||
Canvas.Stroke.Color := FDownColor.Value;
|
||||
Canvas.Stroke.Thickness := 1.0;
|
||||
|
||||
Canvas.DrawLine(TPointF.Create(x, yHigh), TPointF.Create(x, yLow), 1);
|
||||
@@ -279,4 +301,38 @@ begin
|
||||
end;
|
||||
end;
|
||||
|
||||
{ TChartCustomLayer }
|
||||
|
||||
constructor TChartCustomLayer<T>.Create(AOwner: TMycChart; const ADataProvider: IMycDataProvider<T>);
|
||||
begin
|
||||
inherited Create(AOwner);
|
||||
FSeries := TChartSeriesProcessor<T>.Create(ADataProvider, AOwner.Lookback.AsMutable);
|
||||
end;
|
||||
|
||||
destructor TChartCustomLayer<T>.Destroy;
|
||||
begin
|
||||
FSeries.Free;
|
||||
inherited;
|
||||
end;
|
||||
|
||||
function TChartCustomLayer<T>.GetSeries: TMycChart.TSeries;
|
||||
begin
|
||||
Result := FSeries;
|
||||
end;
|
||||
|
||||
function TChartCustomLayer<T>.GetValueRange(First, Last: Int64; out MinValue, MaxValue: Double): Boolean;
|
||||
begin
|
||||
Result := false;
|
||||
end;
|
||||
|
||||
procedure TChartCustomLayer<T>.Paint(const Canvas: TCanvas; First, Last: Int64; const XForm, YForm: TFunc<Double, Single>);
|
||||
begin
|
||||
// nope
|
||||
end;
|
||||
|
||||
procedure TChartCustomLayer<T>.Update;
|
||||
begin
|
||||
FSeries.Update;
|
||||
end;
|
||||
|
||||
end.
|
||||
|
||||
+65
-68
@@ -16,6 +16,7 @@ uses
|
||||
FMX.Graphics,
|
||||
FMX.Forms,
|
||||
Myc.Trade.DataPoint,
|
||||
Myc.Signals,
|
||||
Myc.Lazy;
|
||||
|
||||
type
|
||||
@@ -23,27 +24,33 @@ type
|
||||
public
|
||||
type
|
||||
TSeries = class abstract(TObject)
|
||||
private
|
||||
FOwner: TMycChart;
|
||||
function GetMainSeries: TSeries;
|
||||
protected
|
||||
function GetCount: Int64; virtual; abstract;
|
||||
function GetTotalCount: Int64; 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
|
||||
procedure Paint(const Canvas: TCanvas; First, Last: Int64; const XForm, YForm: TFunc<Double, Single>); virtual;
|
||||
property MainSeries: TSeries read GetMainSeries;
|
||||
public
|
||||
constructor Create(AOwner: TMycChart);
|
||||
property Count: Int64 read GetCount;
|
||||
property Owner: TMycChart read FOwner;
|
||||
property TotalCount: Int64 read GetTotalCount;
|
||||
end;
|
||||
|
||||
TLayer = class abstract(TObject)
|
||||
private
|
||||
FOwner: TMycChart;
|
||||
protected
|
||||
function GetSeries: TSeries; virtual; abstract;
|
||||
procedure Update; virtual; abstract;
|
||||
function GetValueRange(StartIndex, Count: 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;
|
||||
public
|
||||
constructor Create(AOwner: TMycChart);
|
||||
procedure Repaint;
|
||||
property Owner: TMycChart read FOwner;
|
||||
property Series: TSeries read GetSeries;
|
||||
end;
|
||||
|
||||
private
|
||||
FSeriesList: TList<TSeries>;
|
||||
FXAxisSeries: TSeries;
|
||||
FSeriesList: TList<TLayer>;
|
||||
FXAxisSeries: TLayer;
|
||||
FLookback: TWriteable<Int64>;
|
||||
FIdleSubscrId: TMessageSubscriptionId;
|
||||
FViewStartIndex: Int64;
|
||||
@@ -53,6 +60,7 @@ type
|
||||
FJumpButtonRect: TRectF;
|
||||
FJumpButtonHot: Boolean;
|
||||
FJumpButtonPressed: Boolean;
|
||||
FNeedRepaint: TFlag;
|
||||
protected
|
||||
procedure Paint; override;
|
||||
procedure DoIdle;
|
||||
@@ -66,7 +74,7 @@ type
|
||||
|
||||
// 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;
|
||||
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.
|
||||
@@ -74,16 +82,17 @@ type
|
||||
const DataProvider: IMycDataProvider<TOhlcItem>;
|
||||
const AUpColor: TAlphaColor = TAlphaColors.Green;
|
||||
const ADownColor: TAlphaColor = TAlphaColors.Red
|
||||
): TSeries;
|
||||
): 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
|
||||
): TSeries;
|
||||
): TLayer;
|
||||
|
||||
property Lookback: TWriteable<Int64> read FLookback write FLookback;
|
||||
property NeedRepaint: TFlag read FNeedRepaint;
|
||||
end;
|
||||
|
||||
implementation
|
||||
@@ -92,33 +101,12 @@ uses
|
||||
System.Math,
|
||||
Myc.FMX.Chart.Series;
|
||||
|
||||
constructor TMycChart.TSeries.Create(AOwner: TMycChart);
|
||||
begin
|
||||
inherited Create;
|
||||
FOwner := AOwner;
|
||||
end;
|
||||
|
||||
function TMycChart.TSeries.GetMainSeries: TSeries;
|
||||
begin
|
||||
Result := Owner.FXAxisSeries;
|
||||
end;
|
||||
|
||||
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;
|
||||
|
||||
{ TMycChart }
|
||||
|
||||
constructor TMycChart.Create(AOwner: TComponent);
|
||||
begin
|
||||
inherited Create(AOwner);
|
||||
FSeriesList := TObjectList<TSeries>.Create(true);
|
||||
FSeriesList := TObjectList<TLayer>.Create(true);
|
||||
FXAxisSeries := nil;
|
||||
|
||||
FLookback := TWriteable<Int64>.CreateWriteable(1000); // Load more data for panning
|
||||
@@ -141,7 +129,7 @@ begin
|
||||
inherited;
|
||||
end;
|
||||
|
||||
function TMycChart.SetXAxisSeries<T>(const DataProvider: IMycDataProvider<T>): TSeries;
|
||||
function TMycChart.SetXAxisSeries<T>(const DataProvider: IMycDataProvider<T>): TLayer;
|
||||
begin
|
||||
// Free the old one if it exists, and create the new master series
|
||||
FXAxisSeries.Free;
|
||||
@@ -150,7 +138,7 @@ begin
|
||||
|
||||
DataProvider.Link(counter);
|
||||
|
||||
FXAxisSeries := TChartSeriesProcessor<Int64>.Create(Self, counter.Sender);
|
||||
FXAxisSeries := TChartCustomLayer<Int64>.Create(Self, counter.Sender);
|
||||
Result := FXAxisSeries;
|
||||
end;
|
||||
|
||||
@@ -158,9 +146,9 @@ function TMycChart.AddDoubleSeries(
|
||||
const DataProvider: IMycDataProvider<Double>;
|
||||
const ALineColor: TAlphaColor = TAlphaColors.Cornflowerblue;
|
||||
const ALineWidth: Single = 1.5
|
||||
): TSeries;
|
||||
): TLayer;
|
||||
begin
|
||||
Result := TChartLineSeries.Create(Self, DataProvider, ALineColor, ALineWidth);
|
||||
Result := TChartLineLayer.Create(Self, DataProvider, ALineColor, ALineWidth);
|
||||
FSeriesList.Add(Result);
|
||||
end;
|
||||
|
||||
@@ -168,9 +156,10 @@ function TMycChart.AddOhlcSeries(
|
||||
const DataProvider: IMycDataProvider<TOhlcItem>;
|
||||
const AUpColor: TAlphaColor = TAlphaColors.Green;
|
||||
const ADownColor: TAlphaColor = TAlphaColors.Red
|
||||
): TSeries;
|
||||
): TLayer;
|
||||
begin
|
||||
Result := TChartOhlcSeries.Create(Self, DataProvider, AUpColor, ADownColor);
|
||||
Result :=
|
||||
TChartOhlcLayer.Create(Self, DataProvider, TMutable<TAlphaColor>.Constant(AUpColor), TMutable<TAlphaColor>.Constant(ADownColor));
|
||||
FSeriesList.Add(Result);
|
||||
end;
|
||||
|
||||
@@ -181,11 +170,11 @@ begin
|
||||
exit;
|
||||
|
||||
// First update the master series
|
||||
var prevTotalCount := FXAxisSeries.TotalCount;
|
||||
var prevTotalCount := FXAxisSeries.Series.TotalCount;
|
||||
FXAxisSeries.Update;
|
||||
|
||||
// Check if the master series itself has changed
|
||||
var newTotalCount := FXAxisSeries.TotalCount;
|
||||
var newTotalCount := FXAxisSeries.Series.TotalCount;
|
||||
var seriesChanged := prevTotalCount <> newTotalCount;
|
||||
|
||||
// Repaint if the last bar is visible, so we display live data
|
||||
@@ -193,7 +182,7 @@ begin
|
||||
|
||||
for var series in FSeriesList do
|
||||
begin
|
||||
var seriesCount := series.TotalCount;
|
||||
var seriesCount := series.Series.TotalCount;
|
||||
|
||||
// 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
|
||||
@@ -202,7 +191,7 @@ begin
|
||||
// Update the series
|
||||
series.Update;
|
||||
|
||||
if seriesCount <> series.TotalCount then
|
||||
if seriesCount <> series.Series.TotalCount then
|
||||
seriesChanged := true;
|
||||
end;
|
||||
|
||||
@@ -220,7 +209,10 @@ begin
|
||||
end;
|
||||
end;
|
||||
|
||||
if seriesRepaint and seriesChanged then
|
||||
// finally, check the repaint signal
|
||||
var repaintSignal := FNeedRepaint.Reset;
|
||||
|
||||
if repaintSignal or (seriesRepaint and seriesChanged) then
|
||||
Repaint;
|
||||
end;
|
||||
|
||||
@@ -269,7 +261,6 @@ var
|
||||
isHot: Boolean;
|
||||
dx: Single;
|
||||
indexDelta: Int64;
|
||||
mainSeries: TSeries;
|
||||
maxIndex: Int64;
|
||||
begin
|
||||
// Update button hot state
|
||||
@@ -317,8 +308,7 @@ begin
|
||||
FViewStartIndex := FViewStartIndex + indexDelta;
|
||||
|
||||
// Clamp values
|
||||
mainSeries := FXAxisSeries;
|
||||
maxIndex := mainSeries.Count - FViewCount;
|
||||
maxIndex := FXAxisSeries.Series.Count - FViewCount;
|
||||
if (maxIndex < 0) then
|
||||
maxIndex := 0;
|
||||
|
||||
@@ -341,7 +331,6 @@ var
|
||||
zoomFactor: Double;
|
||||
newViewCount: Int64;
|
||||
ratio: Double;
|
||||
mainSeries: TSeries;
|
||||
begin
|
||||
Handled := true;
|
||||
|
||||
@@ -349,8 +338,8 @@ begin
|
||||
if not Assigned(FXAxisSeries) then
|
||||
exit;
|
||||
|
||||
mainSeries := FXAxisSeries;
|
||||
if (mainSeries.Count = 0) or (Width <= 0) then
|
||||
var xAxisCount := FXAxisSeries.Series.Count;
|
||||
if (xAxisCount = 0) or (Width <= 0) then
|
||||
exit;
|
||||
|
||||
mousePos := ScreenToLocal(Screen.MousePos);
|
||||
@@ -368,8 +357,8 @@ begin
|
||||
// Clamp zoom level
|
||||
if (newViewCount < 10) then
|
||||
newViewCount := 10;
|
||||
if (newViewCount > mainSeries.Count) then
|
||||
newViewCount := mainSeries.Count;
|
||||
if (newViewCount > xAxisCount) then
|
||||
newViewCount := xAxisCount;
|
||||
if (newViewCount = FViewCount) then
|
||||
exit;
|
||||
|
||||
@@ -385,9 +374,9 @@ begin
|
||||
// Clamp start index
|
||||
if (FViewStartIndex < 0) then
|
||||
FViewStartIndex := 0;
|
||||
if (FViewStartIndex + FViewCount > mainSeries.Count) then
|
||||
if (FViewStartIndex + FViewCount > xAxisCount) then
|
||||
begin
|
||||
FViewStartIndex := mainSeries.Count - FViewCount;
|
||||
FViewStartIndex := xAxisCount - FViewCount;
|
||||
end;
|
||||
|
||||
Repaint;
|
||||
@@ -409,7 +398,6 @@ procedure TMycChart.Paint;
|
||||
|
||||
var
|
||||
rect: TRectF;
|
||||
series: TSeries;
|
||||
globalMin, globalMax, seriesMin, seriesMax, padding: Double;
|
||||
rangeInitialized: Boolean;
|
||||
xTransform: TFunc<Double, Single>;
|
||||
@@ -417,14 +405,13 @@ var
|
||||
isButtonVisible: Boolean;
|
||||
buttonColor: TAlphaColor;
|
||||
path: TPathData;
|
||||
mainSeries: TSeries;
|
||||
masterTotalCount, seriesFirst, seriesLast: Int64;
|
||||
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.Count <= 1) or (FViewCount <= 1) then
|
||||
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);
|
||||
@@ -432,12 +419,11 @@ begin
|
||||
end;
|
||||
|
||||
rangeInitialized := false;
|
||||
mainSeries := FXAxisSeries;
|
||||
|
||||
masterTotalCount := mainSeries.TotalCount;
|
||||
for series in FSeriesList do
|
||||
masterTotalCount := FXAxisSeries.Series.TotalCount;
|
||||
for var series in FSeriesList do
|
||||
begin
|
||||
if CalcView(series, masterTotalCount, seriesFirst, seriesLast) then
|
||||
if CalcView(series.Series, masterTotalCount, seriesFirst, seriesLast) then
|
||||
begin
|
||||
if series.GetValueRange(seriesFirst, seriesLast, seriesMin, seriesMax) then
|
||||
begin
|
||||
@@ -472,11 +458,11 @@ begin
|
||||
|
||||
// --- Draw Series with Offset Compensation ---
|
||||
// The masterTotalCount is already calculated from the range finding loop
|
||||
for series in FSeriesList do
|
||||
for var series in FSeriesList do
|
||||
begin
|
||||
if CalcView(series, masterTotalCount, seriesFirst, seriesLast) then
|
||||
if CalcView(series.Series, masterTotalCount, seriesFirst, seriesLast) then
|
||||
begin
|
||||
var offset := masterTotalCount - series.TotalCount;
|
||||
var offset := masterTotalCount - series.Series.TotalCount;
|
||||
xTransform :=
|
||||
function(index: Double): Single
|
||||
begin
|
||||
@@ -534,4 +520,15 @@ begin
|
||||
end;
|
||||
end;
|
||||
|
||||
constructor TMycChart.TLayer.Create(AOwner: TMycChart);
|
||||
begin
|
||||
inherited Create;
|
||||
FOwner := AOwner;
|
||||
end;
|
||||
|
||||
procedure TMycChart.TLayer.Repaint;
|
||||
begin
|
||||
FOwner.Repaint;
|
||||
end;
|
||||
|
||||
end.
|
||||
|
||||
+36
-80
@@ -38,6 +38,7 @@ type
|
||||
class property Null: IMutable read FNull;
|
||||
|
||||
class function Construct(const Changing: TSignal; const Proc: TFunc<T>): TMutable<T>; overload; static;
|
||||
class function Constant(const Value: T): TMutable<T>; overload; static;
|
||||
|
||||
property Value: T read GetValue;
|
||||
property Changed: TSignal read GetChanged;
|
||||
@@ -74,38 +75,15 @@ type
|
||||
end;
|
||||
|
||||
TLazy<T> = record
|
||||
type
|
||||
ILazy = interface
|
||||
{$REGION 'property access'}
|
||||
function GetChanged: TState;
|
||||
{$ENDREGION}
|
||||
function Pop(out Res: T): Boolean;
|
||||
property Changed: TState read GetChanged;
|
||||
end;
|
||||
|
||||
{$REGION 'private'}
|
||||
strict private
|
||||
class var
|
||||
FNull: ILazy;
|
||||
class constructor CreateClass;
|
||||
|
||||
private
|
||||
FLazy: ILazy;
|
||||
function GetChanged: TState; inline;
|
||||
{$ENDREGION}
|
||||
FMutable: TMutable<T>;
|
||||
FChanged: TFlag;
|
||||
FChangeState: TSignal.TSubscription;
|
||||
function GetValue: T; inline;
|
||||
public
|
||||
constructor Create(const ALazy: ILazy);
|
||||
class operator Initialize(out Dest: TLazy<T>);
|
||||
class operator Implicit(const A: ILazy): TLazy<T>; overload;
|
||||
class operator Implicit(const A: TLazy<T>): ILazy; overload;
|
||||
|
||||
class function Construct(const Changing: TSignal.ISignal; const Proc: TFunc<T>): TLazy<T>; overload; static;
|
||||
class function Construct(const Mutable: TMutable<T>): TLazy<T>; overload; static;
|
||||
class property Null: ILazy read FNull;
|
||||
|
||||
function Pop(out Res: T): Boolean; inline;
|
||||
|
||||
property Changed: TState read GetChanged;
|
||||
constructor Create(const AMutable: TMutable<T>);
|
||||
function Update: Boolean;
|
||||
property Value: T read GetValue;
|
||||
end;
|
||||
|
||||
implementation
|
||||
@@ -123,7 +101,12 @@ end;
|
||||
|
||||
class constructor TMutable<T>.CreateClass;
|
||||
begin
|
||||
FNull := TMycNullMutable<T>.Create;
|
||||
FNull := TMycNullMutable<T>.Create(Default(T));
|
||||
end;
|
||||
|
||||
class function TMutable<T>.Constant(const Value: T): TMutable<T>;
|
||||
begin
|
||||
Result := TMycNullMutable<T>.Create(Value);
|
||||
end;
|
||||
|
||||
class function TMutable<T>.Construct(const Changing: TSignal; const Proc: TFunc<T>): TMutable<T>;
|
||||
@@ -156,55 +139,6 @@ begin
|
||||
Dest.FMutable := FNull;
|
||||
end;
|
||||
|
||||
{ TLazy<T> }
|
||||
|
||||
constructor TLazy<T>.Create(const ALazy: ILazy);
|
||||
begin
|
||||
if Assigned(ALazy) then
|
||||
FLazy := ALazy;
|
||||
end;
|
||||
|
||||
class function TLazy<T>.Construct(const Changing: TSignal.ISignal; const Proc: TFunc<T>): TLazy<T>;
|
||||
begin
|
||||
Result := TMycFuncLazy<T>.Create(Changing, Proc);
|
||||
end;
|
||||
|
||||
class constructor TLazy<T>.CreateClass;
|
||||
begin
|
||||
FNull := TMycNullLazy<T>.Create;
|
||||
end;
|
||||
|
||||
class function TLazy<T>.Construct(const Mutable: TMutable<T>): TLazy<T>;
|
||||
begin
|
||||
var cap := Mutable;
|
||||
Result := TMycFuncLazy<T>.Create(cap.Changed, function: T begin exit(Mutable.Value) end);
|
||||
end;
|
||||
|
||||
function TLazy<T>.GetChanged: TState;
|
||||
begin
|
||||
Result := FLazy.Changed;
|
||||
end;
|
||||
|
||||
function TLazy<T>.Pop(out Res: T): Boolean;
|
||||
begin
|
||||
Result := FLazy.Pop(Res);
|
||||
end;
|
||||
|
||||
class operator TLazy<T>.Implicit(const A: ILazy): TLazy<T>;
|
||||
begin
|
||||
Result.Create(A);
|
||||
end;
|
||||
|
||||
class operator TLazy<T>.Implicit(const A: TLazy<T>): ILazy;
|
||||
begin
|
||||
Result := A.FLazy;
|
||||
end;
|
||||
|
||||
class operator TLazy<T>.Initialize(out Dest: TLazy<T>);
|
||||
begin
|
||||
Dest.FLazy := FNull;
|
||||
end;
|
||||
|
||||
{ TWriteable<T> }
|
||||
|
||||
constructor TWriteable<T>.Create(const AWriteable: IWriteable);
|
||||
@@ -261,4 +195,26 @@ begin
|
||||
Result := A.FWriteable;
|
||||
end;
|
||||
|
||||
{ TLazy<T> }
|
||||
|
||||
constructor TLazy<T>.Create(const AMutable: TMutable<T>);
|
||||
begin
|
||||
FMutable := AMutable;
|
||||
FChanged := TFlag.CreateFlag;
|
||||
FChanged.Notify;
|
||||
FChangeState := AMutable.Changed.Subscribe(FChanged);
|
||||
end;
|
||||
|
||||
function TLazy<T>.GetValue: T;
|
||||
begin
|
||||
Result := FMutable.Value;
|
||||
end;
|
||||
|
||||
function TLazy<T>.Update: Boolean;
|
||||
begin
|
||||
Result := FChanged.State.IsSet;
|
||||
if Result then
|
||||
FChanged.Reset;
|
||||
end;
|
||||
|
||||
end.
|
||||
|
||||
Reference in New Issue
Block a user