Chart now solid
This commit is contained in:
@@ -24,7 +24,7 @@ type
|
||||
protected
|
||||
// Broadcasts the given data to all linked processors.
|
||||
procedure Broadcast(const Value: T);
|
||||
procedure Update; override;
|
||||
|
||||
public
|
||||
constructor Create;
|
||||
destructor Destroy; override;
|
||||
@@ -42,33 +42,21 @@ type
|
||||
constructor Create(const AFunc: TConvertFunc);
|
||||
end;
|
||||
|
||||
ITicksToTimeframe = interface(IMycConverter<TArray<TDataPoint<TAskBidItem>>, TDataPoint<TOhlcItem>>)
|
||||
function GetCurrentBar: TDataPoint<TOhlcItem>;
|
||||
function GetStateText: TWriteable<String>;
|
||||
function GetTimeframe: TTimeframe;
|
||||
property CurrentBar: TDataPoint<TOhlcItem> read GetCurrentBar;
|
||||
property StateText: TWriteable<String> read GetStateText;
|
||||
property Timeframe: TTimeframe read GetTimeframe;
|
||||
end;
|
||||
|
||||
TTicksToTimeframe = class(TMycConverter<TArray<TDataPoint<TAskBidItem>>, TDataPoint<TOhlcItem>>, ITicksToTimeframe)
|
||||
TTicksToTimeframe = class(TMycConverter<TArray<TDataPoint<TAskBidItem>>, TDataPoint<TOhlcItem>>)
|
||||
private
|
||||
FTimeframe: TTimeframe;
|
||||
FStateText: TWriteable<String>;
|
||||
// Stores the currently aggregating OHLC data.
|
||||
FCurrentBar: TDataPoint<TOhlcItem>;
|
||||
function GetBarStartTime(const TimeStamp: TDateTime; const Timeframe: TTimeframe): TDateTime;
|
||||
function GetCurrentBar: TDataPoint<TOhlcItem>;
|
||||
function GetStateText: TWriteable<String>;
|
||||
function GetTimeframe: TTimeframe;
|
||||
public
|
||||
constructor Create(const ATimeframe: TTimeframe; const AStateText: TWriteable<String>);
|
||||
constructor Create(const ATimeframe: TTimeframe);
|
||||
|
||||
// Process new data. This is called concurrently and must not have side effects out of the scope of this class!
|
||||
function ProcessData(const Values: TArray<TDataPoint<TAskBidItem>>): Boolean; override;
|
||||
|
||||
property CurrentBar: TDataPoint<TOhlcItem> read GetCurrentBar;
|
||||
property StateText: TWriteable<String> read GetStateText;
|
||||
property Timeframe: TTimeframe read GetTimeframe;
|
||||
end;
|
||||
|
||||
@@ -99,10 +87,9 @@ uses
|
||||
|
||||
{ TTicksToTimeframe }
|
||||
|
||||
constructor TTicksToTimeframe.Create(const ATimeframe: TTimeframe; const AStateText: TWriteable<String>);
|
||||
constructor TTicksToTimeframe.Create(const ATimeframe: TTimeframe);
|
||||
begin
|
||||
inherited Create;
|
||||
FStateText := AStateText;
|
||||
FTimeframe := ATimeframe;
|
||||
end;
|
||||
|
||||
@@ -124,11 +111,6 @@ begin
|
||||
Result := FCurrentBar;
|
||||
end;
|
||||
|
||||
function TTicksToTimeframe.GetStateText: TWriteable<String>;
|
||||
begin
|
||||
Result := FStateText;
|
||||
end;
|
||||
|
||||
function TTicksToTimeframe.GetTimeframe: TTimeframe;
|
||||
begin
|
||||
Result := FTimeframe;
|
||||
@@ -178,10 +160,6 @@ begin
|
||||
FCurrentBar.Data := currentBar;
|
||||
end;
|
||||
end;
|
||||
|
||||
with FCurrentBar do
|
||||
FStateText.Value :=
|
||||
Format('Cuur Bar: O:%.5f H:%.5f L:%.5f C:%.5f V:%.0f', [Data.Open, Data.High, Data.Low, Data.Close, Data.Volume]);
|
||||
end;
|
||||
|
||||
{ THullMovingAverage }
|
||||
@@ -302,15 +280,4 @@ begin
|
||||
Result := FSender;
|
||||
end;
|
||||
|
||||
procedure TMycConverter<S, T>.Update;
|
||||
begin
|
||||
FSender.Notify(
|
||||
function(const Processor: IMycProcessor<T>): Boolean
|
||||
begin
|
||||
Processor.Update;
|
||||
Result := true
|
||||
end
|
||||
);
|
||||
end;
|
||||
|
||||
end.
|
||||
|
||||
+12
-13
@@ -156,12 +156,6 @@ begin
|
||||
if Symbol = '' then
|
||||
exit;
|
||||
|
||||
var stateText := TWriteable<String>.CreateWriteable.Protect;
|
||||
var stateLabel := TLabel.Create(Self);
|
||||
AlignControl(stateLabel);
|
||||
|
||||
stateLabel.Text := 'Initializing...';
|
||||
|
||||
var chart := TMycChart.Create(Self);
|
||||
AlignControl(chart);
|
||||
chart.Height := Layout.ChildrenRect.Width * 9 / 16;
|
||||
@@ -169,7 +163,7 @@ begin
|
||||
|
||||
/////
|
||||
|
||||
var OhlcPoint: ITicksToTimeframe := TTicksToTimeframe.Create(H1, stateText);
|
||||
var OhlcPoint: IMycConverter<TArray<TDataPoint<TAskBidItem>>, TDataPoint<TOhlcItem>> := TTicksToTimeframe.Create(M1);
|
||||
|
||||
var Ohlc: IMycConverter<TDataPoint<TOhlcItem>, TOhlcItem> :=
|
||||
TMycGenericConverter<TDataPoint<TOhlcItem>, TOhlcItem>
|
||||
@@ -182,18 +176,23 @@ begin
|
||||
|
||||
Ohlc.Sender.Link(Closes);
|
||||
|
||||
var Hull: IMycConverter<Double, Double> := THullMovingAverage.Create(250);
|
||||
for var i := 0 to 200 do
|
||||
begin
|
||||
var Hull: IMycConverter<Double, Double> := THullMovingAverage.Create(20 + (20 * i));
|
||||
Closes.Sender.Link(Hull);
|
||||
chart.AddDoubleSeries(Hull.Sender, Random(Cardinal.MaxValue));
|
||||
end;
|
||||
|
||||
Closes.Sender.Link(Hull);
|
||||
|
||||
chart.AddDoubleSeries(Hull.Sender);
|
||||
// var Hull: IMycConverter<Double, Double> := THullMovingAverage.Create(250);
|
||||
//
|
||||
// Closes.Sender.Link(Hull);
|
||||
//
|
||||
// chart.AddDoubleSeries(Hull.Sender);
|
||||
|
||||
var done := ExecuteStrategy(Symbol, OhlcPoint);
|
||||
|
||||
/////
|
||||
|
||||
stateLabel.ProcessSignal(stateText.Changed, done, procedure begin stateLabel.Text := stateText.Value; end);
|
||||
|
||||
FProcessDone := TState.All([FProcessDone, done]);
|
||||
|
||||
chart.AddOhlcSeries(OhlcPoint.Sender);
|
||||
|
||||
+145
-100
@@ -31,9 +31,10 @@ type
|
||||
function GetMainSeries: TSeries;
|
||||
protected
|
||||
function GetCount: Int64; virtual; abstract;
|
||||
function GetTotalCount: Int64; virtual; abstract;
|
||||
function GetValueRange(StartIndex, Count: Int64; out Min, Max: Double): Boolean; virtual; abstract;
|
||||
function Update: Boolean; virtual; abstract;
|
||||
// The signature is changed to support viewport painting
|
||||
// The signature is changed to support viewport painting with an index offset
|
||||
procedure Paint(
|
||||
const ACanvas: TCanvas;
|
||||
StartIndex, Count: Int64;
|
||||
@@ -44,6 +45,7 @@ type
|
||||
constructor Create(AOwner: TMycChart);
|
||||
property Count: Int64 read GetCount;
|
||||
property Owner: TMycChart read FOwner;
|
||||
property TotalCount: Int64 read GetTotalCount;
|
||||
end;
|
||||
|
||||
private
|
||||
@@ -68,20 +70,20 @@ type
|
||||
constructor Create(AOwner: TComponent); override;
|
||||
destructor Destroy; override;
|
||||
|
||||
// Creates an OHLC candlestick/bar series
|
||||
procedure AddOhlcSeries(
|
||||
// Creates an OHLC candlestick/bar series and returns it
|
||||
function AddOhlcSeries(
|
||||
const DataProvider: IMycDataProvider<TDataPoint<TOhlcItem>>;
|
||||
const AUpColor: TAlphaColor = TAlphaColors.Green;
|
||||
const ADownColor: TAlphaColor = TAlphaColors.Red;
|
||||
const AStyle: TCandleStyle = csCandleStick
|
||||
);
|
||||
): TSeries;
|
||||
|
||||
// Creates a simple line series for double values
|
||||
procedure AddDoubleSeries(
|
||||
// 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;
|
||||
|
||||
property Lookback: TWriteable<Int64> read FLookback write FLookback;
|
||||
end;
|
||||
@@ -103,7 +105,6 @@ type
|
||||
FData: TWriteable<TMycDataArray<T>>;
|
||||
protected
|
||||
function ProcessData(const Value: T): Boolean; override;
|
||||
procedure Update; override;
|
||||
public
|
||||
constructor Create(const ALookback: TWriteable<Int64>);
|
||||
function GetData(var Data: TMycDataArray<T>): Boolean;
|
||||
@@ -121,6 +122,7 @@ type
|
||||
FReceiverTag: TTag;
|
||||
protected
|
||||
function GetCount: Int64; override;
|
||||
function GetTotalCount: Int64; override;
|
||||
function Update: Boolean; override;
|
||||
|
||||
public
|
||||
@@ -205,29 +207,25 @@ begin
|
||||
inherited;
|
||||
end;
|
||||
|
||||
procedure TMycChart.AddDoubleSeries(
|
||||
function TMycChart.AddDoubleSeries(
|
||||
const DataProvider: IMycDataProvider<Double>;
|
||||
const ALineColor: TAlphaColor = TAlphaColors.Cornflowerblue;
|
||||
const ALineWidth: Single = 1.5
|
||||
);
|
||||
var
|
||||
series: TChartLineSeries;
|
||||
): TSeries;
|
||||
begin
|
||||
series := TChartLineSeries.Create(Self, DataProvider, ALineColor, ALineWidth);
|
||||
FSeriesList.Add(series);
|
||||
Result := TChartLineSeries.Create(Self, DataProvider, ALineColor, ALineWidth);
|
||||
FSeriesList.Add(Result);
|
||||
end;
|
||||
|
||||
procedure TMycChart.AddOhlcSeries(
|
||||
function TMycChart.AddOhlcSeries(
|
||||
const DataProvider: IMycDataProvider<TDataPoint<TOhlcItem>>;
|
||||
const AUpColor: TAlphaColor = TAlphaColors.Green;
|
||||
const ADownColor: TAlphaColor = TAlphaColors.Red;
|
||||
const AStyle: TCandleStyle = csCandleStick
|
||||
);
|
||||
var
|
||||
series: TChartOhlcSeries;
|
||||
): TSeries;
|
||||
begin
|
||||
series := TChartOhlcSeries.Create(Self, DataProvider, AUpColor, ADownColor, AStyle);
|
||||
FSeriesList.Add(series);
|
||||
Result := TChartOhlcSeries.Create(Self, DataProvider, AUpColor, ADownColor, AStyle);
|
||||
FSeriesList.Add(Result);
|
||||
end;
|
||||
|
||||
procedure TMycChart.DoIdle;
|
||||
@@ -235,22 +233,18 @@ var
|
||||
doRepaint: Boolean;
|
||||
mainSeries: TSeries;
|
||||
isLiveView: Boolean;
|
||||
prevCount, newCount, newCandles: Int64;
|
||||
prevTotalCount, newTotalCount, countDelta: Int64;
|
||||
begin
|
||||
if (FSeriesList.Count = 0) then
|
||||
begin
|
||||
exit;
|
||||
end;
|
||||
|
||||
mainSeries := FSeriesList[0];
|
||||
if (mainSeries = nil) then
|
||||
begin
|
||||
exit;
|
||||
end;
|
||||
|
||||
// Remember state before update
|
||||
isLiveView := (FViewStartIndex = 0);
|
||||
prevCount := mainSeries.Count;
|
||||
prevTotalCount := mainSeries.TotalCount;
|
||||
|
||||
// Check all series for updates
|
||||
doRepaint := false;
|
||||
@@ -262,20 +256,20 @@ begin
|
||||
end;
|
||||
end;
|
||||
|
||||
if doRepaint then
|
||||
if doRepaint and (not isLiveView) then
|
||||
begin
|
||||
newCount := mainSeries.Count;
|
||||
newCandles := newCount - prevCount;
|
||||
newTotalCount := mainSeries.TotalCount;
|
||||
countDelta := newTotalCount - prevTotalCount;
|
||||
|
||||
// Adjust viewport only if user was not watching the live data
|
||||
if (not isLiveView) and (newCandles > 0) then
|
||||
if (countDelta > 0) then
|
||||
begin
|
||||
FViewStartIndex := FViewStartIndex + newCandles;
|
||||
// Adjust the start index based on the absolute change in total data points
|
||||
FViewStartIndex := FViewStartIndex + countDelta;
|
||||
end;
|
||||
// If the view was live, it implicitly stays at StartIndex = 0, showing the new data.
|
||||
|
||||
Repaint;
|
||||
end;
|
||||
|
||||
if doRepaint then
|
||||
Repaint;
|
||||
end;
|
||||
|
||||
procedure TMycChart.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Single);
|
||||
@@ -385,7 +379,9 @@ begin
|
||||
if (FViewStartIndex > maxIndex) then
|
||||
FViewStartIndex := maxIndex;
|
||||
|
||||
FDragStartPoint := TPointF.Create(X, Y);
|
||||
// Optimized point update
|
||||
FDragStartPoint.X := X;
|
||||
FDragStartPoint.Y := Y;
|
||||
Repaint;
|
||||
end;
|
||||
end;
|
||||
@@ -452,6 +448,24 @@ begin
|
||||
end;
|
||||
|
||||
procedure TMycChart.Paint;
|
||||
|
||||
function CalcView(Series: TSeries; MasterCount: Int64; out First: Int64; out Count: Int64): Boolean;
|
||||
begin
|
||||
var seriesTotalCount := series.TotalCount;
|
||||
var offset := MasterCount - seriesTotalCount;
|
||||
var startIdx := FViewStartIndex - offset;
|
||||
|
||||
var effectiveStart := Max(0, startIdx);
|
||||
var effectiveEnd := Min(series.Count, startIdx + FViewCount) - 1;
|
||||
|
||||
Result := effectiveStart <= effectiveEnd - 1;
|
||||
if Result then
|
||||
begin
|
||||
First := effectiveStart;
|
||||
Count := effectiveEnd - effectiveStart + 1;
|
||||
end;
|
||||
end;
|
||||
|
||||
var
|
||||
rect: TRectF;
|
||||
series: TSeries;
|
||||
@@ -462,6 +476,8 @@ var
|
||||
isButtonVisible: Boolean;
|
||||
buttonColor: TAlphaColor;
|
||||
path: TPathData;
|
||||
mainSeries: TSeries;
|
||||
masterTotalCount, seriesStart, seriesCount: Int64;
|
||||
begin
|
||||
inherited;
|
||||
rect := Self.LocalRect;
|
||||
@@ -474,22 +490,26 @@ begin
|
||||
end;
|
||||
|
||||
rangeInitialized := false;
|
||||
mainSeries := FSeriesList[0];
|
||||
|
||||
// Determine value range for the visible viewport only
|
||||
masterTotalCount := mainSeries.TotalCount;
|
||||
for series in FSeriesList do
|
||||
begin
|
||||
if series.GetValueRange(FViewStartIndex, FViewCount, seriesMin, seriesMax) then
|
||||
if CalcView(series, masterTotalCount, seriesStart, seriesCount) then
|
||||
begin
|
||||
if not rangeInitialized then
|
||||
if series.GetValueRange(seriesStart, seriesCount, seriesMin, seriesMax) then
|
||||
begin
|
||||
globalMin := seriesMin;
|
||||
globalMax := seriesMax;
|
||||
rangeInitialized := true;
|
||||
end
|
||||
else
|
||||
begin
|
||||
globalMin := Min(globalMin, seriesMin);
|
||||
globalMax := Max(globalMax, seriesMax);
|
||||
if not rangeInitialized then
|
||||
begin
|
||||
globalMin := seriesMin;
|
||||
globalMax := seriesMax;
|
||||
rangeInitialized := true;
|
||||
end
|
||||
else
|
||||
begin
|
||||
globalMin := Min(globalMin, seriesMin);
|
||||
globalMax := Max(globalMax, seriesMax);
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
@@ -504,29 +524,29 @@ begin
|
||||
if (globalMax - globalMin) = 0 then
|
||||
exit;
|
||||
|
||||
// Transform maps an index from the viewport to a screen coordinate
|
||||
xTransform :=
|
||||
function(index: Double): Single
|
||||
begin
|
||||
if (FViewCount <= 1) then
|
||||
begin
|
||||
Result := rect.Left + rect.Width / 2;
|
||||
exit;
|
||||
end;
|
||||
Result := rect.Right - (((index - FViewStartIndex) / (FViewCount - 1)) * rect.Width);
|
||||
end;
|
||||
|
||||
// Base transform maps an index from the viewport to a screen coordinate
|
||||
yTransform :=
|
||||
function(value: Double): Single begin Result := rect.Top + (1 - (value - globalMin) / (globalMax - globalMin)) * rect.Height; end;
|
||||
|
||||
// --- Draw Series with Offset Compensation ---
|
||||
// The masterTotalCount is already calculated from the range finding loop
|
||||
for series in FSeriesList do
|
||||
begin
|
||||
series.Paint(Self.Canvas, FViewStartIndex, FViewCount, xTransform, yTransform);
|
||||
if CalcView(series, masterTotalCount, seriesStart, seriesCount) then
|
||||
begin
|
||||
var offset := masterTotalCount - series.TotalCount;
|
||||
xTransform :=
|
||||
function(index: Double): Single
|
||||
begin
|
||||
Result := rect.Right - (((index + offset - FViewStartIndex) / (FViewCount - 1)) * rect.Width);
|
||||
end;
|
||||
|
||||
series.Paint(Self.Canvas, seriesStart, seriesCount, xTransform, yTransform);
|
||||
end;
|
||||
end;
|
||||
|
||||
// --- Draw the "jump to latest" button ---
|
||||
isButtonVisible := (FViewStartIndex > 0);
|
||||
FJumpButtonHot := FJumpButtonHot and isButtonVisible;
|
||||
|
||||
if isButtonVisible then
|
||||
begin
|
||||
@@ -545,7 +565,7 @@ begin
|
||||
Canvas.Fill.Color := buttonColor;
|
||||
Canvas.FillRect(FJumpButtonRect, 4, 4, AllCorners, 0.7);
|
||||
|
||||
// Draw an icon (e.g., a "fast forward" double arrow)
|
||||
// Draw an icon
|
||||
Canvas.Stroke.Color := TAlphaColors.White;
|
||||
Canvas.Stroke.Thickness := 1.5;
|
||||
|
||||
@@ -553,11 +573,9 @@ begin
|
||||
try
|
||||
var cx := FJumpButtonRect.CenterPoint.X;
|
||||
var cy := FJumpButtonRect.CenterPoint.Y;
|
||||
// First arrow
|
||||
path.MoveTo(PointF(cx - 5, cy - 6));
|
||||
path.LineTo(PointF(cx, cy));
|
||||
path.LineTo(PointF(cx - 5, cy + 6));
|
||||
// Second arrow
|
||||
path.MoveTo(PointF(cx + 2, cy - 6));
|
||||
path.LineTo(PointF(cx + 7, cy));
|
||||
path.LineTo(PointF(cx + 2, cy + 6));
|
||||
@@ -574,6 +592,30 @@ begin
|
||||
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>.GetData(var Data: TMycDataArray<T>): Boolean;
|
||||
begin
|
||||
var prevTotalCount := Data.TotalCount;
|
||||
Data := FData.Value;
|
||||
Result := prevTotalCount <> Data.TotalCount;
|
||||
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>);
|
||||
@@ -598,6 +640,11 @@ begin
|
||||
Result := FData.Count;
|
||||
end;
|
||||
|
||||
function TChartSeriesProcessor<T>.GetTotalCount: Int64;
|
||||
begin
|
||||
Result := FData.TotalCount;
|
||||
end;
|
||||
|
||||
function TChartSeriesProcessor<T>.Update: Boolean;
|
||||
begin
|
||||
Result := FReceiver.GetData(FData);
|
||||
@@ -621,17 +668,30 @@ end;
|
||||
function TChartOhlcSeries.GetValueRange(StartIndex, Count: Int64; out MinValue, MaxValue: Double): Boolean;
|
||||
var
|
||||
i: Int64;
|
||||
lastIndex: Int64;
|
||||
seriesCount: Int64;
|
||||
effectiveStart, effectiveEnd: Int64;
|
||||
begin
|
||||
Result := GetCount > 0;
|
||||
seriesCount := GetCount;
|
||||
// Ensure the request is valid and there's data to check
|
||||
Result := (seriesCount > 0) and (Count > 0);
|
||||
if not Result then
|
||||
Exit;
|
||||
|
||||
// Calculate the effective range that is actually available within this series's data
|
||||
effectiveStart := Max(0, StartIndex);
|
||||
effectiveEnd := Min(seriesCount - 1, StartIndex + Count - 1);
|
||||
|
||||
// If the entire requested range is outside our available data, there's nothing to do.
|
||||
if (effectiveStart > effectiveEnd) then
|
||||
begin
|
||||
Result := false;
|
||||
Exit;
|
||||
end;
|
||||
|
||||
MinValue := MaxDouble;
|
||||
MaxValue := -MaxDouble;
|
||||
lastIndex := Min(GetCount - 1, StartIndex + Count - 1);
|
||||
|
||||
for i := StartIndex to lastIndex do
|
||||
for i := effectiveStart to effectiveEnd do
|
||||
begin
|
||||
var dp := Data.Items[i];
|
||||
MinValue := Min(MinValue, dp.Data.Low);
|
||||
@@ -704,16 +764,29 @@ end;
|
||||
function TChartLineSeries.GetValueRange(StartIndex, Count: Int64; out MinValue, MaxValue: Double): Boolean;
|
||||
var
|
||||
i: Int64;
|
||||
lastIndex: Int64;
|
||||
seriesCount: Int64;
|
||||
effectiveStart, effectiveEnd: Int64;
|
||||
begin
|
||||
Result := GetCount > 0;
|
||||
seriesCount := GetCount;
|
||||
// Ensure the request is valid and there's data to check
|
||||
Result := (seriesCount > 0) and (Count > 0);
|
||||
if not Result then
|
||||
Exit;
|
||||
|
||||
// Calculate the effective range that is actually available within this series's data
|
||||
effectiveStart := Max(0, StartIndex);
|
||||
effectiveEnd := Min(seriesCount - 1, StartIndex + Count - 1);
|
||||
|
||||
// If the entire requested range is outside our available data, there's nothing to do.
|
||||
if (effectiveStart > effectiveEnd) then
|
||||
begin
|
||||
Result := false;
|
||||
Exit;
|
||||
end;
|
||||
|
||||
MinValue := MaxDouble;
|
||||
MaxValue := -MaxDouble;
|
||||
lastIndex := Min(Data.Count - 1, StartIndex + Count - 1);
|
||||
for i := StartIndex to lastIndex do
|
||||
for i := effectiveStart to effectiveEnd do
|
||||
begin
|
||||
var v := Data.Items[i];
|
||||
if not IsNaN(v) then
|
||||
@@ -764,32 +837,4 @@ begin
|
||||
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>.GetData(var Data: TMycDataArray<T>): Boolean;
|
||||
begin
|
||||
var prevTotalCount := Data.TotalCount;
|
||||
Data := FData.Value;
|
||||
Result := prevTotalCount <> Data.TotalCount;
|
||||
end;
|
||||
|
||||
function TChartSeriesReceiver<T>.ProcessData(const Value: T): Boolean;
|
||||
begin
|
||||
Result := true;
|
||||
FCurrData := FCurrData.Add(Value, FLookback.Value);
|
||||
end;
|
||||
|
||||
procedure TChartSeriesReceiver<T>.Update;
|
||||
begin
|
||||
FData.Value := FCurrData;
|
||||
end;
|
||||
|
||||
end.
|
||||
|
||||
Reference in New Issue
Block a user