Chart: Mutable parameters

This commit is contained in:
Michael Schimmel
2025-07-13 11:46:02 +02:00
parent e75cb2ecb3
commit 9ce608ba09
6 changed files with 202 additions and 273 deletions
-1
View File
@@ -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';
-1
View File
@@ -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
View File
@@ -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);
+91 -35
View File
@@ -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
View File
@@ -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
View File
@@ -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.