Merged revision(s) 65456 #b303ecbdd8, 65463 #e03705d02d from trunk:

TAChart/TChartLiveView: Simplify operation of ExtentY modes.
........
TAChart/TChartLiveView: Fix operation when axes have a predefined Range. Modified patch by forum user Muso (https://forum.lazarus.freepascal.org/index.php/topic,55266.msg412075.html#msg412075)
........

git-svn-id: branches/fixes_2_2@65466 -
This commit is contained in:
maxim 2021-07-16 21:20:10 +00:00
parent 605bedcdee
commit d60a55ab43
2 changed files with 101 additions and 65 deletions

View File

@ -6,8 +6,8 @@
</descr>
</element><element name="TChartLiveView.Active"><short>Allows to turn the scrolling controlled by the component ON and OFF.</short>
</element><element name="TChartLiveView.ExtentY"><short>Mode determining how the vertical y axis is displayed in the scrolling viewport</short>
<descr><p><var>lveAuto</var>: adjusts the height to the visible data range automatically. This works also when the chart contains several y axes.
</p><p><var>lveFull</var>: freezes the height to the full extent of the chart.</p><p><var>lveLogical</var>: freezes the height to the logical extent of the chart.</p><p><var>lveMultiAxisRange</var> tries to use a predefined <var>Range</var> of the y axes in case of a multi-axis chart. Use the <var>Range.Min</var>, <var>Range.Max</var>, <var>Range.UseMin</var> and <var>Range.UseMax</var> properties to set up the axis range. Note that the range is extended automatically when a series attached to this axis contains out-of-range y values. When no range is specified (i.e., <var>axis.Range.UseMin=false</var> and <var>axis.Range.UseMax=false</var>) the behaviour is similar to <var>lveAuto</var>.</p>
<descr><p><var>lveAuto</var>: adjusts the height to the visible data range automatically. This works also when the chart contains several y axes. Ranges assigned to the axes (by setting <var>axis.Range.Min</var> and <var>axis.Range.UseMin</var>, and/or <var>axis.Range.Max</var> and <var>axis.Range.UseMax</var>) are respected (as long as data points are within this range).
</p><p><var>lveFull</var>: freezes the height to the full extent of the chart.</p><p><var>lveLogical</var>: freezes the height to the logical extent of the chart.</p>
</descr>
</element><element name="TChartLiveView.Create"><short>Constructor of the <var>TChartLiveView</var>
</short>

View File

@ -4,10 +4,10 @@
-------------------
TChartLiveView is a component optimized for displaying a long array of incoming
data in a viewport with only the most recent data while older data are flowing
to the left out of the viewport.
data in a viewport showing only the most recent data while older data are
flowing out of the viewport to the left.
It was created based on the following forum discussions:
It was created on the basis of the following forum discussions:
- https://forum.lazarus.freepascal.org/index.php/topic,15037.html
- https://forum.lazarus.freepascal.org/index.php/topic,50759.0.html
- https://forum.lazarus.freepascal.org/index.php/topic,55266.html
@ -29,7 +29,7 @@ uses
Classes, SysUtils, TAGraph, TAChartUtils;
type
TChartLiveViewExtentY = (lveAuto, lveFull, lveLogical, lveMultiAxisRange);
TChartLiveViewExtentY = (lveAuto, lveFull, lveLogical);
{ TChartLiveView }
@ -52,13 +52,13 @@ type
procedure SetChart(const AValue: TChart);
procedure SetExtentY(const AValue: TChartLiveViewExtentY);
procedure SetViewportSize(const AValue: Double);
procedure UpdateViewport;
protected
procedure RestoreAxisRanges;
procedure StoreAxisRanges;
procedure UpdateViewport; virtual;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure RestoreAxisRanges;
procedure StoreAxisRanges;
published
property Active: Boolean read FActive write SetActive default false;
property Chart: TChart read FChart write SetChart default nil;
@ -82,6 +82,7 @@ end;
destructor TChartLiveView.Destroy;
begin
RestoreAxisRanges;
FreeAndNil(FListener);
inherited;
end;
@ -95,6 +96,11 @@ begin
UpdateViewport;
end;
{ The ChartLiveView may change the Range properties of an axis. The original
values, before applying the live view, are restored here from internal
variables.
Be careful when calling this procedure in user code, it may disrupt the
operation of the live view. }
procedure TChartLiveView.RestoreAxisRanges;
var
i: Integer;
@ -151,8 +157,9 @@ end;
procedure TChartLiveview.SetExtentY(const AValue: TChartLiveViewExtentY);
begin
if FExtentY = AValue then exit;
if FExtentY = lveAuto then
RestoreAxisRanges;
FExtentY := AValue;
RestoreAxisRanges;
FullExtentChanged(nil);
end;
@ -163,6 +170,10 @@ begin
FullExtentChanged(nil);
end;
{ The ChartLiveView may change the Range properties of an axis. The original
values, before applying the live view, are stored here in internal variables.
Be careful when calling this procedure in user code, it may disrupt the
operation of the live view. }
procedure TChartLiveView.StoreAxisRanges;
var
i: Integer;
@ -187,11 +198,14 @@ var
fext, lext: TDoubleRect; // "full extent", "logical extent" variables
w: double;
i, j: Integer;
ymin, ymax: Double;
ygmin, ygmax: Double;
yminAx, ymaxAx, yminSer, ymaxSer: Double;
dy: Double;
ser: TChartSeries;
ax: TChartAxis;
axis: TChartAxis;
begin
if csDesigning in ComponentState then
exit;
if not FChart.ScalingValid then
exit;
@ -204,72 +218,94 @@ begin
w := lext.b.x - lext.a.x
else
w := FViewportSize;
// Move the extent to the right
lext.b.x := fext.b.x;
lext.a.x := lext.b.X - w;
lext.a.x := lext.b.x - w;
if lext.a.x < fext.a.x then begin
lext.a.x := fext.a.x;
lext.b.x := lext.a.x + w;
end;
case FExtentY of
lveAuto:
// The aim of lveAuto is to determine the y-axis range according to
// the ymin/ymax of the series connected to the axis
begin
lext.a.y := fext.a.y;
lext.b.y := fext.b.y;
for i := 0 to FChart.AxisList.Count-1 do
begin
axis := FChart.AxisList[i];
// Ignore x-axes
if (axis.Alignment in [calTop, calBottom]) then
Continue;
ymaxAx := -Infinity;
yminAx := Infinity;
// Step through all active non-rotated series attached to this axis
for j := 0 to FChart.SeriesCount-1 do
begin
if FChart.Series[j] is TChartSeries then
begin
ser := TChartSeries(FChart.Series[j]);
if (not ser.Active) or (ser.GetAxisY <> axis) or ser.IsRotated then
continue;
yminSer := Infinity;
ymaxSer := -Infinity;
ser.FindYRange(lext.a.x, lext.b.x, yminSer, ymaxSer);
yminAx := Min(yminAx, yminSer);
ymaxAx := Max(ymaxAx, ymaxSer);
end;
end;
// Only if an axis has no active non-rotated series, we have -infinity
if ymaxAx > -Infinity then
begin
if ymaxAx = yminAx then
begin
if yminAx = 0 then
begin
yminAx := -1;
ymaxAx := +1;
end
else
// Set the range to 10% around the value, take care of the sign!
begin
dy := abs(yminAx) * 0.1;
yminAx := yminAx - dy;
ymaxAx := ymaxAx + dy;
end;
end;
end
else
begin
yminAx := -1;
ymaxAx := +1;
end;
// Only if the user did not set its own range we set the axis range
// determined above.
if (not FAxisRanges[i].UseMin) then
begin
axis.Range.Min := yminAx;
axis.Range.UseMin := true; // we had stored the original UseMin in FAxisRanges
lext.a.y := axis.GetTransform.AxisToGraph(yminAx);
end;
if (not FAxisRanges[i].UseMax) then
begin
axis.Range.Max := ymaxAx;
axis.Range.UseMax := true; // we had stored the original UseMax in FAxisRanges
lext.b.y := axis.GetTransform.AxisToGraph(ymaxAx);
end;
end; // series loop
end; // axes loop
lveFull:
begin
lext.a.y := fext.a.y;
lext.b.y := fext.b.y;
end;
lveLogical:
;
lveAuto,
lveMultiAxisRange:
begin
lext.a.y := Infinity;
lext.b.y := -Infinity;
for i := 0 to FChart.AxisList.Count-1 do
begin
ax := FChart.AxisList[i];
// we only support scrolling along x, i.e. ax must be a y axis.
if not (ax.Alignment in [calLeft, calRight]) then
Continue;
ymin := Infinity;
ymax := -Infinity;
for j := 0 to FChart.SeriesCount-1 do
if FChart.Series[j] is TChartSeries then
begin
ser := TChartSeries(FChart.Series[j]);
if (not ser.Active) or (ser.GetAxisY <> ax) or ser.IsRotated then
continue;
ser.FindYRange(lext.a.x, lext.b.x, ymin, ymax);
end;
if (ymin = Infinity) then
begin
ymin := -1;
ymax := +1;
end else
if (ymin = ymax) then
begin
if ymin = 0 then
begin
ymin := -1;
ymax := +1;
end else
begin
ymin := abs(ymin) * 0.9;
ymax := abs(ymax) * 1.1;
end;
end;
ax.Range.Min := ymin;
ax.Range.Max := ymax;
if (FExtentY = lveMultiAxisRange) then
begin
if FAxisRanges[i].UseMin then ax.Range.Min := FAxisRanges[i].Min;
if FAxisRanges[i].UseMax then ax.Range.Max := FAxisRanges[i].Max;
end;
ax.Range.UseMin := true;
ax.Range.UseMax := true;
lext.a.y := Min(lext.a.y, ax.GetTransform.AxisToGraph(ymin));
lext.b.y := Max(lext.b.y, ax.GetTransform.AxisToGraph(ymax));
end; // for i
end;
end;
FChart.LogicalExtent := lext;
end;