mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-08-15 18:19:07 +02:00
TAChart: Use TIntervalChartSource.Params in interval calculations
git-svn-id: trunk@31851 -
This commit is contained in:
parent
a2891026c6
commit
70e64e04d8
@ -551,6 +551,8 @@ begin
|
|||||||
d.FMax := AMax;
|
d.FMax := AMax;
|
||||||
d.FFormat := Marks.Format;
|
d.FFormat := Marks.Format;
|
||||||
d.FUseY := IsVertical;
|
d.FUseY := IsVertical;
|
||||||
|
d.FAxisToGraph := @GetTransform.AxisToGraph;
|
||||||
|
d.FGraphToImage := @FHelper.GraphToImage;
|
||||||
SetLength(FMarkValues, 0);
|
SetLength(FMarkValues, 0);
|
||||||
vis := TChartAxisList(Collection).OnVisitSources;
|
vis := TChartAxisList(Collection).OnVisitSources;
|
||||||
if Marks.AtDataOnly and Assigned(vis) then
|
if Marks.AtDataOnly and Assigned(vis) then
|
||||||
|
@ -24,14 +24,15 @@ interface
|
|||||||
uses
|
uses
|
||||||
Classes, Types, TAChartUtils;
|
Classes, Types, TAChartUtils;
|
||||||
|
|
||||||
|
|
||||||
const
|
|
||||||
DEF_INTERVAL_STEPS = '0.2|0.5|1.0';
|
|
||||||
|
|
||||||
type
|
type
|
||||||
TAxisIntervalParamOption = (
|
TAxisIntervalParamOption = (
|
||||||
aipUseCount, aipUseMaxLength, aipUseMinLength, aipUseNiceSteps);
|
aipUseCount, aipUseMaxLength, aipUseMinLength, aipUseNiceSteps);
|
||||||
|
|
||||||
|
const
|
||||||
|
DEF_INTERVAL_STEPS = '0.2|0.5|1.0';
|
||||||
|
DEF_INTERVAL_OPTIONS = [aipUseMaxLength, aipUseMinLength, aipUseNiceSteps];
|
||||||
|
|
||||||
|
type
|
||||||
TAxisIntervalParamOptions = set of TAxisIntervalParamOption;
|
TAxisIntervalParamOptions = set of TAxisIntervalParamOption;
|
||||||
|
|
||||||
TChartAxisIntervalParams = class(TPersistent)
|
TChartAxisIntervalParams = class(TPersistent)
|
||||||
@ -59,12 +60,12 @@ type
|
|||||||
property StepValues: TDoubleDynArray read FStepValues;
|
property StepValues: TDoubleDynArray read FStepValues;
|
||||||
published
|
published
|
||||||
property Count: Integer read FCount write SetCount default 5;
|
property Count: Integer read FCount write SetCount default 5;
|
||||||
property MaxLength: Integer read FMaxLength write SetMaxLength default 100;
|
property MaxLength: Integer read FMaxLength write SetMaxLength default 50;
|
||||||
property MinLength: Integer read FMinLength write SetMinLength default 5;
|
property MinLength: Integer read FMinLength write SetMinLength default 10;
|
||||||
property NiceSteps: String
|
property NiceSteps: String
|
||||||
read FNiceSteps write SetNiceSteps stored NiceStepsIsStored;
|
read FNiceSteps write SetNiceSteps stored NiceStepsIsStored;
|
||||||
property Options: TAxisIntervalParamOptions
|
property Options: TAxisIntervalParamOptions
|
||||||
read FOptions write SetOptions default [];
|
read FOptions write SetOptions default DEF_INTERVAL_OPTIONS;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
type
|
type
|
||||||
@ -90,10 +91,14 @@ type
|
|||||||
end;
|
end;
|
||||||
PChartDataItem = ^TChartDataItem;
|
PChartDataItem = ^TChartDataItem;
|
||||||
|
|
||||||
|
TGraphToImageFunc = function (AX: Double): Integer of object;
|
||||||
|
|
||||||
TValuesInRangeParams = record
|
TValuesInRangeParams = record
|
||||||
FFormat: String;
|
FFormat: String;
|
||||||
FMin, FMax: Double;
|
FMin, FMax: Double;
|
||||||
FUseY: Boolean;
|
FUseY: Boolean;
|
||||||
|
FAxisToGraph: TTransformFunc;
|
||||||
|
FGraphToImage: TGraphToImageFunc;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
{ TCustomChartSource }
|
{ TCustomChartSource }
|
||||||
|
@ -31,9 +31,7 @@ type
|
|||||||
strict private
|
strict private
|
||||||
FParams: TChartAxisIntervalParams;
|
FParams: TChartAxisIntervalParams;
|
||||||
procedure CalculateIntervals(
|
procedure CalculateIntervals(
|
||||||
AMin, AMax: Double; AxisScale: TAxisScale; out AStart, AStep: Double);
|
AParams: TValuesInRangeParams; out ABestStart, ABestStep: Double);
|
||||||
function GetIntervals(
|
|
||||||
AMin, AMax: Double; AInverted: Boolean): TChartValueTextArray;
|
|
||||||
procedure SetParams(AValue: TChartAxisIntervalParams);
|
procedure SetParams(AValue: TChartAxisIntervalParams);
|
||||||
protected
|
protected
|
||||||
function GetCount: Integer; override;
|
function GetCount: Integer; override;
|
||||||
@ -111,71 +109,89 @@ end;
|
|||||||
{ TIntervalChartSource }
|
{ TIntervalChartSource }
|
||||||
|
|
||||||
procedure TIntervalChartSource.CalculateIntervals(
|
procedure TIntervalChartSource.CalculateIntervals(
|
||||||
AMin, AMax: Double; AxisScale: TAxisScale; out AStart, AStep: Double);
|
AParams: TValuesInRangeParams; out ABestStart, ABestStep: Double);
|
||||||
var
|
|
||||||
ext, extentTmp, stepCount, scale, maxStepCount, m, step: Double;
|
|
||||||
const
|
|
||||||
BASE = 10;
|
|
||||||
begin
|
|
||||||
ext := AMax - AMin;
|
|
||||||
AStep := 1;
|
|
||||||
AStart := AMin;
|
|
||||||
if ext <= 0 then exit;
|
|
||||||
|
|
||||||
maxStepCount := 0;
|
function A2I(AX: Double): Integer; inline;
|
||||||
scale := 1.0;
|
begin
|
||||||
for step in Params.StepValues do begin
|
Result := AParams.FGraphToImage(AParams.FAxisToGraph(AX));
|
||||||
extentTmp := ext / step;
|
|
||||||
m := IntPower(BASE, Round(logn(BASE, extentTmp)));
|
|
||||||
while extentTmp * m > BASE do
|
|
||||||
m /= BASE;
|
|
||||||
while extentTmp * m <= 1 do
|
|
||||||
m *= BASE;
|
|
||||||
stepCount := extentTmp * m;
|
|
||||||
if stepCount > maxStepCount then begin
|
|
||||||
maxStepCount := stepCount;
|
|
||||||
scale := m;
|
|
||||||
AStep := step / m;
|
|
||||||
end;
|
end;
|
||||||
end;
|
|
||||||
case AxisScale of
|
procedure CalcMinMaxCount(out AMinCount, AMaxCount: Integer);
|
||||||
asIncreasing: begin
|
var
|
||||||
// If 0 is in the interval, set it as a mark.
|
imageWidth, d: Integer;
|
||||||
if InRange(0, AMin, AMax) then
|
begin
|
||||||
AStart := 0
|
// If the axis transformation is non-linear, steps may not be equidistant.
|
||||||
|
// However, both minimax and maximin will be achieved on equal steps.
|
||||||
|
imageWidth := Abs(A2I(AParams.FMax) - A2I(AParams.FMin));
|
||||||
|
d := IfThen(aipUseMinLength in Params.Options, Max(Params.MinLength, 2), 2);
|
||||||
|
AMaxCount := Max(imageWidth div d, 2);
|
||||||
|
if aipUseMaxLength in Params.Options then
|
||||||
|
AMinCount := Max((imageWidth + 1) div Max(Params.MaxLength, 2), 2)
|
||||||
else
|
else
|
||||||
AStart := Round((AMin - AStep) * scale) / scale;
|
AMinCount := 2;
|
||||||
while AStart >= AMin do AStart -= AStep;
|
|
||||||
end;
|
end;
|
||||||
asDecreasing: begin
|
|
||||||
// If 0 is in the interval, set it as a mark.
|
function CountToStep(ACount: Integer): Double;
|
||||||
if InRange(0, AMin, AMax) then
|
begin
|
||||||
AStart := 0
|
Result := (AParams.FMax - AParams.FMin) / ACount;
|
||||||
|
Result := Power(10, Floor(Log10(Result)));
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TryStep(AStep: Double; var ABestCount: Integer);
|
||||||
|
var
|
||||||
|
m, start: Double;
|
||||||
|
mi, prev, cnt: Integer;
|
||||||
|
begin
|
||||||
|
start := Floor(AParams.FMin / AStep) * AStep;
|
||||||
|
m := start;
|
||||||
|
prev := A2I(m);
|
||||||
|
cnt := 0;
|
||||||
|
while m <= AParams.FMax do begin
|
||||||
|
mi := A2I(m + AStep);
|
||||||
|
prev := Abs(prev - mi);
|
||||||
|
if
|
||||||
|
(aipUseMinLength in Params.Options) and (prev < Params.MinLength) or
|
||||||
|
(aipUseMaxLength in Params.Options) and (prev > Params.MaxLength)
|
||||||
|
then
|
||||||
|
exit;
|
||||||
|
m += AStep;
|
||||||
|
prev := mi;
|
||||||
|
cnt += 1;
|
||||||
|
end;
|
||||||
|
if
|
||||||
|
not (aipUseCount in Params.Options) or
|
||||||
|
(Abs(cnt - Params.Count) < Abs(ABestCount - Params.Count))
|
||||||
|
then begin
|
||||||
|
ABestStart := start - AStep;
|
||||||
|
ABestStep := AStep;
|
||||||
|
ABestCount := cnt;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
var
|
||||||
|
minCount, maxCount, cnt: Integer;
|
||||||
|
s, sv: Double;
|
||||||
|
begin
|
||||||
|
CalcMinMaxCount(minCount, maxCount);
|
||||||
|
cnt := MaxInt;
|
||||||
|
if aipUseNiceSteps in Params.Options then begin
|
||||||
|
s := CountToStep(minCount) * 10;
|
||||||
|
while s >= CountToStep(maxCount) do begin
|
||||||
|
for sv in Params.StepValues do
|
||||||
|
TryStep(s * sv, cnt);
|
||||||
|
// We are not required to pick best count, so any one will do.
|
||||||
|
if not (aipUseCount in Params.Options) and (cnt < MaxInt) then break;
|
||||||
|
s *= 0.1;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
if cnt < MaxInt then exit;
|
||||||
|
// Either nice steps were not required, or we failed to find one.
|
||||||
|
if aipUseCount in Params.Options then
|
||||||
|
cnt := EnsureRange(Params.Count, minCount, maxCount)
|
||||||
else
|
else
|
||||||
AStart := Round((AMax + AStep) * scale) / scale;
|
cnt := minCount;
|
||||||
while AStart <= AMax do AStart += AStep;
|
ABestStep := (AParams.FMax - AParams.FMin) / cnt;
|
||||||
end;
|
ABestStart := AParams.FMin - ABestStep;
|
||||||
asLogIncreasing: begin
|
|
||||||
// FIXME: asLogIncreasing is still not implemented.
|
|
||||||
// The following is the same code for asIncreasing;
|
|
||||||
// If 0 is in the interval, set it as a mark.
|
|
||||||
if InRange(0, AMin, AMax) then
|
|
||||||
AStart := 0
|
|
||||||
else
|
|
||||||
AStart := Round((AMin - AStep) * scale) / scale;
|
|
||||||
while AStart > AMin do AStart -= AStep;
|
|
||||||
end;
|
|
||||||
asLogDecreasing: begin
|
|
||||||
// FIXME: asLogDecreasing is still not implemented.
|
|
||||||
// The following is the same code for asIncreasing;
|
|
||||||
// If 0 is in the interval, set it as a mark.
|
|
||||||
if InRange(0, AMin, AMax) then
|
|
||||||
AStart := 0
|
|
||||||
else
|
|
||||||
AStart := Round((AMax + AStep) * scale) / scale;
|
|
||||||
while AStart < AMax do AStart += AStep;
|
|
||||||
end;
|
|
||||||
end; {case AxisScale}
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
constructor TIntervalChartSource.Create(AOwner: TComponent);
|
constructor TIntervalChartSource.Create(AOwner: TComponent);
|
||||||
@ -195,40 +211,6 @@ begin
|
|||||||
Result := 0;
|
Result := 0;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TIntervalChartSource.GetIntervals(
|
|
||||||
AMin, AMax: Double; AInverted: Boolean): TChartValueTextArray;
|
|
||||||
const
|
|
||||||
INV_TO_SCALE: array [Boolean] of TAxisScale = (asIncreasing, asDecreasing);
|
|
||||||
var
|
|
||||||
start, step, m: Double;
|
|
||||||
markCount, crossCount: Integer;
|
|
||||||
begin
|
|
||||||
CalculateIntervals(AMin, AMax, INV_TO_SCALE[AInverted], start, step);
|
|
||||||
if AInverted then
|
|
||||||
step := - step;
|
|
||||||
m := start;
|
|
||||||
crossCount := 0;
|
|
||||||
markCount := 1;
|
|
||||||
repeat
|
|
||||||
markCount += 1;
|
|
||||||
crossCount += Ord(InRange(m, AMin, AMax) <> InRange(m + step, AMin, AMax));
|
|
||||||
m += step;
|
|
||||||
until (crossCount = 2) or (m + step = m);
|
|
||||||
SetLength(Result, markCount);
|
|
||||||
m := start;
|
|
||||||
crossCount := 0;
|
|
||||||
markCount := 0;
|
|
||||||
repeat
|
|
||||||
if IsZero(m) then
|
|
||||||
m := 0;
|
|
||||||
Result[markCount].FValue := m;
|
|
||||||
markCount += 1;
|
|
||||||
crossCount += Ord(InRange(m, AMin, AMax) <> InRange(m + step, AMin, AMax));
|
|
||||||
m += step;
|
|
||||||
until (crossCount = 2) or (m + step = m);
|
|
||||||
Result[markCount].FValue := m;
|
|
||||||
end;
|
|
||||||
|
|
||||||
function TIntervalChartSource.GetItem(AIndex: Integer): PChartDataItem;
|
function TIntervalChartSource.GetItem(AIndex: Integer): PChartDataItem;
|
||||||
begin
|
begin
|
||||||
Unused(AIndex);
|
Unused(AIndex);
|
||||||
@ -249,11 +231,28 @@ end;
|
|||||||
|
|
||||||
procedure TIntervalChartSource.ValuesInRange(
|
procedure TIntervalChartSource.ValuesInRange(
|
||||||
AParams: TValuesInRangeParams; var AValues: TChartValueTextArray);
|
AParams: TValuesInRangeParams; var AValues: TChartValueTextArray);
|
||||||
|
const
|
||||||
|
// Arbitrary limit to prevent hangup/OOM in case of bug in CalculateIntervals.
|
||||||
|
MAX_COUNT = 10000;
|
||||||
var
|
var
|
||||||
|
start, step, m: Double;
|
||||||
i: Integer;
|
i: Integer;
|
||||||
begin
|
begin
|
||||||
if AParams.FMin > AParams.FMax then exit;
|
if AParams.FMin >= AParams.FMax then exit;
|
||||||
AValues := GetIntervals(AParams.FMin, AParams.FMax, false);
|
CalculateIntervals(AParams, start, step);
|
||||||
|
if step <= 0 then exit;
|
||||||
|
m := start;
|
||||||
|
SetLength(AValues, Trunc(Min((AParams.FMax - m) / step + 2, MAX_COUNT)));
|
||||||
|
for i := 0 to High(AValues) do begin
|
||||||
|
if IsZero(m) then
|
||||||
|
m := 0;
|
||||||
|
AValues[i].FValue := m;
|
||||||
|
if m > AParams.FMax then begin
|
||||||
|
SetLength(AValues, i + 1);
|
||||||
|
break;
|
||||||
|
end;
|
||||||
|
m += step;
|
||||||
|
end;
|
||||||
for i := 0 to High(AValues) do
|
for i := 0 to High(AValues) do
|
||||||
// Extra format arguments for compatibility with FormatItem.
|
// Extra format arguments for compatibility with FormatItem.
|
||||||
AValues[i].FText := Format(
|
AValues[i].FText := Format(
|
||||||
|
Loading…
Reference in New Issue
Block a user