Units renamed & Chart refactored

This commit is contained in:
Michael Schimmel
2025-07-14 15:07:36 +02:00
parent 661faba75c
commit d0ad547aa3
20 changed files with 340 additions and 1050 deletions
+53 -82
View File
@@ -18,7 +18,7 @@ uses
Myc.Trade.Types,
Myc.Trade.DataPoint,
Myc.Signals,
Myc.Lazy;
Myc.Mutable;
type
TMycChart = class(TStyledControl)
@@ -59,18 +59,14 @@ type
constructor Create(AParent: TPanel);
end;
TAxisLayer = class(TLayer)
TAxisLayer = class abstract(TLayer)
private
FOwner: TMycChart;
protected
function GetOwner: TMycChart; override; final;
// Paint crosshair and other axis related overlays
procedure Paint(
const Canvas: TCanvas;
const Viewport: TRectF;
ViewStartIndex, ViewCount: Int64;
const MousePos: TPointF
); virtual;
procedure Paint(const Canvas: TCanvas; const Viewport: TRectF; ViewStartIndex, ViewCount: Int64; IdxAtMousePos: Integer); virtual;
abstract;
public
constructor Create(AOwner: TMycChart);
end;
@@ -78,12 +74,7 @@ type
TXAxisLayer = class(TAxisLayer)
protected
// Paint crosshair and time caption
procedure Paint(
const Canvas: TCanvas;
const Viewport: TRectF;
ViewStartIndex, ViewCount: Int64;
const MousePos: TPointF
); override;
procedure Paint(const Canvas: TCanvas; const Viewport: TRectF; ViewStartIndex, ViewCount: Int64; IdxAtMousePos: Integer); override;
// This function delivers the text for the caption.
function GetCaption(Idx: Int64): String; virtual; abstract;
end;
@@ -119,6 +110,11 @@ type
property Weight: Single read FWeight write SetWeight;
end;
TDragPoint = record
Point: TPointF;
Idx: Int64;
end;
private
FPanelList: TObjectList<TPanel>;
FXAxisSeries: TMycChart.TXAxisLayer;
@@ -127,8 +123,8 @@ type
FViewStartIndex: Int64;
FViewCount: Int64;
FIsDragging: Boolean;
FDragStartPoint: TPointF;
FMousePos: TPointF;
FMousePos: TDragPoint;
FDragStartPoint: TDragPoint;
FIsMouseInControl: Boolean;
FJumpButtonRect: TRectF;
FJumpButtonHot: Boolean;
@@ -139,6 +135,7 @@ type
FHotResizePanelIndex: Integer;
function GetPanel(Index: Integer): TPanel;
function GetPanelCount: Integer;
function CreateDragPoint(X, Y: Single): TDragPoint;
protected
procedure Paint; override;
procedure DoIdle;
@@ -221,6 +218,15 @@ begin
Repaint;
end;
function TMycChart.CreateDragPoint(X, Y: Single): TDragPoint;
begin
Result.Point.X := X;
Result.Point.Y := Y;
Result.Idx := Round((LocalRect.Right - X) * (FViewCount - 1) / LocalRect.Width);
if (Result.Idx < 0) or (Result.Idx >= FXAxisSeries.Series.Count) then
Result.Idx := -1;
end;
function TMycChart.GetPanel(Index: Integer): TPanel;
begin
Result := FPanelList[Index];
@@ -333,8 +339,12 @@ begin
end;
// Draw crosshair if mouse is in control
if FIsMouseInControl and Assigned(FXAxisSeries) and not FIsDragging and not FIsResizing then
FXAxisSeries.Paint(Self.Canvas, rect, FViewStartIndex, FViewCount, FMousePos);
if FIsMouseInControl and Assigned(FXAxisSeries) and (FMousePos.Idx >= 0 ) then
begin
// Do not draw crosshair if mouse is over the jump button
if not FJumpButtonRect.Contains(FMousePos.Point) then
FXAxisSeries.Paint(Self.Canvas, rect, FViewStartIndex, FViewCount, FViewStartIndex + FMousePos.Idx );
end;
// --- Draw the "jump to latest" button (code unchanged) ---
isButtonVisible := (FViewStartIndex > 0);
@@ -380,6 +390,8 @@ end;
procedure TMycChart.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Single);
begin
inherited MouseDown(Button, Shift, X, Y);
// Check for button press first
if (Button = TMouseButton.mbLeft) and (FViewStartIndex > 0) and FJumpButtonRect.Contains(PointF(X, Y)) then
begin
@@ -393,16 +405,15 @@ begin
begin
FIsResizing := true;
FResizePanelIndex := FHotResizePanelIndex;
FDragStartPoint := TPointF.Create(X, Y);
FDragStartPoint := CreateDragPoint(X, Y);
Capture;
exit;
end;
inherited MouseDown(Button, Shift, X, Y);
if (Button = TMouseButton.mbLeft) then
begin
FIsDragging := true;
FDragStartPoint := TPointF.Create(X, Y);
FDragStartPoint := CreateDragPoint(X, Y);
Capture;
end;
end;
@@ -427,10 +438,7 @@ begin
end;
// Stop panning
if FIsDragging then
begin
FIsDragging := false;
end;
FIsDragging := false;
// Stop button press
if FJumpButtonPressed then
@@ -459,12 +467,14 @@ begin
if not FIsMouseInControl then
FIsMouseInControl := true;
FMousePos := TPointF.Create(X, Y);
FMousePos := CreateDragPoint(X, Y);
// --- Panel Resizing Logic ---
if FIsResizing then
begin
dy := Y - FDragStartPoint.Y;
dy := Y - FDragStartPoint.Point.Y;
if Abs(dx) < 2 then // Threshold to avoid jitter
exit;
var panel1 := Panels[FResizePanelIndex];
var panel2 := Panels[FResizePanelIndex + 1];
@@ -502,7 +512,7 @@ begin
panel2.Weight := (newH2 / (newH1 + newH2)) * twoPanelWeight;
end;
FDragStartPoint.Y := Y; // Update start point for next move
FDragStartPoint := CreateDragPoint( FDragStartPoint.Point.X, Y ); // Update start point for next move
Repaint;
exit; // Do not continue with other mouse move logic
end;
@@ -510,17 +520,17 @@ begin
// --- Chart Panning Logic ---
if FIsDragging then
begin
dx := X - FDragStartPoint.X;
dx := X - FDragStartPoint.Point.X;
if Abs(dx) < 2 then // Threshold to avoid jitter
begin
exit;
end;
if not Assigned(FXAxisSeries) or (FViewCount <= 0) then
exit;
indexDelta := Round(dx / (Self.Width / FViewCount));
FViewStartIndex := FViewStartIndex + indexDelta;
indexDelta := FMousePos.Idx - FDragStartPoint.Idx;
FDragStartPoint.Idx := FDragStartPoint.Idx + indexDelta;
FViewStartIndex := FViewStartIndex - indexDelta;
// Clamp values
maxIndex := FXAxisSeries.Series.Count - FViewCount;
@@ -531,8 +541,6 @@ begin
if (FViewStartIndex > maxIndex) then
FViewStartIndex := maxIndex;
FDragStartPoint.X := X;
FDragStartPoint.Y := Y;
Repaint;
exit;
end;
@@ -574,12 +582,11 @@ end;
procedure TMycChart.MouseWheel(Shift: TShiftState; WheelDelta: Integer; var Handled: Boolean);
var
mousePos: TPointF;
anchorIndex: Double;
zoomFactor: Double;
newViewCount: Int64;
ratio: Double;
begin
inherited MouseWheel(Shift, WheelDelta, Handled);
Handled := true;
// An X-axis series is required for zooming
@@ -590,11 +597,6 @@ begin
if (xAxisCount = 0) or (Width <= 0) then
exit;
mousePos := ScreenToLocal(Screen.MousePos);
// Calculate the anchor index for a right-to-left axis
anchorIndex := FViewStartIndex + ((Self.Width - mousePos.X) / Self.Width) * FViewCount;
if (WheelDelta > 0) then
zoomFactor := 0.8 // Zoom In
else
@@ -610,15 +612,12 @@ begin
if (newViewCount = FViewCount) then
exit;
// Adjust start index to keep anchor point stable
if (FViewCount > 0) then
ratio := (anchorIndex - FViewStartIndex) / FViewCount
else
ratio := 0;
FViewStartIndex := Round(anchorIndex - (ratio * newViewCount));
FViewCount := newViewCount;
var oldIdx := FViewStartIndex + FMousePos.Idx;
FMousePos := CreateDragPoint( FMousePos.Point.X, FMousePos.Point.Y );
FViewStartIndex := oldIdx - FMousePos.Idx;
// Clamp start index
if (FViewStartIndex < 0) then
FViewStartIndex := 0;
@@ -818,51 +817,23 @@ begin
Result := FOwner;
end;
procedure TMycChart.TAxisLayer.Paint(
const Canvas: TCanvas;
const Viewport: TRectF;
ViewStartIndex, ViewCount: Int64;
const MousePos: TPointF
);
begin
// Do nothing in base class
end;
{ TMycChart.TXAxisLayer }
procedure TMycChart.TXAxisLayer.Paint(
const Canvas: TCanvas;
const Viewport: TRectF;
ViewStartIndex, ViewCount: Int64;
const MousePos: TPointF
);
procedure TMycChart.TXAxisLayer.Paint(const Canvas: TCanvas; const Viewport: TRectF; ViewStartIndex, ViewCount: Int64; IdxAtMousePos:
Integer);
var
idx: Int64;
caption: string;
captionRect: TRectF;
padding: Single;
indexFromMouse: Double;
candleX: Single;
textSize: TRectF;
begin
// Do not draw crosshair if mouse is over the jump button
if Owner.JumpButtonRect.Contains(MousePos) then
exit;
// Do not draw if no data or view is invalid
if (not Assigned(Series)) or (Series.Count = 0) or (ViewCount <= 1) then
exit;
// 1. Calculate the data index from the mouse X position
indexFromMouse := ViewStartIndex + (Viewport.Right - MousePos.X) * (ViewCount - 1) / Viewport.Width;
idx := Round(indexFromMouse);
// Check if the calculated index is valid for the series
if (idx < 0) or (idx >= Series.Count) then
exit;
// Snap X to the candle's center
candleX := Viewport.Right - ((idx - ViewStartIndex) * (ViewCount - 1)) / (ViewCount - 1) * (Viewport.Width / (ViewCount - 1));
candleX := Viewport.Right - ((IdxAtMousePos - ViewStartIndex) * (ViewCount - 1)) / (ViewCount - 1) * (Viewport.Width / (ViewCount - 1));
// 2. Draw the vertical line at the snapped position
Canvas.Stroke.Kind := TBrushKind.Solid;
@@ -871,7 +842,7 @@ begin
Canvas.DrawLine(PointF(candleX, Viewport.Top), PointF(candleX, Viewport.Bottom), 1.0);
// 3. Get the caption for the index
caption := GetCaption(idx);
caption := GetCaption(IdxAtMousePos);
if caption.IsEmpty then
exit;