Fix chart left data range fail
This commit is contained in:
@@ -176,11 +176,11 @@ begin
|
|||||||
|
|
||||||
Ohlc.Sender.Link(Closes);
|
Ohlc.Sender.Link(Closes);
|
||||||
|
|
||||||
for var i := 0 to 200 do
|
for var i := 0 to 3 do
|
||||||
begin
|
begin
|
||||||
var Hull: IMycConverter<Double, Double> := THullMovingAverage.Create(20 + (20 * i));
|
var Hull: IMycConverter<Double, Double> := THullMovingAverage.Create(50 + (50 * i));
|
||||||
Closes.Sender.Link(Hull);
|
Closes.Sender.Link(Hull);
|
||||||
chart.AddDoubleSeries(Hull.Sender, Random(Cardinal.MaxValue));
|
chart.AddDoubleSeries(Hull.Sender, TAlphaColor(Random(Integer.MaxValue - 1)));
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// var Hull: IMycConverter<Double, Double> := THullMovingAverage.Create(250);
|
// var Hull: IMycConverter<Double, Double> := THullMovingAverage.Create(250);
|
||||||
|
|||||||
+82
-178
@@ -20,8 +20,6 @@ uses
|
|||||||
Myc.Lazy;
|
Myc.Lazy;
|
||||||
|
|
||||||
type
|
type
|
||||||
TCandleStyle = (csCandleStick, csHiLoBar);
|
|
||||||
|
|
||||||
TMycChart = class(TStyledControl)
|
TMycChart = class(TStyledControl)
|
||||||
public
|
public
|
||||||
type
|
type
|
||||||
@@ -33,13 +31,9 @@ type
|
|||||||
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;
|
function GetValueRange(StartIndex, Count: Int64; out Min, Max: Double): Boolean; virtual; abstract;
|
||||||
function Update: Boolean; virtual; abstract;
|
procedure Update; virtual; abstract;
|
||||||
// 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(
|
procedure Paint(const Canvas: TCanvas; First, Last: Int64; const XForm, YForm: TFunc<Double, Single>); virtual; abstract;
|
||||||
const ACanvas: TCanvas;
|
|
||||||
StartIndex, Count: Int64;
|
|
||||||
const AXForm, AYForm: TFunc<Double, Single>
|
|
||||||
); virtual; abstract;
|
|
||||||
property MainSeries: TSeries read GetMainSeries;
|
property MainSeries: TSeries read GetMainSeries;
|
||||||
public
|
public
|
||||||
constructor Create(AOwner: TMycChart);
|
constructor Create(AOwner: TMycChart);
|
||||||
@@ -74,8 +68,7 @@ type
|
|||||||
function AddOhlcSeries(
|
function AddOhlcSeries(
|
||||||
const DataProvider: IMycDataProvider<TDataPoint<TOhlcItem>>;
|
const DataProvider: IMycDataProvider<TDataPoint<TOhlcItem>>;
|
||||||
const AUpColor: TAlphaColor = TAlphaColors.Green;
|
const AUpColor: TAlphaColor = TAlphaColors.Green;
|
||||||
const ADownColor: TAlphaColor = TAlphaColors.Red;
|
const ADownColor: TAlphaColor = TAlphaColors.Red
|
||||||
const AStyle: TCandleStyle = csCandleStick
|
|
||||||
): TSeries;
|
): TSeries;
|
||||||
|
|
||||||
// Creates a simple line series for double values and returns it
|
// Creates a simple line series for double values and returns it
|
||||||
@@ -107,7 +100,6 @@ type
|
|||||||
function ProcessData(const Value: T): Boolean; override;
|
function ProcessData(const Value: T): Boolean; override;
|
||||||
public
|
public
|
||||||
constructor Create(const ALookback: TWriteable<Int64>);
|
constructor Create(const ALookback: TWriteable<Int64>);
|
||||||
function GetData(var Data: TMycDataArray<T>): Boolean;
|
|
||||||
property Data: TWriteable<TMycDataArray<T>> read FData;
|
property Data: TWriteable<TMycDataArray<T>> read FData;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -123,7 +115,7 @@ type
|
|||||||
protected
|
protected
|
||||||
function GetCount: Int64; override;
|
function GetCount: Int64; override;
|
||||||
function GetTotalCount: Int64; override;
|
function GetTotalCount: Int64; override;
|
||||||
function Update: Boolean; override;
|
procedure Update; override;
|
||||||
|
|
||||||
public
|
public
|
||||||
constructor Create(AOwner: TMycChart; const ADataProvider: IMycDataProvider<T>);
|
constructor Create(AOwner: TMycChart; const ADataProvider: IMycDataProvider<T>);
|
||||||
@@ -136,16 +128,14 @@ type
|
|||||||
private
|
private
|
||||||
FUpColor: TAlphaColor;
|
FUpColor: TAlphaColor;
|
||||||
FDownColor: TAlphaColor;
|
FDownColor: TAlphaColor;
|
||||||
FStyle: TCandleStyle;
|
|
||||||
protected
|
protected
|
||||||
function GetValueRange(StartIndex, Count: Int64; out MinValue, MaxValue: Double): Boolean; override;
|
function GetValueRange(First, Last: Int64; out MinValue, MaxValue: Double): Boolean; override;
|
||||||
procedure Paint(const ACanvas: TCanvas; StartIndex, Count: Int64; const AXForm, AYForm: TFunc<Double, Single>); override;
|
procedure Paint(const Canvas: TCanvas; First, Last: Int64; const XForm, YForm: TFunc<Double, Single>); override;
|
||||||
public
|
public
|
||||||
constructor Create(
|
constructor Create(
|
||||||
AOwner: TMycChart;
|
AOwner: TMycChart;
|
||||||
const ADataProvider: IMycDataProvider<TDataPoint<TOhlcItem>>;
|
const ADataProvider: IMycDataProvider<TDataPoint<TOhlcItem>>;
|
||||||
const AUpColor, ADownColor: TAlphaColor;
|
const AUpColor, ADownColor: TAlphaColor
|
||||||
AStyle: TCandleStyle
|
|
||||||
);
|
);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -155,8 +145,8 @@ type
|
|||||||
FLineColor: TAlphaColor;
|
FLineColor: TAlphaColor;
|
||||||
FLineWidth: Single;
|
FLineWidth: Single;
|
||||||
protected
|
protected
|
||||||
function GetValueRange(StartIndex, Count: Int64; out MinValue, MaxValue: Double): Boolean; override;
|
function GetValueRange(First, Last: Int64; out MinValue, MaxValue: Double): Boolean; override;
|
||||||
procedure Paint(const ACanvas: TCanvas; StartIndex, Count: Int64; const AXForm, AYForm: TFunc<Double, Single>); override;
|
procedure Paint(const Canvas: TCanvas; First, Last: Int64; const XForm, YForm: TFunc<Double, Single>); override;
|
||||||
public
|
public
|
||||||
constructor Create(
|
constructor Create(
|
||||||
AOwner: TMycChart;
|
AOwner: TMycChart;
|
||||||
@@ -220,55 +210,53 @@ end;
|
|||||||
function TMycChart.AddOhlcSeries(
|
function TMycChart.AddOhlcSeries(
|
||||||
const DataProvider: IMycDataProvider<TDataPoint<TOhlcItem>>;
|
const DataProvider: IMycDataProvider<TDataPoint<TOhlcItem>>;
|
||||||
const AUpColor: TAlphaColor = TAlphaColors.Green;
|
const AUpColor: TAlphaColor = TAlphaColors.Green;
|
||||||
const ADownColor: TAlphaColor = TAlphaColors.Red;
|
const ADownColor: TAlphaColor = TAlphaColors.Red
|
||||||
const AStyle: TCandleStyle = csCandleStick
|
|
||||||
): TSeries;
|
): TSeries;
|
||||||
begin
|
begin
|
||||||
Result := TChartOhlcSeries.Create(Self, DataProvider, AUpColor, ADownColor, AStyle);
|
Result := TChartOhlcSeries.Create(Self, DataProvider, AUpColor, ADownColor);
|
||||||
FSeriesList.Add(Result);
|
FSeriesList.Add(Result);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TMycChart.DoIdle;
|
procedure TMycChart.DoIdle;
|
||||||
var
|
|
||||||
doRepaint: Boolean;
|
|
||||||
mainSeries: TSeries;
|
|
||||||
isLiveView: Boolean;
|
|
||||||
prevTotalCount, newTotalCount, countDelta: Int64;
|
|
||||||
begin
|
begin
|
||||||
if (FSeriesList.Count = 0) then
|
if (FSeriesList.Count = 0) then
|
||||||
exit;
|
exit;
|
||||||
|
|
||||||
mainSeries := FSeriesList[0];
|
var prevTotalCount := FSeriesList[0].TotalCount;
|
||||||
if (mainSeries = nil) then
|
|
||||||
exit;
|
|
||||||
|
|
||||||
// Remember state before update
|
var seriesRepaint := false;
|
||||||
isLiveView := (FViewStartIndex = 0);
|
var seriesChanged := false;
|
||||||
prevTotalCount := mainSeries.TotalCount;
|
|
||||||
|
|
||||||
// Check all series for updates
|
|
||||||
doRepaint := false;
|
|
||||||
for var series in FSeriesList do
|
for var series in FSeriesList do
|
||||||
begin
|
begin
|
||||||
if series.Update then
|
var seriesCount := series.TotalCount;
|
||||||
begin
|
|
||||||
doRepaint := true;
|
// Check if this series may need a repaint
|
||||||
end;
|
if FViewStartIndex <= prevTotalCount - seriesCount then
|
||||||
|
seriesRepaint := true;
|
||||||
|
|
||||||
|
// Update the series
|
||||||
|
series.Update;
|
||||||
|
|
||||||
|
if seriesCount <> series.TotalCount then
|
||||||
|
seriesChanged := true;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
if doRepaint and (not isLiveView) then
|
// Don't move with live data, if the last bar isn't visible
|
||||||
|
if FViewStartIndex > 0 then
|
||||||
begin
|
begin
|
||||||
newTotalCount := mainSeries.TotalCount;
|
var newTotalCount := FSeriesList[0].TotalCount;
|
||||||
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;
|
||||||
|
if FViewStartIndex + FViewCount > newTotalCount then
|
||||||
|
FViewStartIndex := newTotalCount - FViewCount;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
if doRepaint then
|
if seriesRepaint and seriesChanged then
|
||||||
Repaint;
|
Repaint;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -342,6 +330,7 @@ begin
|
|||||||
|
|
||||||
// Panning logic
|
// Panning logic
|
||||||
inherited MouseMove(Shift, X, Y);
|
inherited MouseMove(Shift, X, Y);
|
||||||
|
|
||||||
if FIsDragging then
|
if FIsDragging then
|
||||||
begin
|
begin
|
||||||
dx := X - FDragStartPoint.X;
|
dx := X - FDragStartPoint.X;
|
||||||
@@ -364,24 +353,18 @@ begin
|
|||||||
|
|
||||||
// Clamp values
|
// Clamp values
|
||||||
mainSeries := FSeriesList[0];
|
mainSeries := FSeriesList[0];
|
||||||
maxIndex := 0;
|
maxIndex := mainSeries.Count - FViewCount;
|
||||||
if (mainSeries <> nil) then
|
|
||||||
begin
|
|
||||||
maxIndex := mainSeries.Count - FViewCount;
|
|
||||||
end;
|
|
||||||
if (maxIndex < 0) then
|
if (maxIndex < 0) then
|
||||||
begin
|
|
||||||
maxIndex := 0;
|
maxIndex := 0;
|
||||||
end;
|
|
||||||
|
|
||||||
if (FViewStartIndex < 0) then
|
if (FViewStartIndex < 0) then
|
||||||
FViewStartIndex := 0;
|
FViewStartIndex := 0;
|
||||||
if (FViewStartIndex > maxIndex) then
|
if (FViewStartIndex > maxIndex) then
|
||||||
FViewStartIndex := maxIndex;
|
FViewStartIndex := maxIndex;
|
||||||
|
|
||||||
// Optimized point update
|
|
||||||
FDragStartPoint.X := X;
|
FDragStartPoint.X := X;
|
||||||
FDragStartPoint.Y := Y;
|
FDragStartPoint.Y := Y;
|
||||||
|
|
||||||
Repaint;
|
Repaint;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
@@ -398,18 +381,14 @@ begin
|
|||||||
Handled := true;
|
Handled := true;
|
||||||
|
|
||||||
if (FSeriesList.Count = 0) then
|
if (FSeriesList.Count = 0) then
|
||||||
begin
|
|
||||||
exit;
|
exit;
|
||||||
end;
|
|
||||||
mainSeries := FSeriesList[0];
|
mainSeries := FSeriesList[0];
|
||||||
if (mainSeries = nil) or (mainSeries.Count = 0) or (Self.Width <= 0) then
|
if (mainSeries.Count = 0) or (Width <= 0) then
|
||||||
begin
|
|
||||||
exit;
|
exit;
|
||||||
end;
|
|
||||||
|
|
||||||
mousePos := Self.ScreenToLocal(Screen.MousePos);
|
mousePos := ScreenToLocal(Screen.MousePos);
|
||||||
|
|
||||||
// Correctly calculate the anchor index for a right-to-left axis
|
// Calculate the anchor index for a right-to-left axis
|
||||||
anchorIndex := FViewStartIndex + ((Self.Width - mousePos.X) / Self.Width) * FViewCount;
|
anchorIndex := FViewStartIndex + ((Self.Width - mousePos.X) / Self.Width) * FViewCount;
|
||||||
|
|
||||||
if (WheelDelta > 0) then
|
if (WheelDelta > 0) then
|
||||||
@@ -449,21 +428,16 @@ end;
|
|||||||
|
|
||||||
procedure TMycChart.Paint;
|
procedure TMycChart.Paint;
|
||||||
|
|
||||||
function CalcView(Series: TSeries; MasterCount: Int64; out First: Int64; out Count: Int64): Boolean;
|
function CalcView(Series: TSeries; MasterCount: Int64; out First: Int64; out Last: Int64): Boolean;
|
||||||
begin
|
begin
|
||||||
var seriesTotalCount := series.TotalCount;
|
var seriesTotalCount := series.TotalCount;
|
||||||
var offset := MasterCount - seriesTotalCount;
|
var offset := MasterCount - seriesTotalCount;
|
||||||
var startIdx := FViewStartIndex - offset;
|
var startIdx := FViewStartIndex - offset;
|
||||||
|
|
||||||
var effectiveStart := Max(0, startIdx);
|
First := Max(0, startIdx);
|
||||||
var effectiveEnd := Min(series.Count, startIdx + FViewCount) - 1;
|
Last := Min(series.Count, startIdx + FViewCount) - 1;
|
||||||
|
|
||||||
Result := effectiveStart <= effectiveEnd - 1;
|
Result := First <= Last - 1;
|
||||||
if Result then
|
|
||||||
begin
|
|
||||||
First := effectiveStart;
|
|
||||||
Count := effectiveEnd - effectiveStart + 1;
|
|
||||||
end;
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
var
|
var
|
||||||
@@ -477,7 +451,7 @@ var
|
|||||||
buttonColor: TAlphaColor;
|
buttonColor: TAlphaColor;
|
||||||
path: TPathData;
|
path: TPathData;
|
||||||
mainSeries: TSeries;
|
mainSeries: TSeries;
|
||||||
masterTotalCount, seriesStart, seriesCount: Int64;
|
masterTotalCount, seriesFirst, seriesLast: Int64;
|
||||||
begin
|
begin
|
||||||
inherited;
|
inherited;
|
||||||
rect := Self.LocalRect;
|
rect := Self.LocalRect;
|
||||||
@@ -495,9 +469,9 @@ begin
|
|||||||
masterTotalCount := mainSeries.TotalCount;
|
masterTotalCount := mainSeries.TotalCount;
|
||||||
for series in FSeriesList do
|
for series in FSeriesList do
|
||||||
begin
|
begin
|
||||||
if CalcView(series, masterTotalCount, seriesStart, seriesCount) then
|
if CalcView(series, masterTotalCount, seriesFirst, seriesLast) then
|
||||||
begin
|
begin
|
||||||
if series.GetValueRange(seriesStart, seriesCount, seriesMin, seriesMax) then
|
if series.GetValueRange(seriesFirst, seriesLast, seriesMin, seriesMax) then
|
||||||
begin
|
begin
|
||||||
if not rangeInitialized then
|
if not rangeInitialized then
|
||||||
begin
|
begin
|
||||||
@@ -532,7 +506,7 @@ begin
|
|||||||
// The masterTotalCount is already calculated from the range finding loop
|
// The masterTotalCount is already calculated from the range finding loop
|
||||||
for series in FSeriesList do
|
for series in FSeriesList do
|
||||||
begin
|
begin
|
||||||
if CalcView(series, masterTotalCount, seriesStart, seriesCount) then
|
if CalcView(series, masterTotalCount, seriesFirst, seriesLast) then
|
||||||
begin
|
begin
|
||||||
var offset := masterTotalCount - series.TotalCount;
|
var offset := masterTotalCount - series.TotalCount;
|
||||||
xTransform :=
|
xTransform :=
|
||||||
@@ -541,7 +515,7 @@ begin
|
|||||||
Result := rect.Right - (((index + offset - FViewStartIndex) / (FViewCount - 1)) * rect.Width);
|
Result := rect.Right - (((index + offset - FViewStartIndex) / (FViewCount - 1)) * rect.Width);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
series.Paint(Self.Canvas, seriesStart, seriesCount, xTransform, yTransform);
|
series.Paint(Self.Canvas, seriesFirst, seriesLast, xTransform, yTransform);
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -602,13 +576,6 @@ begin
|
|||||||
FData := TWriteable<TMycDataArray<T>>.CreateWriteable(FCurrData).Protect;
|
FData := TWriteable<TMycDataArray<T>>.CreateWriteable(FCurrData).Protect;
|
||||||
end;
|
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;
|
function TChartSeriesReceiver<T>.ProcessData(const Value: T): Boolean;
|
||||||
begin
|
begin
|
||||||
Result := true;
|
Result := true;
|
||||||
@@ -645,9 +612,9 @@ begin
|
|||||||
Result := FData.TotalCount;
|
Result := FData.TotalCount;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TChartSeriesProcessor<T>.Update: Boolean;
|
procedure TChartSeriesProcessor<T>.Update;
|
||||||
begin
|
begin
|
||||||
Result := FReceiver.GetData(FData);
|
FData := FReceiver.Data.Value;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
{ TChartOhlcSeries }
|
{ TChartOhlcSeries }
|
||||||
@@ -655,43 +622,22 @@ end;
|
|||||||
constructor TChartOhlcSeries.Create(
|
constructor TChartOhlcSeries.Create(
|
||||||
AOwner: TMycChart;
|
AOwner: TMycChart;
|
||||||
const ADataProvider: IMycDataProvider<TDataPoint<TOhlcItem>>;
|
const ADataProvider: IMycDataProvider<TDataPoint<TOhlcItem>>;
|
||||||
const AUpColor, ADownColor: TAlphaColor;
|
const AUpColor, ADownColor: TAlphaColor
|
||||||
AStyle: TCandleStyle
|
|
||||||
);
|
);
|
||||||
begin
|
begin
|
||||||
inherited Create(AOwner, ADataProvider);
|
inherited Create(AOwner, ADataProvider);
|
||||||
FUpColor := AUpColor;
|
FUpColor := AUpColor;
|
||||||
FDownColor := ADownColor;
|
FDownColor := ADownColor;
|
||||||
FStyle := AStyle;
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TChartOhlcSeries.GetValueRange(StartIndex, Count: Int64; out MinValue, MaxValue: Double): Boolean;
|
function TChartOhlcSeries.GetValueRange(First, Last: Int64; out MinValue, MaxValue: Double): Boolean;
|
||||||
var
|
var
|
||||||
i: Int64;
|
i: Int64;
|
||||||
seriesCount: Int64;
|
|
||||||
effectiveStart, effectiveEnd: Int64;
|
|
||||||
begin
|
begin
|
||||||
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;
|
MinValue := MaxDouble;
|
||||||
MaxValue := -MaxDouble;
|
MaxValue := -MaxDouble;
|
||||||
|
|
||||||
for i := effectiveStart to effectiveEnd do
|
for i := First to Last do
|
||||||
begin
|
begin
|
||||||
var dp := Data.Items[i];
|
var dp := Data.Items[i];
|
||||||
MinValue := Min(MinValue, dp.Data.Low);
|
MinValue := Min(MinValue, dp.Data.Low);
|
||||||
@@ -700,50 +646,39 @@ begin
|
|||||||
Result := (MinValue <> MaxDouble);
|
Result := (MinValue <> MaxDouble);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TChartOhlcSeries.Paint(const ACanvas: TCanvas; StartIndex, Count: Int64; const AXForm, AYForm: TFunc<Double, Single>);
|
procedure TChartOhlcSeries.Paint(const Canvas: TCanvas; First, Last: Int64; const XForm, YForm: TFunc<Double, Single>);
|
||||||
var
|
var
|
||||||
i: Int64;
|
i: Int64;
|
||||||
lastIndex: Int64;
|
|
||||||
x, candleWidth: Single;
|
x, candleWidth: Single;
|
||||||
yOpen, yHigh, yLow, yClose: Single;
|
yOpen, yHigh, yLow, yClose: Single;
|
||||||
item: TDataPoint<TOhlcItem>;
|
item: TDataPoint<TOhlcItem>;
|
||||||
isUp: boolean;
|
isUp: boolean;
|
||||||
begin
|
begin
|
||||||
if (Count <= 0) then
|
candleWidth := Max(2, 0.8 * Abs((XForm(First + 1) - XForm(First))));
|
||||||
Exit;
|
|
||||||
|
|
||||||
if (Count > 1) then
|
for i := First to Last do
|
||||||
candleWidth := Max(2, 0.8 * Abs((AXForm(StartIndex + 1) - AXForm(StartIndex))))
|
|
||||||
else
|
|
||||||
candleWidth := 10;
|
|
||||||
|
|
||||||
lastIndex := Min(Data.Count - 1, StartIndex + Count - 1);
|
|
||||||
for i := StartIndex to lastIndex do
|
|
||||||
begin
|
begin
|
||||||
item := Data.Items[i];
|
item := Data.Items[i];
|
||||||
x := AXForm(i);
|
x := XForm(i);
|
||||||
yOpen := AYForm(item.Data.Open);
|
yOpen := YForm(item.Data.Open);
|
||||||
yClose := AYForm(item.Data.Close);
|
yClose := YForm(item.Data.Close);
|
||||||
yHigh := AYForm(item.Data.High);
|
yHigh := YForm(item.Data.High);
|
||||||
yLow := AYForm(item.Data.Low);
|
yLow := YForm(item.Data.Low);
|
||||||
isUp := item.Data.Close >= item.Data.Open;
|
isUp := item.Data.Close >= item.Data.Open;
|
||||||
|
|
||||||
if isUp then
|
if isUp then
|
||||||
ACanvas.Stroke.Color := FUpColor
|
Canvas.Stroke.Color := FUpColor
|
||||||
else
|
else
|
||||||
ACanvas.Stroke.Color := FDownColor;
|
Canvas.Stroke.Color := FDownColor;
|
||||||
ACanvas.Stroke.Thickness := 1.0;
|
Canvas.Stroke.Thickness := 1.0;
|
||||||
|
|
||||||
ACanvas.DrawLine(TPointF.Create(x, yHigh), TPointF.Create(x, yLow), 1);
|
Canvas.DrawLine(TPointF.Create(x, yHigh), TPointF.Create(x, yLow), 1);
|
||||||
|
|
||||||
if (FStyle = csCandleStick) then
|
Canvas.Fill.Color := Canvas.Stroke.Color;
|
||||||
begin
|
if isUp then
|
||||||
ACanvas.Fill.Color := ACanvas.Stroke.Color;
|
Canvas.FillRect(TRectF.Create(x - candleWidth / 2, yClose, x + candleWidth / 2, yOpen), 0, 0, AllCorners, 1)
|
||||||
if isUp then
|
else
|
||||||
ACanvas.FillRect(TRectF.Create(x - candleWidth / 2, yClose, x + candleWidth / 2, yOpen), 0, 0, AllCorners, 1)
|
Canvas.FillRect(TRectF.Create(x - candleWidth / 2, yOpen, x + candleWidth / 2, yClose), 0, 0, AllCorners, 1);
|
||||||
else
|
|
||||||
ACanvas.FillRect(TRectF.Create(x - candleWidth / 2, yOpen, x + candleWidth / 2, yClose), 0, 0, AllCorners, 1);
|
|
||||||
end;
|
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -761,32 +696,13 @@ begin
|
|||||||
FLineWidth := ALineWidth;
|
FLineWidth := ALineWidth;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TChartLineSeries.GetValueRange(StartIndex, Count: Int64; out MinValue, MaxValue: Double): Boolean;
|
function TChartLineSeries.GetValueRange(First, Last: Int64; out MinValue, MaxValue: Double): Boolean;
|
||||||
var
|
var
|
||||||
i: Int64;
|
i: Int64;
|
||||||
seriesCount: Int64;
|
|
||||||
effectiveStart, effectiveEnd: Int64;
|
|
||||||
begin
|
begin
|
||||||
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;
|
MinValue := MaxDouble;
|
||||||
MaxValue := -MaxDouble;
|
MaxValue := -MaxDouble;
|
||||||
for i := effectiveStart to effectiveEnd do
|
for i := First to Last do
|
||||||
begin
|
begin
|
||||||
var v := Data.Items[i];
|
var v := Data.Items[i];
|
||||||
if not IsNaN(v) then
|
if not IsNaN(v) then
|
||||||
@@ -798,40 +714,28 @@ begin
|
|||||||
Result := (MinValue <> MaxDouble);
|
Result := (MinValue <> MaxDouble);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TChartLineSeries.Paint(const ACanvas: TCanvas; StartIndex, Count: Int64; const AXForm, AYForm: TFunc<Double, Single>);
|
procedure TChartLineSeries.Paint(const Canvas: TCanvas; First, Last: Int64; const XForm, YForm: TFunc<Double, Single>);
|
||||||
var
|
var
|
||||||
points: TPathData;
|
points: TPathData;
|
||||||
i, n: Int64;
|
i, n: Int64;
|
||||||
lastIndex: Int64;
|
|
||||||
begin
|
begin
|
||||||
if (Data.Count = 0) or (Count < 2) then
|
|
||||||
Exit;
|
|
||||||
|
|
||||||
lastIndex := Min(Data.Count - 1, StartIndex + Count - 1);
|
|
||||||
points := TPathData.Create;
|
points := TPathData.Create;
|
||||||
try
|
try
|
||||||
// Skip warmup data
|
// Skip warmup data
|
||||||
n := StartIndex;
|
n := First;
|
||||||
while (n <= lastIndex) and (IsNaN(Data[n])) do
|
while (n <= Last) and (IsNaN(Data[n])) do
|
||||||
begin
|
|
||||||
inc(n);
|
inc(n);
|
||||||
end;
|
if (n > Last) then
|
||||||
|
|
||||||
if (n > lastIndex) then
|
|
||||||
exit;
|
exit;
|
||||||
|
|
||||||
points.MoveTo(TPointF.Create(AXForm(n), AYForm(Data[n])));
|
points.MoveTo(TPointF.Create(XForm(n), YForm(Data[n])));
|
||||||
for i := n + 1 to lastIndex do
|
for i := n + 1 to Last do
|
||||||
begin
|
|
||||||
if not IsNaN(Data[i]) then
|
if not IsNaN(Data[i]) then
|
||||||
begin
|
points.LineTo(TPointF.Create(XForm(i), YForm(Data[i])));
|
||||||
points.LineTo(TPointF.Create(AXForm(i), AYForm(Data[i])));
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
|
|
||||||
ACanvas.Stroke.Color := FLineColor;
|
Canvas.Stroke.Color := FLineColor;
|
||||||
ACanvas.Stroke.Thickness := FLineWidth;
|
Canvas.Stroke.Thickness := FLineWidth;
|
||||||
ACanvas.DrawPath(points, 1);
|
Canvas.DrawPath(points, 1);
|
||||||
finally
|
finally
|
||||||
points.Free;
|
points.Free;
|
||||||
end;
|
end;
|
||||||
|
|||||||
Reference in New Issue
Block a user