1221 lines
41 KiB
ObjectPascal

{****************************************************
This file is part of the Eye Candy Controls (EC-C)
Copyright (C) 2013 Vojtěch Čihák, Czech Republic
This library is free software.
See the file COPYING.LGPL.txt,
included in this distribution,
for details about the license.
****************************************************}
unit ECScale;
{$mode objfpc}{$H+}
//{$DEFINE DBGSCALE} {don't remove, just comment}
interface
uses
Classes, SysUtils, Controls, ECTypes, Graphics,
{$IFDEF DBGSCALE} LCLProc, {$ENDIF} LCLType, Math, Themes;
type
{$PACKENUM 2}
TScaleType = (estLinear, estLogarithmic);
TTickAlign = (etaOuter, etaInner, etaCenter);
TTickSize = (etsShort, etsMiddle, etsLong);
TTickMetrics = record
Pos: Integer;
Size: TTickSize;
end;
TTicksVisible = (etvNone, etvLongOnly, etvLongShort, etvAll);
TValueDisplay = (evdNormal, evdTopLeft, evdBottomRight, evdTopLeftInside,
evdBttmRightInside, evdCompactTopLeft, evdCompactBttmRight);
TScaleValueFormat = (esvfAutoRound, esvfFixedRound, esvfSmartRound, esvfExponential, esvfHexadecimal,
esvfMarkHexadec, esvfOctal, esvfBinary, esvfDate, esvfTime, esvfText);
const
cDefTickAlign = etaOuter;
cDefTickIndent = 5;
cDefTickLength = 20;
cDefTickVisible = etvAll;
cDefValFormat = esvfAutoRound;
cDefValIndent = 5;
cDefValVisible = evvAll;
type
{ TCustomECScale }
TCustomECScale = class(TPersistent)
private
FMax: Double;
FMin: Double;
FOnPrepareValue: TOnPrepareValue;
FText: TStrings;
procedure SetCorrection(AValue: Double);
procedure SetDateTimeFormat(const AValue: string);
procedure SetDigits(const AValue: SmallInt);
procedure SetFontOrientation(const AValue: Integer);
procedure SetLogarithmBase(const AValue: Double);
procedure SetMax(const AValue: Double);
procedure SetMin(const AValue: Double);
procedure SetScaleType(const AValue: TScaleType);
procedure SetText(const AValue: TStrings);
procedure SetTickAlign(const AValue: TTickAlign);
procedure SetTickColor(const AValue: TColor);
procedure SetTickDesign(const AValue: TTickDesign);
procedure SetTickIndent(const AValue: SmallInt);
procedure SetTickLength(const AValue: SmallInt);
procedure SetTickLongValue(const AValue: Double);
procedure SetTickMiddleValue(const AValue: Double);
procedure SetTickShortValue(const AValue: Double);
procedure SetTickVisible(const AValue: TTicksVisible);
procedure SetValueDisplay(const AValue: TValueDisplay);
procedure SetValueFormat(const AValue: TScaleValueFormat);
procedure SetValueIndent(const AValue: SmallInt);
procedure SetValueShift(const AValue: SmallInt);
procedure SetValueVisible(const AValue: TValuesVisibility);
protected
FCorrection: Double;
FDateTimeFormat: string;
FDigits: SmallInt;
FFontOrientation: Integer;
FLogarithmBase: Double;
FScaleType: TScaleType;
FTickAlign: TTickAlign;
FTickColor: TColor;
FTickDesign: TTickDesign;
FTickIndent: SmallInt;
FTickLength: SmallInt;
FTickLongValue: Double;
FTickMiddleValue: Double;
FTickShortValue: Double;
FTickVisible: TTicksVisible;
FValueDisplay: TValueDisplay;
FValueFormat: TScaleValueFormat;
FValueIndent: SmallInt;
FValueShift: SmallInt;
FValueVisible: TValuesVisibility;
protected
LengthPx, MaxL, MinL: Integer;
Reversed: Boolean;
Parent: TControl;
procedure CalcTicksValuesLinear(ALength: Integer; AReversed: Boolean);
procedure CalcTicksValuesLogarithmic(ALength: Integer; AReversed: Boolean);
function GetStringValue(AIndex: Integer): string; overload;
procedure Redraw; virtual;
procedure RecalcRedraw; virtual;
public
DTFormat: TFormatSettings;
OnRecalcRedraw: TObjectMethod;
OnRedraw: TObjectMethod;
TickArray: array of TTickMetrics;
UpdateCount: Integer;
ValueArray: array of Double;
constructor Create(AParent: TControl);
destructor Destroy; override;
procedure BeginUpdate;
procedure CalcTickPosAndValues(ALength: Integer; AReversed: Boolean);
procedure Draw(ACanvas: TCanvas; ATicks, AValues: Boolean; AScalePos: TObjectPos;
ATickStart: TPoint; AExtraValues: array of Double); overload;
procedure Draw(ACanvas: TCanvas; ATicks, AValues: Boolean;
AScalePos: TObjectPos; AColor3DDark, AColor3DLight: TColor;
ATickStart: TPoint; AExtraValues: array of Double);
procedure EndUpdate;
function GetPreferredSize(ACanvas: TCanvas; AHorizontal: Boolean;
ATicks: Boolean = True; AValues: Boolean = True): Integer;
function GetPreferredValuesHeight(ACanvas: TCanvas): Integer;
function GetPreferredValuesWidth(ACanvas: TCanvas): Integer;
function GetStringMax: string;
function GetStringMin: string;
function GetStringPosition(APosition: Double): string; overload;
function GetStringPosition(APosition: Double; ARound: SmallInt): string; overload;
function GetStringValue(AValue: Double; ARound: Integer): string; overload;
function GetTextSize(ACanvas: TCanvas; AStrVal: string; AHorizontal: Boolean): Integer;
property Correction: Double read FCorrection write SetCorrection;
property DateTimeFormat: string read FDateTimeFormat write SetDateTimeFormat;
property Digits: SmallInt read FDigits write SetDigits default 0;
property FontOrientation: Integer read FFontOrientation write SetFontOrientation default 0;
property LogarithmBase: Double read FLogarithmBase write SetLogarithmBase;
property Max: Double read FMax write SetMax;
property Min: Double read FMin write SetMin;
property ScaleType: TScaleType read FScaleType write SetScaleType default estLinear;
property Text: TStrings read FText write SetText;
property TickAlign: TTickAlign read FTickAlign write SetTickAlign default cDefTickAlign;
property TickColor: TColor read FTickColor write SetTickColor default clDefault;
property TickDesign: TTickDesign read FTickDesign write SetTickDesign default etdSimple;
property TickIndent: SmallInt read FTickIndent write SetTickIndent default cDefTickIndent;
property TickLength: SmallInt read FTickLength write SetTickLength default cDefTickLength;
property TickLongValue: Double read FTickLongValue write SetTickLongValue;
property TickMiddleValue: Double read FTickMiddleValue write SetTickMiddleValue;
property TickShortValue: Double read FTickShortValue write SetTickShortValue;
property TickVisible: TTicksVisible read FTickVisible write SetTickVisible default cDefTickVisible;
property ValueDisplay: TValueDisplay read FValueDisplay write SetValueDisplay default evdNormal;
property ValueFormat: TScaleValueFormat read FValueFormat write SetValueFormat default cDefValFormat;
property ValueIndent: SmallInt read FValueIndent write SetValueIndent default cDefValIndent;
property ValueShift: SmallInt read FValueShift write SetValueShift default 0;
property ValueVisible: TValuesVisibility read FValueVisible write SetValueVisible default cDefValVisible;
property OnPrepareValue: TOnPrepareValue read FOnPrepareValue write FOnPrepareValue;
end;
{ TECScale }
TECScale = class(TCustomECScale)
published
property DateTimeFormat;
property Digits;
{ property FontOrientation; }
property LogarithmBase;
property Max;
property Min;
property ScaleType;
property Text;
property TickAlign;
property TickColor;
property TickDesign;
property TickIndent;
property TickLength;
property TickLongValue;
property TickMiddleValue;
property TickShortValue;
property TickVisible;
property ValueDisplay;
property ValueFormat;
property ValueIndent;
property ValueShift;
property ValueVisible;
property OnPrepareValue;
end;
implementation
{ TCustomECScale }
constructor TCustomECScale.Create(AParent: TControl);
begin
inherited Create;
Parent:=AParent;
DTFormat:=DefaultFormatSettings;
FCorrection:=0;
FDateTimeFormat:='hh:nn:ss';
DTFormat.LongTimeFormat:=FDateTimeFormat;
FLogarithmBase:=10;
FMin:=0;
FMax:=100;
FText:=TStringList.Create;
FTickAlign:=cDefTickAlign;
FTickColor:=clDefault;
FTickIndent:=cDefTickIndent;
FTickLength:=cDefTickLength;
FTickShortValue:=1;
FTickMiddleValue:=5;
FTickLongValue:=10;
FTickVisible:=cDefTickVisible;
FValueFormat:=cDefValFormat;
FValueIndent:=cDefValIndent;
FValueVisible:=cDefValVisible;
end;
destructor TCustomECScale.Destroy;
begin
FreeAndNil(FText);
inherited Destroy;
end;
procedure TCustomECScale.BeginUpdate;
begin
inc(UpdateCount);
end;
procedure TCustomECScale.CalcTickPosAndValues(ALength: Integer; AReversed: Boolean);
begin
LengthPx:=ALength;
Reversed:=AReversed;
SetLength(TickArray, 0);
if (TickVisible>etvNone) or (ValueVisible>evvNone) then
case ScaleType of
estLinear: CalcTicksValuesLinear(ALength, AReversed);
estLogarithmic: CalcTicksValuesLogarithmic(ALength, AReversed);
end;
end;
procedure TCustomECScale.CalcTicksValuesLinear(ALength: Integer; AReversed: Boolean);
var aDistPx, aShift, pxlsPerUnit: Double;
AMinL, AMaxL, i, j, aPos, myPos, lLim, hLim, tMin, tMax: Integer;
bAddedTick, bTickInserted: Boolean;
function GetValueMax(ATickValue: Double): Integer;
var aCorrection, aFrac, aHighest: Double;
begin
aHighest:=Max/ATickValue;
aFrac:=frac(aHighest);
aCorrection:=Correction/ATickValue;
Result:=trunc(aHighest);
if aFrac>(1-aCorrection) then inc(Result)
else if aFrac<-aCorrection then dec(Result);
end;
function GetValueMin(ATickValue: Double): Integer;
var aCorrection, aFrac, aLowest: Double;
begin
aLowest:=Min/ATickValue;
aFrac:=frac(aLowest);
aCorrection:=Correction/ATickValue;
Result:=trunc(aLowest);
if aFrac>aCorrection then
begin
inc(Result);
aShift:=1-abs(aFrac);
end else
begin
if aFrac<(-1+aCorrection) then
begin
dec(Result);
aShift:=0;
end else
aShift:=abs(aFrac);
end;
end;
begin
pxlsPerUnit:=(ALength-0.99)/(Max-Min);
aDistPx:=TickShortValue*pxlsPerUnit; { distance between Short Ticks }
if (TickVisible>etvLongOnly) and (aDistPx>0.5) then
begin { Calc. Small Ticks }
tMin:=GetValueMin(TickShortValue);
tMax:=GetValueMax(TickShortValue);
SetLength(TickArray, tMax-tMin+1);
for i:=0 to tMax-tMin do
begin
TickArray[i].Pos:=round((aShift+i)*aDistPx);
TickArray[i].Size:=etsShort;
end;
end;
bAddedTick:=False;
aDistPx:=TickMiddleValue*pxlsPerUnit; { distance between Middle Ticks }
if (TickVisible=etvAll) and (aDistPx>0.5) then
begin { Calc. Middle Ticks }
lLim:=0;
hLim:=trunc(TickMiddleValue/TickShortValue);
tMin:=GetValueMin(TickMiddleValue);
tMax:=GetValueMax(TickMiddleValue);
for i:=0 to tMax-tMin do
begin
aPos:=trunc((aShift+i)*aDistPx);
bTickInserted:=False;
for j:=lLim to high(TickArray) do
begin
if (TickArray[j].Pos=aPos) or (TickArray[j].Pos=(aPos+1)) then
begin
TickArray[j].Size:=etsMiddle;
bTickInserted:=True;
lLim:=j+hLim;
break;
end;
end;
if not bTickInserted then
begin
SetLength(TickArray, high(TickArray)+2);
TickArray[high(TickArray)].Pos:=aPos;
TickArray[high(TickArray)].Size:=etsMiddle;
bAddedTick:=True;
end;
end;
end;
{ Calc. Long Ticks }
aDistPx:=TickLongValue*pxlsPerUnit; { distance between Long Ticks }
if aDistPx>0.5 then
begin
AMinL:=high(Integer);
AMaxL:=low(Integer);
lLim:=0;
hLim:=trunc(TickLongValue/TickShortValue);
tMin:=GetValueMin(TickLongValue);
tMax:=GetValueMax(TickLongValue);
for i:=0 to tMax-tMin do
begin
aPos:=trunc((aShift+i)*aDistPx);
bTickInserted:=False;
for j:=lLim to high(TickArray) do
begin
if (TickArray[j].Pos=aPos) or (TickArray[j].Pos=(aPos+1)) then
begin
myPos:=TickArray[j].Pos;
TickArray[j].Size:=etsLong;
bTickInserted:=True;
if not bAddedTick then lLim:=j+hLim; { ensure to search whole array when at least one tick was added }
break;
end;
end;
if not bTickInserted then
begin
SetLength(TickArray, high(TickArray)+2);
TickArray[high(TickArray)].Pos:=aPos;
TickArray[high(TickArray)].Size:=etsLong;
myPos:=aPos;
end;
if myPos<AMinL then AMinL:=myPos;
if myPos>AMaxL then AMaxL:=myPos;
end;
end else
begin
tMin:=0;
tMax:=0;
end;
if AReversed then
begin
MaxL:=LengthPx-AMinL;
MinL:=LengthPx-AMaxL;
end else
begin
MaxL:=AMaxL;
MinL:=AMinL;
end;
if ValueVisible>evvNone then
begin
SetLength(ValueArray, tMax-tMin+1);
for i:=0 to tMax-tMin do
ValueArray[i]:=TickLongValue*(i+tMin);
end;
end;
procedure TCustomECScale.CalcTicksValuesLogarithmic(ALength: Integer; AReversed: Boolean);
var aHelp, aMin, aMax, fracMin, pxlsPerUnit: Double;
aMinHasFrac: SmallInt;
aHiTick, aMiddlePos, i, j, k, aLogBaseMinus1, lowTicks, hiTicks, truncMin, truncMax: Integer;
bMiddleMark: Boolean;
const caTick: array[False..True] of TTickSize = (etsShort, etsMiddle);
begin
aLogBaseMinus1:=trunc(LogarithmBase)-1;
aMin:=Min;
aMax:=Max;
if aMin>0
then aMin:=logn(LogarithmBase, aMin)
else aMin:=0;
truncMin:=trunc(AMin);
fracMin:=frac(AMin);
if fracMin<0.0 then dec(truncMin);
if aMax>0
then aMax:=logn(LogarithmBase, aMax)
else aMax:=0;
truncMax:=trunc(AMax);
if frac(aMax)<0.0 then dec(truncMax);
if aMin<aMax then
begin
pxlsPerUnit:=(ALength-0.99)/(aMax-aMin);
if abs(fracMin)>0.0
then aMinHasFrac:=1
else aMinHasFrac:=0;
if TickVisible>etvLongOnly then
begin { All Ticks }
hiTicks:=trunc(Max/power(LogarithmBase, truncMax));
aHelp:=Min/power(LogarithmBase, truncMin);
lowTicks:=trunc(LogarithmBase)-trunc(aHelp);
if frac(aHelp)>0.0 then dec(lowTicks);
if lowTicks>=aLogBaseMinus1 then lowTicks:=0;
j:=truncMax-truncMin;
if (lowTicks>0) or (frac(aHelp)>0.0) then dec(j);
if j>=0 then
begin
SetLength(TickArray, aLogBaseMinus1*j+lowTicks+hiTicks);
aHiTick:=aLogBaseMinus1;
end else
begin
if frac(AHelp)=0.0 then j:=1 else j:=0;
SetLength(TickArray, hiTicks+j-trunc(aHelp));
aHiTick:=hiTicks;
end;
SetLength(ValueArray, truncMax-truncMin+1-aMinHasFrac);
aHelp:=fracMin;
if aHelp<0 then aHelp:=1+aHelp;
bMiddleMark:=(TickVisible=etvAll) and (frac(LogarithmBase)=0.0) and (trunc(LogarithmBase) mod 2 =0);
aMiddlePos:=trunc(LogarithmBase) div 2;
if aMinHasFrac=1 then
begin { Small ticks in front of the very first long tick }
lowTicks:=trunc(LogarithmBase)-lowTicks;
for j:=lowTicks to aHiTick do
begin
k:=j-lowTicks;
TickArray[k].Pos:=round((-aHelp+logn(LogarithmBase, j))*pxlsPerUnit);
TickArray[k].Size:=caTick[bMiddleMark and (j=aMiddlePos)];
end;
end;
i:=0;
if lowTicks>0 then lowTicks:=trunc(LogarithmBase)-lowTicks;
while i<(truncMax-truncMin-aMinHasFrac) do
begin
TickArray[lowTicks+i*aLogBaseMinus1].Pos:=round((abs(aMinHasFrac-aHelp)+i)*pxlsPerUnit);
TickArray[lowTicks+i*aLogBaseMinus1].Size:=etsLong;
for j:=2 to aLogBaseMinus1 do { in the middle }
begin
k:=lowTicks+i*aLogBaseMinus1+j-1;
TickArray[k].Pos:=TickArray[lowTicks+i*aLogBaseMinus1].Pos+round(logn(LogarithmBase, j)*pxlsPerUnit);
TickArray[k].Size:=caTick[bMiddleMark and (j=aMiddlePos)];
end;
inc(i);
end;
if length(ValueArray)>0 then
begin
TickArray[lowTicks+i*aLogBaseMinus1].Pos:=round((abs(aMinHasFrac-aHelp)+i)*pxlsPerUnit);
TickArray[lowTicks+i*aLogBaseMinus1].Size:=etsLong;
for j:=2 to hiTicks do { in the end }
begin
k:=lowTicks+i*aLogBaseMinus1+j-1;
TickArray[k].Pos:=TickArray[lowTicks+i*aLogBaseMinus1].Pos+round(logn(LogarithmBase, j)*pxlsPerUnit);
TickArray[k].Size:=caTick[bMiddleMark and (j=aMiddlePos)];
end;
MinL:=TickArray[lowTicks].Pos;
MaxL:=TickArray[lowTicks+i*aLogBaseMinus1].Pos;
end;
end else
begin { LongTicks only }
aHelp:=Min/power(LogarithmBase, truncMin);
j:=truncMax-truncMin;
SetLength(ValueArray, j+1-aMinHasFrac);
if j>=0
then SetLength(TickArray, j+1-aMinHasFrac)
else
begin
if frac(AHelp)=0.0
then j:=1
else j:=0;
SetLength(TickArray, j-trunc(aHelp));
end;
aHelp:=fracMin;
if aHelp<0 then aHelp:=1+aHelp;
i:=0;
k:=truncMax-truncMin-aMinHasFrac;
while i<=k do
begin
TickArray[i].Pos:=round((abs(aMinHasFrac-aHelp)+i)*pxlsPerUnit);
TickArray[i].Size:=etsLong;
inc(i);
end;
MinL:=TickArray[0].Pos;
dec(i);
MaxL:=TickArray[i].Pos;
end;
if not AReversed then
for i:=0 to high(ValueArray) do
ValueArray[i]:=power(LogarithmBase, truncMin+i+aMinHasFrac)
else
begin
MaxL:=LengthPx-MaxL;
MinL:=LengthPx-MinL;
j:=high(ValueArray);
for i:=0 to j do
ValueArray[j-i]:=power(LogarithmBase, truncMin+i+aMinHasFrac);
end;
end;
end;
procedure TCustomECScale.Draw(ACanvas: TCanvas; ATicks, AValues: Boolean;
AScalePos: TObjectPos; ATickStart: TPoint; AExtraValues: array of Double);
begin
Draw(ACanvas, ATicks, AValues, AScalePos, clDefault, clDefault, ATickStart, AExtraValues);
end;
procedure TCustomECScale.Draw(ACanvas: TCanvas; ATicks, AValues: Boolean; AScalePos: TObjectPos;
AColor3DDark, AColor3DLight: TColor; ATickStart: TPoint; AExtraValues: array of Double);
var aTLStart, aTLEnd, aTMStart, aTMEnd, aTSStart, aTSEnd: Integer; AStart: Integer;
{$IFDEF DBGSCALE} td: TDateTime; {$ENDIF}
procedure CalcTickPoints(AEdge, ARight: Integer);
var aTLSize, aTMSize, aTSSize: Integer;
begin { Calculates Start end End points of Ticks }
aTLSize:=TickLength;
if FValueDisplay<=evdBottomRight then
begin { evdNormal, evdTopLeft, evdBottomRight }
aTMSize:=round(aTLSize*0.7);
aTSSize:=round(aTLSize*0.4);
end else
begin { evdTopLeftInside, evdBttmRightInside, evdCompactTopLeft, evdCompactBttmRight }
aTMSize:=round(aTLSize*0.5);
aTSSize:=round(aTLSize*0.3);
end;
aTLEnd:=AEdge+ARight*TickIndent;
aTLStart:=aTLEnd+ARight*aTLSize;
case TickAlign of
etaOuter:
begin
aTMStart:=aTLStart;
aTSStart:=aTLStart;
aTMEnd:=aTMStart-ARight*aTMSize;
aTSEnd:=aTSStart-ARight*aTSSize;
end;
etaInner:
begin
aTMEnd:=aTLEnd;
aTSEnd:=aTLEnd;
aTMStart:=aTMEnd+ARight*aTMSize;
aTSStart:=aTSEnd+ARight*aTSSize;
end;
etaCenter:
begin
aTMEnd:=aTLEnd+ARight*(aTLSize-aTMSize) div 2;
aTMStart:=aTMEnd+ARight*aTMSize;
aTSEnd:=aTLEnd+ARight*(aTLSize-aTSSize) div 2;
aTSStart:=aTSEnd+ARight*aTSSize;
end;
end;
end;
procedure DrawTicks;
var aTickColor: TColor;
procedure nDrawTicks(AOffSet: Integer);
var aPos, i: Integer;
begin
if not Reversed then AOffSet:=AStart+AOffSet
else AOffSet:=LengthPx+AOffSet+AStart-1;
if AScalePos in [eopTop, eopBottom] then
begin
for i:=0 to high(TickArray) do
begin
if not Reversed then aPos:=AOffSet+TickArray[i].Pos
else aPos:=AOffSet-TickArray[i].Pos;;
case TickArray[i].Size of
etsShort: ACanvas.Line(aPos, aTSStart, aPos, aTSEnd);
etsMiddle: ACanvas.Line(aPos, aTMStart, aPos, aTMEnd);
etsLong: ACanvas.Line(aPos, aTLStart, aPos, aTLEnd);
end;
end;
end else
begin
for i:=0 to high(TickArray) do
begin
if not Reversed then aPos:=AOffSet+TickArray[i].Pos
else aPos:=AOffSet-TickArray[i].Pos;
case TickArray[i].Size of
etsShort: ACanvas.Line(aTSStart, aPos, aTSEnd, aPos);
etsMiddle: ACanvas.Line(aTMStart, aPos, aTMEnd, aPos);
etsLong: ACanvas.Line(aTLStart, aPos, aTLEnd, aPos);
end;
end;
end;
end;
begin
with ACanvas do
begin
Pen.Style:=psSolid;
aTickColor:=GetColorResolvingDefault(TickColor, clBtnText);
if not Parent.IsEnabled then
if TickDesign<etd3DLowered then
begin
aTickColor:=GetMergedColor(aTickColor, Pixels[Width div 2, Height div 2], 0.45);
end else
begin
AColor3DDark:=GetMergedColor(AColor3DDark, Pixels[Width div 2, Height div 2], 0.67);
AColor3DLight:=GetMergedColor(AColor3DLight, Pixels[Width div 2, Height div 2], 0.67);
end;
case TickDesign of
etdSimple:
begin
Pen.Color:=aTickColor;
nDrawTicks(0);
end;
etdThick:
begin
Pen.Color:=aTickColor;
nDrawTicks(-1);
nDrawTicks(0);
end;
etd3DLowered:
begin
Pen.Color:=AColor3DLight;
nDrawTicks(1);
Pen.Color:=AColor3DDark;
nDrawTicks(0);
end;
etd3DRaised:
begin
Pen.Color:=AColor3DLight;
nDrawTicks(-1);
Pen.Color:=AColor3DDark;
nDrawTicks(0);
end;
end;
end;
end;
procedure DrawValues; { ARight: -1..TopLeft, 1.. BottomRight }
var i, lV, valueTL, valueBR: Integer;
aDetails: TThemedElementDetails;
aFontColor: TColor;
cosPi1800, sinPi1800, lHelp: Double;
strVal: string;
aRect: TRect;
aValueVisible: TValuesVisibility;
function GetTopLeftPos: Integer;
begin
with ACanvas do
if AScalePos in [eopLeft, eopRight]
then Result:=round(cosPi1800*TextWidth(strVal)+sinPi1800*TextHeight(strVal))
else Result:=round(sinPi1800*TextWidth(strVal)+cosPi1800*TextHeight(strVal));
end;
function GetValuePos: Integer;
var c: Char;
h: SmallInt;
begin
with ACanvas do
if AScalePos in [eopLeft, eopRight] then
case ValueDisplay of { Vertical }
evdNormal: Result:=-round(cosPi1800*(TextHeight(strVal) div 2)+sinPi1800*(TextWidth(strVal) div 2));
evdTopLeft, evdTopLeftInside: Result:=-round(cosPi1800*TextHeight(strVal)+sinPi1800*TextWidth(strVal));
evdCompactTopLeft:
begin
Result:=0;
h:=TextHeight('9');
for c in strVal do
if not (c=FormatSettings.DecimalSeparator)
then dec(Result, h)
else dec(Result, h div 2);
inc(Result, length(strVal));
end;
otherwise Result:=1;
end else
case ValueDisplay of { Horizontal }
evdNormal: Result:=-round(sinPi1800*(TextHeight(strVal) div 2)+cosPi1800*(TextWidth(strVal) div 2));
evdTopLeft, evdTopLeftInside, evdCompactTopLeft: Result:=-round(sinPi1800*TextHeight(strVal)+cosPi1800*TextWidth(strVal));
otherwise Result:=1;
end;
end;
procedure DrawValue;
var aFlags: Cardinal;
aValueShift: Integer;
procedure nDrawValue(AValue: Integer);
begin
if AScalePos in [eopLeft, eopRight] then aRect.Left:=AValue else aRect.Top:=AValue;
ThemeServices.DrawText(ACanvas, aDetails, strVal, aRect, aFlags, 0);
end;
procedure nDrawValueSplitted(AValue: Integer); { for evdCompactTopLeft & evdCompactBttmRight }
var c: Char;
h: Integer;
begin
aRect.Left:=AValue;
h:=ACanvas.TextHeight('9')-1;
for c in strVal do
begin
if c=FormatSettings.DecimalSeparator then dec(aRect.Top, h div 2);
ThemeServices.DrawText(ACanvas, aDetails, c, aRect, aFlags, 0);
inc(aRect.Top, h);
end;
end;
begin
if ValueDisplay in [evdTopLeft, evdTopLeftInside, evdCompactTopLeft]
then aValueShift:=-ValueShift
else aValueShift:=ValueShift;
aFlags:=DT_NOPREFIX or DT_SINGLELINE;
case AScalePos of
eopTop:
begin
aRect.Left:=lV+aValueShift;
nDrawValue(valueTL-GetTopLeftPos);
end;
eopRight:
begin
aRect.Top:=lV+aValueShift;
if ValueDisplay<evdCompactTopLeft
then nDrawValue(valueBR)
else nDrawValueSplitted(valueBR);
end;
eopBottom:
begin
aRect.Left:=lV+aValueShift;
nDrawValue(valueBR)
end;
eopLeft:
begin
aRect.Top:=lV+aValueShift;
if ValueDisplay<evdCompactTopLeft
then nDrawValue(valueTL-GetTopLeftPos)
else nDrawValueSplitted(valueTL);
end;
end;
end;
begin
aDetails:=ThemeServices.GetElementDetails(caThemedContent[caItemState[Parent.IsEnabled]]);
aRect.Right:=ACanvas.Width;
aRect.Bottom:=ACanvas.Height;
aValueVisible:=ValueVisible;
case AScalePos of
eopTop:
if ATicks then
case ValueDisplay of
evdNormal, evdTopLeft, evdBottomRight: valueTL:=aTLStart-ValueIndent+2;
otherwise
if TickAlign=etaInner
then valueTL:=aTLStart-ValueIndent+ACanvas.TextHeight('1,9')-1
else valueTL:=aTLStart-ValueIndent+2;
end else
valueTL:=ATickStart.Y-ValueIndent;
eopRight:
if ATicks then
case ValueDisplay of
evdNormal, evdTopLeft, evdBottomRight: valueBR:=aTLStart+ValueIndent+1;
otherwise
case TickAlign of
etaOuter: valueBR:=aTLStart+ValueIndent+1;
etaInner: valueBR:=aTLStart+ValueIndent-ACanvas.TextWidth('9')+1;
etaCenter: valueBR:=aTLStart+ValueIndent-ACanvas.TextWidth('9') div 2 +1;
end;
end else
valueBR:=ATickStart.X+ValueIndent;
eopBottom:
if ATicks then
case ValueDisplay of
evdNormal, evdTopLeft, evdBottomRight: valueBR:=aTLStart+ValueIndent;
otherwise
if TickAlign=etaInner
then valueBR:=aTLStart+ValueIndent-ACanvas.TextHeight('1,9')+3
else valueBR:=aTLStart+ValueIndent;
end else
valueBR:=ATickStart.Y+ValueIndent;
eopLeft:
if ATicks then
case ValueDisplay of
evdNormal, evdTopLeft, evdBottomRight: valueTL:=aTLStart-ValueIndent;
evdTopLeftInside, evdBttmRightInside:
case TickAlign of
etaOuter: valueTL:=aTLStart-ValueIndent;
etaInner: valueTL:=aTLStart-ValueIndent+ACanvas.TextWidth('9');
etaCenter: valueTL:=aTLStart-ValueIndent+ACanvas.TextWidth('9') div 2;
end;
otherwise
case TickAlign of
etaOuter: valueTL:=aTLStart-ValueIndent-ACanvas.TextWidth('9');
etaInner: valueTL:=aTLStart-ValueIndent;
etaCenter: valueTL:=aTLStart-ValueIndent-ACanvas.TextWidth('9') div 2;
end;
end else
valueTL:=ATickStart.X-ValueIndent;
end;
with ACanvas do
begin
Font.Orientation:=FontOrientation;
aFontColor:=GetColorResolvingDefault(Font.Color, clBtnText);
if TickDesign>=etd3DLowered then
Font.Color:=GetMergedColor(aFontColor, Pixels[Width div 2, height div 2], 0.75);
Brush.Style:=bsClear;
cosPi1800:=abs(cos(pi_1800*FFontOrientation));
sinPi1800:=abs(sin(pi_1800*FFontOrientation));
if aValueVisible>evvBounds then
begin
lHelp:=(MaxL-MinL)/high(ValueArray);
for i:=0 to high(ValueArray) do
begin
strVal:=GetStringValue(i);
if not Reversed
then lV:=MinL+round(i*lHelp)
else lV:=MaxL-round(i*lHelp);
lV:=AStart+lV+GetValuePos;
DrawValue;
end;
end;
if aValueVisible in [evvBounds, evvAll] then
begin
if (aValueVisible=evvBounds) or (ValueArray=nil) or (Min<>ValueArray[0]) then
begin
strVal:=GetStringMin;
lV:=AStart+GetValuePos;
if Reversed then lV:=lV+LengthPx;
DrawValue;
end;
if (aValueVisible=evvBounds) or (ValueArray=nil) or (Max<>ValueArray[high(ValueArray)]) then
begin
strVal:=GetStringMax;
lV:=AStart+GetValuePos;
if not Reversed then lV:=lV+LengthPx;
DrawValue;
end;
for i:=0 to high(AExtraValues) do
if (aValueVisible=evvBounds) and (AExtraValues[i]>Min) and (AExtraValues[i]<Max) then
begin
strVal:=GetStringValue(AExtraValues[i], FDigits);
lV:=round((AExtraValues[i]-Min)*LengthPx/(Max-Min));
if not Reversed
then lV:=AStart+lV+GetValuePos
else lV:=AStart+LengthPx-lV+GetValuePos;
DrawValue;
end;
end;
end;
end;
begin
{$IFDEF DBGSCALE} td:=Now; {$ENDIF}
case AScalePos of
eopTop:
begin
CalcTickPoints(ATickStart.Y, -1);
AStart:=ATickStart.X;
end;
eopRight:
begin
CalcTickPoints(ATickStart.X, 1);
AStart:=ATickStart.Y;
end;
eopBottom:
begin
CalcTickPoints(ATickStart.Y, 1);
AStart:=ATickStart.X;
end;
eopLeft:
begin
CalcTickPoints(ATickStart.X, -1);
AStart:=ATickStart.Y;
end;
end;
ATicks:=ATicks and (TickVisible>etvNone);
if ATicks then
begin
if TickDesign>=etd3DLowered then
begin
AColor3DDark:=GetColorResolvingDefault(AColor3DDark, clBtnShadow);
AColor3DLight:=GetColorResolvingDefault(AColor3DLight, clBtnHilight);
end;
DrawTicks;
end;
if AValues and (ValueVisible>evvNone) then DrawValues;
{$IFDEF DBGSCALE} DebugLn('Scale.Draw '+floattostr((now-td)*24*60*60*1000)); {$ENDIF}
end;
procedure TCustomECScale.EndUpdate;
begin
dec(UpdateCount);
if UpdateCount=0 then RecalcRedraw;
end;
function TCustomECScale.GetPreferredSize(ACanvas: TCanvas; AHorizontal: Boolean;
ATicks: Boolean = True; AValues: Boolean = True): Integer;
var bValues: Boolean;
begin
Result:=0;
if ATicks or AValues then
begin
if ATicks and (TickVisible>etvNone) then inc(Result,TickIndent+TickLength);
bValues:= (AValues and (ValueVisible>evvNone));
if AHorizontal then
begin { Horizontal }
if bValues then inc(Result, GetPreferredValuesHeight(ACanvas))
end else
begin { Vertical }
if bValues then inc(Result, GetPreferredValuesWidth(ACanvas))
end;
end;
end;
function TCustomECScale.GetPreferredValuesHeight(ACanvas: TCanvas): Integer;
begin
Result:=ValueIndent;
if ValueDisplay<=evdBottomRight
then inc(Result, Math.max(GetTextSize(ACanvas, GetStringMax, True),
GetTextSize(ACanvas, GetStringMin, True)))
else if TickAlign<>etaInner then inc(Result, ACanvas.TextHeight(',9')-2);
end;
function TCustomECScale.GetPreferredValuesWidth(ACanvas: TCanvas): Integer;
var aWidth: Integer;
begin
Result:=ValueIndent+2;
if ValueDisplay<=evdBttmRightInside
then aWidth:=(Math.max(GetTextSize(ACanvas, GetStringMin, False),
GetTextSize(ACanvas, GetStringMax, False))-2); { -2 is correction }
case ValueDisplay of
evdNormal, evdTopLeft, evdBottomRight: inc(Result, aWidth);
evdTopLeftInside, evdBttmRightInside:
begin
inc(Result, aWidth);
case TickAlign of
etaInner: dec(Result, ACanvas.TextWidth('9'));
etaCenter: dec(Result, ACanvas.TextWidth('9') div 2);
end;
end;
evdCompactTopLeft, evdCompactBttmRight:
begin
case TickAlign of
etaOuter: inc(Result, ACanvas.TextWidth('9'));
etaCenter: inc(Result, ACanvas.TextWidth('9') div 2);
end;
end;
end;
end;
function TCustomECScale.GetStringMax: string;
begin
Result:='';
if ValueFormat<>esvfText
then Result:=GetStringValue(Max, Digits)
else if FText.Count>0 then Result:=FText[FText.Count-1];
end;
function TCustomECScale.GetStringMin: string;
begin
Result:='';
if ValueFormat<>esvfText
then Result:=GetStringValue(Min, Digits)
else if FText.Count>0 then Result:=FText[0];
end;
function TCustomECScale.GetStringPosition(APosition: Double): string;
begin
Result:=GetStringPosition(APosition, Digits);
end;
function TCustomECScale.GetStringPosition(APosition: Double; ARound: SmallInt): string;
var i: Integer;
begin
if ValueFormat<>esvfText then
begin
if ScaleType=estLogarithmic
then APosition:=LinearToLogarithmic(APosition, Min, Max, LogarithmBase);
Result:=GetStringValue(APosition, ARound);
end else
begin
i:=round((APosition-Min)/TickLongValue);
if (i>=0) and (i<Text.Count)
then Result:=FText.Strings[i]
else Result:='';
end;
end;
function TCustomECScale.GetStringValue(AValue: Double; ARound: Integer): string;
begin
if assigned(FOnPrepareValue) then FOnPrepareValue(self, AValue);
case ValueFormat of
esvfAutoRound: if ARound<0
then Result:=floattostr(AValue)
else Result:=floattostrF(AValue, ffFixed, 1, ARound);
esvfFixedRound: Result:=floattostrF(AValue, ffFixed, 1, ARound);
esvfSmartRound:
begin
if abs(AValue)>=10 then dec(ARound, trunc(log10(abs(AValue))));
if (ARound<0) or (frac(AValue)=0) then ARound:=0;
Result:=floattostrF(AValue, ffFixed, 1, ARound);
end;
esvfExponential: Result:=floattostrF(AValue, ffExponent, ARound, ARound);
esvfHexadecimal: Result:=hexStr(round(AValue), ARound);
esvfMarkHexadec: Result:='$'+hexStr(round(AValue), ARound);
esvfOctal: Result:=octStr(round(AValue), ARound);
esvfBinary: Result:=binStr(round(AValue), ARound);
esvfDate: Result:=datetostr(AValue, DTFormat);
esvfTime: Result:=timetostr(AValue, DTFormat);
end;
end;
function TCustomECScale.GetStringValue(AIndex: Integer): string;
begin
Result:='';
if ValueFormat<>esvfText
then Result:=GetStringValue(ValueArray[AIndex], Digits)
else if (AIndex>=0) and (AIndex<FText.Count) then Result:=FText.Strings[AIndex];
end;
function TCustomECScale.GetTextSize(ACanvas: TCanvas; AStrVal: string; AHorizontal: Boolean): Integer;
var cosPi1800, sinPi1800, aHelp: Double;
begin
aHelp:=pi_1800*FontOrientation;
cosPi1800:=abs(cos(aHelp));
sinPi1800:=abs(sin(aHelp));
with ACanvas do
begin
if AHorizontal
then Result:=round(sinPi1800*TextWidth(AStrVal)+cosPi1800*(TextHeight(AStrVal)-2))
else Result:=round(cosPi1800*TextWidth(AStrVal)+sinPi1800*TextHeight(AStrVal));
end;
end;
procedure TCustomECScale.Redraw;
begin
if (UpdateCount=0) and assigned(OnRedraw) then OnRedraw;
end;
procedure TCustomECScale.RecalcRedraw;
begin
if (UpdateCount=0) and assigned(OnRecalcRedraw) then OnRecalcRedraw;
end;
{ TCustomECScale.Setters }
procedure TCustomECScale.SetCorrection(AValue: Double);
begin
if FCorrection = AValue then exit;
FCorrection := AValue;
if ((TickVisible>etvNone) or (ValueVisible>evvNone)) and (ScaleType=estLinear) then RecalcRedraw;
end;
procedure TCustomECScale.SetDateTimeFormat(const AValue: string);
begin
if FDateTimeFormat=AValue then exit;
FDateTimeFormat:=AValue;
DTFormat.LongDateFormat:=AValue;
DTFormat.LongTimeFormat:=AValue;
if (ValueVisible>evvNone) and (ValueFormat in [esvfDate, esvfTime]) then RecalcRedraw;
end;
procedure TCustomECScale.SetDigits(const AValue: SmallInt);
begin
if FDigits=AValue then exit;
FDigits:=AValue;
if ValueVisible>evvNone then RecalcRedraw;
end;
procedure TCustomECScale.SetFontOrientation(const AValue: Integer);
begin
if FFontOrientation=AValue then exit;
{FFontOrientation:=AValue;} //seems Qt/GTK2/Win32 works differently now
if ValueVisible>evvNone then RecalcRedraw;
end;
procedure TCustomECScale.SetLogarithmBase(const AValue: Double);
begin
if FLogarithmBase=AValue then exit;
FLogarithmBase:=AValue;
if ((TickVisible>etvNone) or (ValueVisible>evvNone))
and (ScaleType=estLogarithmic) then RecalcRedraw;
end;
procedure TCustomECScale.SetMax(const AValue: Double);
begin
if FMax=AValue then exit;
if (Min<AValue) or (csLoading in Parent.ComponentState) then
begin
FMax:=AValue;
RecalcRedraw;
end;
end;
procedure TCustomECScale.SetMin(const AValue: Double);
begin
if FMin=AValue then exit;
if (AValue<Max) or (csLoading in Parent.ComponentState) then
begin
FMin:=AValue;
RecalcRedraw;
end;
end;
procedure TCustomECScale.SetScaleType(const AValue: TScaleType);
begin
if FScaleType=AValue then exit;
FScaleType:=AValue;
if (TickVisible>etvNone) or (ValueVisible>evvNone) then RecalcRedraw;
end;
procedure TCustomECScale.SetText(const AValue: TStrings);
begin
if FText=AValue then exit;
FText.Assign(AValue);
end;
procedure TCustomECScale.SetTickAlign(const AValue: TTickAlign);
begin
if FTickAlign=AValue then exit;
FTickAlign:=AValue;
if TickVisible>etvNone then RecalcRedraw;
end;
procedure TCustomECScale.SetTickColor(const AValue: TColor);
begin
if FTickColor=AValue then exit;
FTickColor:=AValue;
if TickVisible>etvNone then Redraw;
end;
procedure TCustomECScale.SetTickDesign(const AValue: TTickDesign);
begin
if FTickDesign=AValue then exit;
FTickDesign:=AValue;
if TickVisible>etvNone then Redraw;
end;
procedure TCustomECScale.SetTickIndent(const AValue: SmallInt);
begin
if FTickIndent=AValue then exit;
FTickIndent:=AValue;
RecalcRedraw;
end;
procedure TCustomECScale.SetTickLength(const AValue: SmallInt);
begin
if FTickLength=AValue then exit;
FTickLength:=AValue;
if TickVisible>etvNone then RecalcRedraw;
end;
procedure TCustomECScale.SetTickLongValue(const AValue: Double);
begin
if FTickLongValue=AValue then exit;
FTickLongValue:=AValue;
if TickVisible>etvNone then RecalcRedraw;
end;
procedure TCustomECScale.SetTickMiddleValue(const AValue: Double);
begin
if FTickMiddleValue=AValue then exit;
FTickMiddleValue:=AValue;
if TickVisible=etvAll then RecalcRedraw;
end;
procedure TCustomECScale.SetTickShortValue(const AValue: Double);
begin
if FTickShortValue=AValue then exit;
FTickShortValue:=AValue;
if TickVisible>=etvLongShort then RecalcRedraw;
end;
procedure TCustomECScale.SetTickVisible(const AValue: TTicksVisible);
begin
if FTickVisible=AValue then exit;
FTickVisible:=AValue;
RecalcRedraw;
end;
procedure TCustomECScale.SetValueDisplay(const AValue: TValueDisplay);
begin
if FValueDisplay=AValue then exit;
FValueDisplay:=AValue;
if ValueVisible>evvNone then RecalcRedraw;
end;
procedure TCustomECScale.SetValueFormat(const AValue: TScaleValueFormat);
begin
if FValueFormat=AValue then exit;
FValueFormat:=AValue;
if ValueVisible>evvNone
then RecalcRedraw
else Redraw;
end;
procedure TCustomECScale.SetValueIndent(const AValue: SmallInt);
begin
if FValueIndent=AValue then exit;
FValueIndent:=AValue;
if ValueVisible>evvNone then RecalcRedraw;
end;
procedure TCustomECScale.SetValueShift(const AValue: SmallInt);
begin
if FValueShift=AValue then exit;
FValueShift:=AValue;
if ValueVisible>evvNone then RecalcRedraw;
end;
procedure TCustomECScale.SetValueVisible(const AValue: TValuesVisibility);
begin
if FValueVisible=AValue then exit;
FValueVisible:=AValue;
RecalcRedraw;
end;
end.