2007. november 17., szombat
SignalDisplay component
Problem/Question/Abstract:
Ever wanted to display audio from a microphone? ever wanted to have the ability to see wave file actual samples like CoolEdit does?
Answer:
The following component allows:
Multiple data series.
Individual control over X axis and Y axis.
Paning
Zoom
and much more....
the original intention was to be able to display wave file samples like CoolEdit does, a lot of times you need to work on the data and doesn't need the graph component to hold a second copy (like in audio analysis software) so we wrote a component that doesn't hold the data but only displays it.
You can download a demo application (with source) that operates like CoolEdit in the sense it shows the actual samples of the wave file and a lot of neat options at: http://www.com-n-sense.com/ftproot/SignalDisplay.zip
(the zip file contains number of components such as: WaveFileParser and SignalDisplay and more...)
{*==============================================================================
Copyright (C) 2002, All rights reserved, Com-N-Sense Ltd
================================================================================
File: SignalDisplay.pas
Author: Liran Shahar, Com-N-Sense Ltd
Updated: 24/03/2022
Purpose: 2D signal graph display
================================================================================
History:
24/03/2002, Liran Shahar
- Axis visible property at design time bug fixed.
- Axis color property at design time bug fixed.
- Memory leak fixed (caused by unfreed series objects).
- Added ClearSeries procedure to clear the graph from all series (i.e data).
08/03/2002, Liran Shahar
- Initial release.
==============================================================================*}
unit SignalDisplay;
interface
uses
Windows, Messages, Sysutils, Classes, Graphics, Controls, Contnrs, Forms, Math,
SignalTypes;
const
X_MARGIN = 10;
Y_MARGIN = 10;
TICK_MARGIN = 4;
DEFAULT_WIDTH = 100;
DEFAULT_HEIGHT = 100;
type
TcnsBufferType = (btShortint, btByte, btSmallint, btWord, btLongint, btLongword,
btSingle, btDouble);
TcnsSignalDisplay = class;
TcnsSignalDisplayObject = class(TPersistent)
private
FVisible: boolean;
FColor: TColor;
Parent: TcnsSignalDisplay;
protected
procedure SetVisible(AVisible: boolean); virtual;
procedure SetColor(AColor: TColor); virtual;
procedure InitInternalVariables; virtual;
procedure NotifyParent; virtual; abstract;
public
constructor Create(AParent: TcnsSignalDisplay); virtual;
destructor Destroy; override;
published
property Visible: boolean read FVisible write SetVisible default true;
property Color: TColor read FColor write SetColor default clWhite;
end;
TcnsAxis = class(TcnsSignalDisplayObject)
private
FMin: double;
FMax: double;
FTicks: integer;
protected
procedure SetTicks(ATicks: integer); virtual;
procedure InitInternalVariables; override;
procedure NotifyParent; override;
public
procedure SetRange(AMin, AMax: double); virtual;
procedure DrawOn(Canvas: TCanvas; WorkRect: TRect; bVertical: boolean); virtual;
property Min: double read FMin;
property Max: double read FMax;
published
property Ticks: integer read FTicks write SetTicks default 0;
end;
TcnsSerie = class(TcnsSignalDisplayObject)
private
FBufferPtr: pointer;
FBufferType: TcnsBufferType;
FBufferSamples: integer;
FBufferStep: integer;
protected
procedure SetBufferPtr(ABufferPtr: pointer); virtual;
procedure SetBufferType(ABufferType: TcnsBufferType); virtual;
procedure SetBufferSamples(ABufferSamples: integer); virtual;
procedure SetBufferStep(ABufferStep: integer); virtual;
procedure InitInternalVariables; override;
procedure NotifyParent; override;
function GetSampleValue(iSample: integer): double; virtual;
public
procedure DrawOn(Canvas: TCanvas; WorkRect: TRect); virtual;
procedure GetMinMax(var dMin, dMax: double); virtual;
property BufferPtr: pointer read FBufferPtr write SetBufferPtr;
published
property BufferType: TcnsBufferType read FBufferType write SetBufferType default
btByte;
property BufferSamples: integer read FBufferSamples write SetBufferSamples default
0;
property BufferStep: integer read FBufferStep write SetBufferStep default 1;
end;
TcnsSignalDisplayMouseState = (gmsNormal, gmsZoom, gmsMove);
TcnsSignalDisplayDrawState = set of (dsEraseBackground, dsAxises, dsSeries);
TcnsSignalDisplayZoomKind = (zkFree, zkXAxis, zkYAxis);
TcnsSignalDisplay = class(TGraphicControl)
private
FXAxis: TcnsAxis;
FYAxis: TcnsAxis;
FColor: TColor;
LockCount: integer;
Series: TObjectList;
dXRatio: double;
dYRatio: double;
BackBuffer: TBitmap;
MarkerX, MarkerY, StartX, StartY, MoveX, MoveY: integer;
MouseState: TcnsSignalDisplayMouseState;
XAxisRect, YAxisRect, DataRect, RubberBandRect: TRect;
DrawState: TcnsSignalDisplayDrawState;
ZoomKind: TcnsSignalDisplayZoomKind;
protected
procedure CMMouseEnter(var Message: TMessage); message CM_MOUSEENTER;
procedure CMMouseLeave(var Message: TMessage); message CM_MOUSELEAVE;
procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
override;
procedure MouseMove(Shift: TShiftState; X, Y: Integer); override;
procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
override;
procedure DrawMarker(X, Y: integer); virtual;
procedure DrawRubberBand(StartX, StartY, EndX, EndY: integer; Kind:
TcnsSignalDisplayZoomKind); virtual;
procedure DrawMoveLine(X, Y: integer); virtual;
procedure CalculateAllRange; virtual;
procedure CalculateRects; virtual;
procedure DrawAxises; virtual;
procedure DrawSeries; virtual;
procedure Paint; override;
procedure Loaded; override;
function GetSerie(Index: integer): TcnsSerie; virtual;
procedure SetColor(AColor: TColor); virtual;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure Lock; virtual;
procedure Unlock; virtual;
procedure SetBounds(ALeft, ATop, AWidth, AHeight: integer); override;
function AddSerie: TcnsSerie; virtual;
function RemoveSerie(Serie: TcnsSerie): boolean; virtual;
procedure ClearSeries; virtual;
procedure MouseToWorld(Mx, My: integer; var Wx, Wy: double); virtual;
procedure WorldToMouse(Wx, Wy: double; var Mx, My: integer); virtual;
procedure Redraw(NewDrawState: TcnsSignalDisplayDrawState = []); virtual;
procedure DrawLine(X1, Y1, X2, Y2: double; Color: TColor); virtual;
property Serie[Index: integer]: TcnsSerie read GetSerie;
published
property XAxis: TcnsAxis read FXAxis write FXAxis;
property YAxis: TcnsAxis read FYAxis write FYAxis;
property Color: TColor read FColor write SetColor;
property OnCanResize;
property OnClick;
property OnConstrainedResize;
property OnContextPopup;
property OnDblClick;
property OnMouseDown;
property OnMouseMove;
property OnMouseUp;
property OnResize;
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('Com-N-Sense', [TcnsSignalDisplay]);
end;
//=============================================================================
// TcnsSignalDisplayObject
//=============================================================================
constructor TcnsSignalDisplayObject.Create(AParent: TcnsSignalDisplay);
begin
inherited Create;
Parent := AParent;
InitInternalVariables;
end;
destructor TcnsSignalDisplayObject.Destroy;
begin
inherited Destroy;
end;
procedure TcnsSignalDisplayObject.SetVisible(AVisible: boolean);
begin
if AVisible <> FVisible then
begin
FVisible := AVisible;
NotifyParent;
end; // if
end;
procedure TcnsSignalDisplayObject.SetColor(AColor: TColor);
begin
if AColor <> FColor then
begin
FColor := AColor;
NotifyParent;
end; // if
end;
procedure TcnsSignalDisplayObject.InitInternalVariables;
begin
FVisible := true;
FColor := clWhite;
end;
//=============================================================================
// TcnsAxis
//=============================================================================
procedure TcnsAxis.SetTicks(ATicks: integer);
begin
if ATicks <> FTicks then
begin
FTicks := ATicks;
NotifyParent;
end; // if
end;
procedure TcnsAxis.InitInternalVariables;
begin
inherited InitInternalVariables;
FMin := 0.0;
FMax := 0.0;
FTicks := 0;
end;
procedure TcnsAxis.NotifyParent;
begin
Parent.Redraw([dsEraseBackground, dsAxises]);
end;
procedure TcnsAxis.SetRange(AMin, AMax: double);
begin
if (AMin <> FMin) or (AMax <> FMax) then
begin
FMin := AMin;
FMax := AMax;
Parent.Redraw([dsEraseBackground, dsAxises, dsSeries]);
end; // if
end;
procedure TcnsAxis.DrawOn(Canvas: TCanvas; WorkRect: TRect; bVertical: boolean);
var
iTextWidth, iTextHeight, iLoop, iPos, iTicks: integer;
sText: AnsiString;
dTickDelta, dRangeDelta: double;
begin
iTextHeight := Canvas.TextHeight('0123456789');
Canvas.Font.Color := FColor;
Canvas.Pen.Color := FColor;
Canvas.Pen.Style := psSolid;
Canvas.Pen.Width := 1;
Canvas.Pen.Mode := pmCopy;
if not IsRectEmpty(WorkRect) then
with WorkRect do
begin
Canvas.Brush.Style := bsSolid;
Canvas.Brush.Color := Parent.Color;
Canvas.FillRect(WorkRect);
Canvas.Brush.Style := bsClear;
if bVertical then
begin
sText := format('%f', [FMax]);
Canvas.TextRect(WorkRect, Left + TICK_MARGIN, Top, sText);
sText := format('%f', [FMin]);
Canvas.TextRect(WorkRect, Left + TICK_MARGIN, Bottom - iTextHeight, sText);
iTicks := FTicks;
if iTicks > 0 then
begin
dTickDelta := (Bottom - Top + 1) / (iTicks + 1);
dRangeDelta := (FMax - FMin) / (iTicks + 1);
for iLoop := 1 to Ticks do
begin
iPos := Bottom - trunc(dTickDelta * iLoop);
Canvas.Polyline([Point(Left, iPos), Point(Left + TICK_MARGIN, iPos)]);
sText := format('%f', [FMin + iLoop * dRangeDelta]);
Canvas.TextRect(WorkRect, Left + TICK_MARGIN, iPos - iTextHeight shr 1,
sText);
end; // for
end; // if
Canvas.Polyline([Point(Right, Top), Point(Left, Top), Point(Left, Bottom),
Point(Right, Bottom)]);
end
else
begin
sText := format('%f', [FMin]);
Canvas.TextRect(WorkRect, Left + 1, Top + TICK_MARGIN, sText);
sText := format('%f', [FMax]);
iTextWidth := Canvas.TextWidth(sText);
Canvas.TextRect(WorkRect, Right - iTextWidth - 1, Top + TICK_MARGIN, sText);
iTicks := FTicks;
if iTicks > 0 then
begin
dTickDelta := (Right - Left + 1) / (iTicks + 1);
dRangeDelta := (FMax - FMin) / (iTicks + 1);
for iLoop := 1 to Ticks do
begin
iPos := Left + trunc(dTickDelta * iLoop);
Canvas.Polyline([Point(iPos, Top), Point(iPos, Top + TICK_MARGIN)]);
sText := format('%f', [FMin + iLoop * dRangeDelta]);
iTextWidth := Canvas.TextWidth(sText);
Canvas.TextRect(WorkRect, iPos - iTextWidth shr 1, Top + TICK_MARGIN,
sText);
end; // for
end; // if
Canvas.Polyline([Point(Left, Bottom), Point(Left, Top), Point(Right, Top),
Point(Right, Bottom)]);
end; // if/else
end; // with
end;
//=============================================================================
// TcnsSerie
//=============================================================================
procedure TcnsSerie.SetBufferPtr(ABufferPtr: pointer);
begin
if ABufferPtr <> FBufferPtr then
begin
FBufferPtr := ABufferPtr;
NotifyParent;
end; // if
end;
procedure TcnsSerie.SetBufferType(ABufferType: TcnsBufferType);
begin
if ABufferType <> FBufferType then
begin
FBufferType := ABufferType;
NotifyParent;
end; // if
end;
procedure TcnsSerie.SetBufferSamples(ABufferSamples: integer);
begin
if ABufferSamples <> FBufferSamples then
begin
FBufferSamples := ABufferSamples;
NotifyParent;
end; // if
end;
procedure TcnsSerie.SetBufferStep(ABufferStep: integer);
begin
if ABufferStep <> FBufferStep then
begin
FBufferStep := ABufferStep;
NotifyParent;
end; // if
end;
procedure TcnsSerie.InitInternalVariables;
begin
inherited InitInternalVariables;
FBufferPtr := nil;
FBufferType := btByte;
FBufferSamples := 0;
FBufferStep := 1;
end;
procedure TcnsSerie.NotifyParent;
begin
Parent.Redraw([dsSeries]);
end;
function TcnsSerie.GetSampleValue(iSample: integer): double;
begin
Result := 0;
case FBufferType of
btShortint: Result := PArrayShortint(FBufferPtr)^[iSample];
btByte: Result := PArrayByte(FBufferPtr)^[iSample];
btSmallint: Result := PArraySmallint(FBufferPtr)^[iSample];
btWord: Result := PArrayWord(FBufferPtr)^[iSample];
btLongint: Result := PArrayLongint(FBufferPtr)^[iSample];
btLongword: Result := PArrayLongword(FBufferPtr)^[iSample];
btSingle: Result := PArraySingle(FBufferPtr)^[iSample];
btDouble: Result := PArrayDouble(FBufferPtr)^[iSample];
end; // case
end;
procedure TcnsSerie.DrawOn(Canvas: TCanvas; WorkRect: TRect);
var
ClippingRgn: HRGN;
bFirst: boolean;
iLoop, iX, iY, iHeight, iSample, iNumberOfSamples, PrevX, PrevY: integer;
dValue: double;
begin
PrevX := -1;
PrevY := -1;
ClippingRgn := CreateRectRgnIndirect(WorkRect);
SelectClipRgn(Canvas.Handle, ClippingRgn);
iHeight := WorkRect.Bottom - WorkRect.Top + 1;
Canvas.Pen.Color := FColor;
Canvas.Pen.Style := psSolid;
Canvas.Pen.Width := 1;
bFirst := true;
with Parent.XAxis do
iNumberOfSamples := trunc(Max - Min);
for iLoop := 0 to iNumberOfSamples - 1 do
begin
iX := trunc(Parent.dXRatio * iLoop);
iSample := (iLoop + trunc(Parent.XAxis.Min)) * FBufferStep;
if (iSample >= 0) and (iSample < FBufferSamples) then
begin
dValue := GetSampleValue(iSample);
iY := iHeight - trunc((dValue - Parent.YAxis.Min) * Parent.dYRatio);
if bFirst or (iX <> PrevX) or (iY <> PrevY) then
begin
if bFirst then
Canvas.MoveTo(WorkRect.Left + iX, WorkRect.Top + iY)
else
Canvas.LineTo(WorkRect.Left + iX, WorkRect.Top + iY);
bFirst := false;
end; // if
PrevX := iX;
PrevY := iY;
end; // if
end; // for
SelectClipRgn(Canvas.Handle, 0);
DeleteObject(ClippingRgn);
end;
procedure TcnsSerie.GetMinMax(var dMin, dMax: double);
var
iSample: integer;
dSample: double;
begin
for iSample := 0 to FBufferSamples - 1 do
begin
dSample := GetSampleValue(iSample);
if iSample = 0 then
begin
dMin := dSample;
dMax := dSample;
end
else
begin
dMin := Min(dMin, dSample);
dMax := Max(dMax, dSample);
end; // if/else
end; // for
end;
//=============================================================================
// TcnsSignalDisplay
//=============================================================================
const
Y_TICK = 4;
X_TICK = 4;
MARKER_X_SIZE = 8;
MARKER_Y_SIZE = 8;
MARKER_COLOR = clWhite;
BAND_COLOR = clWhite;
MOVE_LINE_COLOR = clWhite;
constructor TcnsSignalDisplay.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FXAxis := TcnsAxis.Create(Self);
FYAxis := TcnsAxis.Create(Self);
Width := DEFAULT_WIDTH;
Height := DEFAULT_HEIGHT;
LockCount := 0;
Series := TObjectList.Create;
Series.OwnsObjects := true;
MarkerX := -1;
MarkerY := -1;
MoveX := -1;
MoveY := -1;
MouseState := gmsNormal;
end;
destructor TcnsSignalDisplay.Destroy;
begin
FreeAndNil(FXAxis);
FreeAndNil(FYAxis);
FreeAndNil(Series);
inherited Destroy;
end;
procedure TcnsSignalDisplay.CMMouseEnter(var Message: TMessage);
begin
inherited;
MouseState := gmsNormal;
end;
procedure TcnsSignalDisplay.CMMouseLeave(var Message: TMessage);
begin
inherited;
DrawMarker(-1, -1);
end;
procedure TcnsSignalDisplay.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y:
Integer);
var
WorldRect: TRect;
begin
WorldRect.TopLeft := ClientToScreen(DataRect.TopLeft);
WorldRect.BottomRight := ClientToScreen(DataRect.BottomRight);
if PtInRect(DataRect, Point(X, Y)) then
begin
if (Button = mbLeft) then
begin
MouseState := gmsZoom;
if ssShift in Shift then
ZoomKind := zkYAxis
else if ssCtrl in Shift then
ZoomKind := zkXAxis
else
ZoomKind := zkFree;
StartX := X;
StartY := Y;
ClipCursor(@WorldRect);
end
else if (Button = mbRight) then
begin
MouseState := gmsMove;
StartX := X;
StartY := Y;
ClipCursor(@WorldRect);
end;
end; // if
inherited;
end;
procedure TcnsSignalDisplay.MouseMove(Shift: TShiftState; X, Y: Integer);
begin
case MouseState of
gmsNormal:
if PtInRect(DataRect, Point(X, Y)) then
begin
Cursor := crNone;
DrawMarker(X, Y)
end
else
begin
DrawMarker(-1, -1);
Cursor := crDefault;
end; // if
gmsZoom:
begin
DrawMarker(X, Y);
DrawRubberBand(StartX, StartY, X, Y, ZoomKind);
end;
gmsMove:
begin
DrawMoveLine(X, Y);
DrawMarker(X, Y);
end;
end; // case
inherited;
end;
procedure TcnsSignalDisplay.MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y:
Integer);
var
dXMin, dXMax, dYMin, dYMax: double;
begin
DrawMarker(-1, -1);
case MouseState of
gmsNormal:
if Button = mbMiddle then
begin
CalculateAllRange;
end; // if
gmsZoom:
begin
with RubberBandRect.TopLeft do
MouseToWorld(X, Y, dXMin, dYMax);
with RubberBandRect.BottomRight do
MouseToWorld(X, Y, dXMax, dYMin);
DrawRubberBand(0, 0, 0, 0, ZoomKind);
MouseState := gmsNormal;
Lock;
if ZoomKind in [zkFree, zkXAxis] then
FXAxis.SetRange(dXMin, dXMax);
if ZoomKind in [zkFree, zkYAxis] then
FYAxis.SetRange(dYMin, dYMax);
Unlock;
ClipCursor(nil);
end;
gmsMove:
begin
Lock;
if dXRatio <> 0 then
with FXAxis do
SetRange(Min - (X - StartX) / dXRatio, Max - (X - StartX) / dXRatio);
if dYRatio <> 0 then
with FYAxis do
SetRange(Min + (Y - StartY) / dYRatio, Max + (Y - StartY) / dYRatio);
MouseState := gmsNormal;
DrawMoveLine(-1, -1);
Unlock;
ClipCursor(nil);
end;
end; // case
DrawMarker(X, Y);
inherited;
end;
procedure TcnsSignalDisplay.DrawMarker(X, Y: integer);
begin
Canvas.Pen.Mode := pmXor;
Canvas.Pen.Color := MARKER_COLOR;
Canvas.Pen.Width := 1;
if (MarkerX <> -1) and (MarkerY <> -1) then
begin
Canvas.MoveTo(MarkerX, MarkerY - MARKER_Y_SIZE);
Canvas.LineTo(MarkerX, MarkerY + MARKER_Y_SIZE);
Canvas.MoveTo(MarkerX - MARKER_X_SIZE, MarkerY);
Canvas.LineTo(MarkerX + MARKER_X_SIZE, MarkerY);
MarkerX := -1;
MarkerY := -1;
end; // if
if (X <> -1) and (Y <> -1) then
begin
MarkerX := X;
MarkerY := Y;
Canvas.MoveTo(MarkerX, MarkerY - MARKER_Y_SIZE);
Canvas.LineTo(MarkerX, MarkerY + MARKER_Y_SIZE);
Canvas.MoveTo(MarkerX - MARKER_X_SIZE, MarkerY);
Canvas.LineTo(MarkerX + MARKER_X_SIZE, MarkerY);
end; // if
end;
procedure TcnsSignalDisplay.DrawRubberBand(StartX, StartY, EndX, EndY: integer; Kind:
TcnsSignalDisplayZoomKind);
begin
Canvas.Pen.Mode := pmXor;
Canvas.Pen.Color := BAND_COLOR;
Canvas.Pen.Width := 1;
Canvas.Pen.Style := psDot;
if not IsRectEmpty(RubberBandRect) then
with RubberBandRect do
Canvas.Polyline([Point(Left, Top), Point(Right, Top), Point(Right, Bottom),
Point(Left, Bottom), Point(Left, Top)]);
case Kind of
zkYAxis:
begin
StartX := DataRect.Left;
EndX := DataRect.Right - 1;
end;
zkXAxis:
begin
StartY := DataRect.Top;
EndY := DataRect.Bottom - 1;
end;
end;
RubberBandRect.Left := Min(StartX, EndX);
RubberBandRect.Top := Min(StartY, EndY);
RubberBandRect.Right := Max(StartX, EndX);
RubberBandRect.Bottom := Max(StartY, EndY);
if not IsRectEmpty(RubberBandRect) then
with RubberBandRect do
Canvas.Polyline([Point(Left, Top), Point(Right, Top), Point(Right, Bottom),
Point(Left, Bottom), Point(Left, Top)]);
end;
procedure TcnsSignalDisplay.DrawMoveLine(X, Y: integer);
begin
Canvas.Pen.Mode := pmXor;
Canvas.Pen.Color := MOVE_LINE_COLOR;
Canvas.Pen.Width := 1;
Canvas.Pen.Style := psDash;
if (MoveX <> -1) and (MoveY <> -1) then
begin
Canvas.MoveTo(StartX, StartY);
Canvas.LineTo(MoveX, MoveY);
MoveX := -1;
MoveY := -1;
end; // if
if (X <> -1) and (Y <> -1) then
begin
Canvas.MoveTo(StartX, StartY);
Canvas.LineTo(X, Y);
MoveX := X;
MoveY := Y;
end; // if
end;
procedure TcnsSignalDisplay.CalculateAllRange;
var
XMin, XMax, YMin, YMax, TmpYMin, TmpYMax: double;
iLoop: integer;
Serie: TcnsSerie;
begin
XMax := 0;
XMin := 0;
for iLoop := 0 to Series.Count - 1 do
begin
Serie := GetSerie(iLoop);
if iLoop = 0 then
begin
XMax := Serie.BufferSamples;
Serie.GetMinMax(YMin, YMax);
end
else
begin
XMax := Max(XMax, Serie.BufferSamples);
Serie.GetMinMax(TmpYMin, TmpYMax);
YMin := Min(YMin, TmpYMin);
YMax := Max(YMax, TmpYMax);
end; // if/else
end;
Lock;
FXAxis.SetRange(XMin, XMax);
FYAxis.SetRange(YMin, YMax);
Unlock;
end;
procedure TcnsSignalDisplay.CalculateRects;
var
iLeft, iTop, iRight, iBottom, iTextWidth, iTextHeight: integer;
begin
XAxisRect := Rect(0, 0, 0, 0);
YAxisRect := Rect(0, 0, 0, 0);
iLeft := ClientRect.Left + X_MARGIN;
iTop := ClientRect.Top + Y_MARGIN;
iRight := ClientRect.Right - X_MARGIN - TICK_MARGIN;
iBottom := ClientRect.Bottom - Y_MARGIN - TICK_MARGIN;
iTextWidth := Math.Max(Canvas.TextWidth(format('%fW', [FYAxis.Min])),
Canvas.TextWidth(format('%fW', [FYAxis.Max])));
iTextHeight := BackBuffer.Canvas.TextHeight('0123456789');
DataRect := Rect(iLeft, iTop, iRight, iBottom);
if FXAxis.Visible then
DataRect.Bottom := iBottom - iTextHeight;
if FYAxis.Visible then
DataRect.Right := iRight - iTextWidth;
with DataRect do
begin
if FXAxis.Visible then
XAxisRect := Rect(iLeft, Bottom + 1, Right, iBottom + TICK_MARGIN);
if FYAxis.Visible then
YAxisRect := Rect(Right + 1, Top, iRight + TICK_MARGIN, Bottom);
end; // with
dXRatio := 0;
dYRatio := 0;
with FXAxis do
dXRatio := (DataRect.Right - DataRect.Left + 1) / (Max - Min + 1);
with FYAxis do
dYRatio := (DataRect.Bottom - DataRect.Top + 1) / (Max - Min + 1);
end;
procedure TcnsSignalDisplay.DrawAxises;
begin
FXAxis.DrawOn(BackBuffer.Canvas, XAxisRect, false);
FYAxis.DrawOn(BackBuffer.Canvas, YAxisRect, true);
end;
procedure TcnsSignalDisplay.DrawSeries;
var
iSerie: integer;
Serie: TcnsSerie;
begin
BackBuffer.Canvas.Brush.Color := FColor;
BackBuffer.Canvas.FillRect(DataRect);
for iSerie := 0 to Series.Count - 1 do
begin
Serie := GetSerie(iSerie);
with Serie do
if Visible and assigned(BufferPtr) then
DrawOn(BackBuffer.Canvas, DataRect);
end; // for
end;
procedure TcnsSignalDisplay.Paint;
begin
if not assigned(BackBuffer) then
begin
BackBuffer := TBitmap.Create;
BackBuffer.Width := Width;
BackBuffer.Height := Height;
BackBuffer.PixelFormat := pf24Bit;
DrawState := DrawState + [dsEraseBackground, dsAxises, dsSeries];
end; // if
if dsEraseBackground in DrawState then
begin
BackBuffer.Canvas.Brush.Color := FColor;
BackBuffer.Canvas.FillRect(ClientRect);
end; // if
CalculateRects;
if dsAxises in DrawState then
DrawAxises;
if dsSeries in DrawState then
DrawSeries;
Canvas.Draw(0, 0, BackBuffer);
DrawState := [];
end;
procedure TcnsSignalDisplay.Loaded;
begin
inherited Loaded;
FreeAndNil(BackBuffer);
Redraw([dsEraseBackground, dsAxises, dsSeries]);
end;
function TcnsSignalDisplay.GetSerie(Index: integer): TcnsSerie;
begin
Result := nil;
if (Index >= 0) and (Index < Series.Count) then
Result := TcnsSerie(Series[Index]);
end;
procedure TcnsSignalDisplay.SetColor(AColor: TColor);
begin
if AColor <> FColor then
begin
FColor := AColor;
Redraw([dsEraseBackground, dsSeries, dsAxises]);
end; // if
end;
procedure TcnsSignalDisplay.Lock;
begin
LockCount := LockCount + 1;
end;
procedure TcnsSignalDisplay.Unlock;
begin
LockCount := LockCount - 1;
Redraw;
end;
procedure TcnsSignalDisplay.SetBounds(ALeft, ATop, AWidth, AHeight: integer);
begin
inherited SetBounds(ALeft, ATop, AWidth, AHeight);
FreeAndNil(BackBuffer);
end;
function TcnsSignalDisplay.AddSerie: TcnsSerie;
begin
Result := TcnsSerie.Create(Self);
Series.Add(Result);
end;
function TcnsSignalDisplay.RemoveSerie(Serie: TcnsSerie): boolean;
var
iIndex: integer;
begin
Result := true;
iIndex := Series.IndexOf(Serie);
if iIndex > -1 then
begin
Series.Delete(iIndex);
Redraw([dsSeries]);
end
else
Result := false;
end;
procedure TcnsSignalDisplay.ClearSeries;
begin
Series.Clear;
end;
procedure TcnsSignalDisplay.MouseToWorld(Mx, My: integer; var Wx, Wy: double);
begin
Wx := 0;
if dXRatio <> 0 then
Wx := FXAxis.FMin + (Mx - DataRect.Left) / dXRatio;
Wy := 0;
if dYRatio <> 0 then
Wy := FYAxis.FMax - (My - DataRect.Top) / dYRatio;
end;
procedure TcnsSignalDisplay.WorldToMouse(Wx, Wy: double; var Mx, My: integer);
begin
Mx := 0;
My := 0;
if dXRatio <> 0 then
Mx := DataRect.Left + trunc((Wx - FXAxis.FMin) * dXRatio);
if dYRatio <> 0 then
My := DataRect.Top + trunc((FYAxis.FMax - Wy) * dYRatio);
end;
procedure TcnsSignalDisplay.Redraw(NewDrawState: TcnsSignalDisplayDrawState);
begin
DrawState := DrawState + NewDrawState;
if LockCount = 0 then
Repaint;
end;
procedure TcnsSignalDisplay.DrawLine(X1, Y1, X2, Y2: double; Color: TColor);
var
iX1, iY1, iX2, iY2: integer;
begin
WorldToMouse(X1, Y1, iX1, iY1);
WorldToMouse(X2, Y2, iX2, iY2);
Canvas.Pen.Color := Color;
Canvas.Pen.Style := psSolid;
Canvas.Pen.Mode := pmCopy;
Canvas.MoveTo(iX1, iY1);
Canvas.LineTo(iX2, iY2);
end;
end.
Feliratkozás:
Megjegyzések küldése (Atom)
Nincsenek megjegyzések:
Megjegyzés küldése