Mega Code Archive

 
Categories / Delphi / VCL
 

SignalDisplay component

Title: SignalDisplay component Question: 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: 1. Multiple data series. 2. Individual control over X axis and Y axis. 3. Paning 4. 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 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 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.