diff --git a/.gitattributes b/.gitattributes index 14f58ea004..79bfcd81a7 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5404,6 +5404,7 @@ components/tachart/fpdoc/descr.lst svneol=native#text/plain components/tachart/fpdoc/input.lst svneol=native#text/plain components/tachart/fpdoc/tachartaxis.xml svneol=native#text/plain components/tachart/fpdoc/tachartaxisutils.xml svneol=native#text/xml +components/tachart/fpdoc/tachartliveview.xml svneol=native#text/xml components/tachart/fpdoc/tachartutils.xml svneol=native#text/plain components/tachart/fpdoc/tacustomseries.xml svneol=native#text/plain components/tachart/fpdoc/tacustomsource.xml svneol=native#text/xml @@ -5415,6 +5416,7 @@ components/tachart/fpdoc/talegend.xml svneol=native#text/plain components/tachart/fpdoc/tamath.xml svneol=native#text/xml components/tachart/fpdoc/taseries.xml svneol=native#text/plain components/tachart/fpdoc/tasources.xml svneol=native#text/plain +components/tachart/fpdoc/tatools.xml svneol=native#text/xml components/tachart/fpdoc/tatypes.xml svneol=native#text/plain components/tachart/fpvectorial/tachartfpvectorial.lpk svneol=native#text/plain components/tachart/fpvectorial/tachartfpvectorial.pas svneol=native#text/pascal diff --git a/components/tachart/demo/charteditor/ceaxisdlg.lfm b/components/tachart/demo/charteditor/ceaxisdlg.lfm index 59b7ccc6d4..645ab6aab0 100644 --- a/components/tachart/demo/charteditor/ceaxisdlg.lfm +++ b/components/tachart/demo/charteditor/ceaxisdlg.lfm @@ -11,7 +11,7 @@ object ChartAxisEditor: TChartAxisEditor OnCreate = FormCreate OnShow = FormShow Position = poScreenCenter - LCLVersion = '2.1.0.0' + LCLVersion = '2.3.0.0' object ButtonPanel: TButtonPanel Left = 6 Height = 34 diff --git a/components/tachart/demo/charteditor/ceaxisdlg.pas b/components/tachart/demo/charteditor/ceaxisdlg.pas index 50537b7070..18c5a3cf78 100644 --- a/components/tachart/demo/charteditor/ceaxisdlg.pas +++ b/components/tachart/demo/charteditor/ceaxisdlg.pas @@ -179,8 +179,17 @@ begin end; procedure TChartAxisEditor.OKButtonClick(Sender: TObject); +var + msg: String; + C: TWinControl; begin - FOKClicked := true; + if not FAxisFrame.Validate(msg, C) then + begin + C.SetFocus; + MessageDlg(msg, mtError, [mbOK], 0); + ModalResult := mrNone; + end else + FOKClicked := true; end; procedure TChartAxisEditor.Prepare(Axis: TChartAxis; diff --git a/components/tachart/demo/charteditor/ceaxisframe.lfm b/components/tachart/demo/charteditor/ceaxisframe.lfm index 45d613caa0..629ff4b53c 100644 --- a/components/tachart/demo/charteditor/ceaxisframe.lfm +++ b/components/tachart/demo/charteditor/ceaxisframe.lfm @@ -40,23 +40,24 @@ object ChartAxisFrame: TChartAxisFrame Height = 446 Top = 25 Width = 646 - ActivePage = pgLabels + ActivePage = pgGrid Align = alClient - TabIndex = 1 + TabIndex = 2 TabOrder = 1 + OnChanging = PageControlChanging object pgTitle: TTabSheet Caption = 'Title' - ClientHeight = 378 + ClientHeight = 418 ClientWidth = 638 object TitleMemoPanel: TPanel Left = 8 - Height = 151 + Height = 191 Top = 8 Width = 622 Align = alClient BorderSpacing.Around = 8 BevelOuter = bvNone - ClientHeight = 151 + ClientHeight = 191 ClientWidth = 622 TabOrder = 0 object lblTitle: TLabel @@ -68,7 +69,6 @@ object ChartAxisFrame: TChartAxisFrame Width = 21 BorderSpacing.Top = 2 Caption = 'Text' - ParentColor = False end object mmoTitle: TMemo AnchorSideLeft.Control = lblTitle @@ -79,7 +79,7 @@ object ChartAxisFrame: TChartAxisFrame AnchorSideBottom.Control = TitleMemoPanel AnchorSideBottom.Side = asrBottom Left = 0 - Height = 130 + Height = 170 Top = 21 Width = 622 Anchors = [akTop, akLeft, akRight, akBottom] @@ -109,7 +109,7 @@ object ChartAxisFrame: TChartAxisFrame object TitleParamsPanel: TPanel Left = 8 Height = 203 - Top = 167 + Top = 207 Width = 622 Align = alBottom BorderSpacing.Around = 8 @@ -199,7 +199,6 @@ object ChartAxisFrame: TChartAxisFrame Width = 45 Caption = 'Distance' FocusControl = seTitleDistance - ParentColor = False end object seTitleDistance: TSpinEdit AnchorSideLeft.Control = lblTitleDistance @@ -248,7 +247,6 @@ object ChartAxisFrame: TChartAxisFrame BorderSpacing.Left = 16 BorderSpacing.Top = 6 Caption = 'Automatic' - ParentColor = False end object cbAutoMax: TCheckBox AnchorSideLeft.Control = lblAutomatic @@ -427,7 +425,6 @@ object ChartAxisFrame: TChartAxisFrame Top = 31 Width = 38 Caption = 'Format' - ParentColor = False end object seLabelDistance: TSpinEdit AnchorSideLeft.Control = edLabelFormat @@ -453,7 +450,6 @@ object ChartAxisFrame: TChartAxisFrame Width = 45 Caption = 'Distance' FocusControl = seLabelDistance - ParentColor = False end end object Bevel2: TBevel @@ -524,7 +520,6 @@ object ChartAxisFrame: TChartAxisFrame Width = 67 BorderSpacing.Left = 16 Caption = 'Outer length' - ParentColor = False end object lblTickInnerLength: TLabel AnchorSideLeft.Control = lblTickLength @@ -535,7 +530,6 @@ object ChartAxisFrame: TChartAxisFrame Top = 37 Width = 64 Caption = 'Inner length' - ParentColor = False end object cbTickColor: TColorButton AnchorSideTop.Side = asrCenter @@ -555,8 +549,8 @@ object ChartAxisFrame: TChartAxisFrame end object pgGrid: TTabSheet Caption = 'Grid' - ClientHeight = 378 - ClientWidth = 513 + ClientHeight = 418 + ClientWidth = 638 object gbGrid: TGroupBox AnchorSideLeft.Control = pgGrid AnchorSideTop.Control = pgGrid @@ -584,14 +578,15 @@ object ChartAxisFrame: TChartAxisFrame BorderSpacing.Right = 16 BorderSpacing.Bottom = 8 Caption = 'Visible' + OnChange = cbGridVisibleChange TabOrder = 0 end end end object pgLine: TTabSheet Caption = 'Line' - ClientHeight = 378 - ClientWidth = 513 + ClientHeight = 418 + ClientWidth = 638 object gbFrame: TGroupBox AnchorSideLeft.Control = pgLine AnchorSideTop.Control = pgLine @@ -599,19 +594,19 @@ object ChartAxisFrame: TChartAxisFrame Left = 8 Height = 119 Top = 8 - Width = 275 + Width = 400 Anchors = [akTop, akLeft, akRight] BorderSpacing.Left = 8 BorderSpacing.Top = 8 Caption = 'gbFrame' ClientHeight = 99 - ClientWidth = 271 + ClientWidth = 396 TabOrder = 0 object cbFrameVisible: TCheckBox Left = 16 Height = 19 Top = 8 - Width = 239 + Width = 364 Align = alTop BorderSpacing.Left = 16 BorderSpacing.Top = 8 @@ -631,18 +626,18 @@ object ChartAxisFrame: TChartAxisFrame Left = 8 Height = 137 Top = 143 - Width = 275 + Width = 400 Anchors = [akTop, akLeft, akRight] BorderSpacing.Top = 16 Caption = 'gbAxisLine' ClientHeight = 117 - ClientWidth = 271 + ClientWidth = 396 TabOrder = 1 object cbAxisLineVisible: TCheckBox Left = 16 Height = 19 Top = 8 - Width = 239 + Width = 364 Align = alTop BorderSpacing.Left = 16 BorderSpacing.Top = 8 @@ -659,7 +654,7 @@ object ChartAxisFrame: TChartAxisFrame AnchorSideTop.Control = gbFrame AnchorSideRight.Control = pgLine AnchorSideRight.Side = asrBottom - Left = 307 + Left = 432 Height = 156 Top = 8 Width = 200 @@ -693,7 +688,6 @@ object ChartAxisFrame: TChartAxisFrame Top = 39 Width = 61 Caption = 'Base length' - ParentColor = False end object lblArrowLength: TLabel AnchorSideLeft.Control = cbArrowVisible @@ -704,7 +698,6 @@ object ChartAxisFrame: TChartAxisFrame Top = 70 Width = 37 Caption = 'Length' - ParentColor = False end object lblArrowWidth: TLabel AnchorSideLeft.Control = cbArrowVisible @@ -715,7 +708,6 @@ object ChartAxisFrame: TChartAxisFrame Top = 101 Width = 32 Caption = 'Width' - ParentColor = False end object seArrowBaseLength: TSpinEdit AnchorSideLeft.Control = lblArrowBaseLength diff --git a/components/tachart/demo/charteditor/ceaxisframe.pas b/components/tachart/demo/charteditor/ceaxisframe.pas index 2b5834ecfe..f8eed86eb7 100644 --- a/components/tachart/demo/charteditor/ceaxisframe.pas +++ b/components/tachart/demo/charteditor/ceaxisframe.pas @@ -76,6 +76,7 @@ type procedure cbAutoMinChange(Sender: TObject); procedure cbAxisLineVisibleChange(Sender: TObject); procedure cbFrameVisibleChange(Sender: TObject); + procedure cbGridVisibleChange(Sender: TObject); procedure cbInvertedChange(Sender: TObject); procedure cbLabelsVisibleChange(Sender: TObject); procedure cbShowChange(Sender: TObject); @@ -83,6 +84,7 @@ type procedure cbTitleVisibleChange(Sender: TObject); procedure edLabelFormatEditingDone(Sender: TObject); procedure mmoTitleChange(Sender: TObject); + procedure PageControlChanging(Sender: TObject; var AllowChange: Boolean); procedure rgTitleAlignmentClick(Sender: TObject); procedure seArrowBaseLengthChange(Sender: TObject); procedure seArrowLengthChange(Sender: TObject); @@ -95,6 +97,7 @@ type procedure seTitleDistanceChange(Sender: TObject); private FAxis: TChartAxis; + FAxisMin, FAxisMax: Double; FTitleFontFrame: TChartFontFrame; FTitleShapeBrushPenMarginsFrame: TChartShapeBrushPenMarginsFrame; FLabelFontFrame: TChartFontFrame; @@ -119,9 +122,12 @@ type procedure CalculatePreferredSize(var PreferredWidth, PreferredHeight: integer; {%H-}WithThemeSpace: Boolean); override; function GetChart: TChart; + function GetRealAxisMax: Double; + function GetRealAxisMin: Double; public constructor Create(AOwner: TComponent); override; procedure Prepare(Axis: TChartAxis); + function Validate(out AMsg: String; out AControl: TWinControl): Boolean; property Page: TChartAxisEditorPage read GetPage write SetPage; end; @@ -266,7 +272,7 @@ end; procedure TChartAxisFrame.cbAxisLineVisibleChange(Sender: TObject); begin - FAxis.Grid.Visible := cbGridVisible.Checked; + FAxis.AxisPen.Visible := cbAxisLineVisible.Checked; end; procedure TChartAxisFrame.cbFrameVisibleChange(Sender: TObject); @@ -274,9 +280,14 @@ begin GetChart.Frame.Visible := cbFrameVisible.Checked; end; +procedure TChartAxisFrame.cbGridVisibleChange(Sender: TObject); +begin + FAxis.Grid.Visible := cbGridVisible.Checked; +end; + procedure TChartAxisFrame.cbInvertedChange(Sender: TObject); begin - FAxis.Inverted := not FAxis.Inverted; + FAxis.Inverted := cbInverted.Checked; end; procedure TChartAxisFrame.cbLabelsVisibleChange(Sender: TObject); @@ -336,6 +347,22 @@ begin Result := TChartAxisEditorPage(PageControl.ActivePageIndex); end; +function TChartAxisFrame.GetRealAxisMax: Double; +begin + if cbAutoMax.Checked then + Result := FAxisMax + else + Result := seMaximum.Value; +end; + +function TChartAxisFrame.GetRealAxisMin: Double; +begin + if cbAutoMin.Checked then + Result := FAxisMin + else + Result := seMinimum.Value; +end; + procedure TChartAxisFrame.LabelChangedHandler(Sender: TObject); begin GetChart.Invalidate; @@ -356,6 +383,20 @@ begin FAxis.Title.Caption := mmoTitle.Lines.Text; end; +procedure TChartAxisFrame.PageControlChanging(Sender: TObject; + var AllowChange: Boolean); +var + msg: String; + C: TWinControl; +begin + if not Validate(msg, C) then + begin + C.SetFocus; + MessageDlg(msg, mtError, [mbOK], 0); + AllowChange := false; + end; +end; + procedure TChartAxisFrame.Prepare(Axis: TChartAxis); begin FAxis := Axis; @@ -379,8 +420,9 @@ begin end; // Page "Labels" - seMaximum.Value := Axis.Range.Max; - seMinimum.Value := Axis.Range.Min; + GetChart.GetAllSeriesAxisLimits(Axis, FAxisMin, FAxisMax); + seMaximum.Value := IfThen(Axis.Range.UseMax, Axis.Range.Max, FAxisMax); + seMinimum.Value := IfThen(Axis.Range.UseMin, Axis.Range.Min, FAxisMin); cbAutoMax.Checked := not Axis.Range.UseMax; cbAutoMin.Checked := not Axis.Range.UseMin; cbInverted.Checked := Axis.Inverted; @@ -491,6 +533,23 @@ begin FAxis.Title.Shape := AShape; end; +function TChartAxisFrame.Validate(out AMsg: String; out AControl: TWinControl): Boolean; +begin + Result := false; + if GetRealAxisMin >= GetRealAxisMax then + begin + AMsg := 'The axis minimum must be smaller than the axis maximum.'; + if seMaximum.Visible then + AControl := seMaximum + else if seMinimum.Visible then + AControl := seMinimum + else + AControl := cbAutoMax; + exit; + end; + Result := true; +end; + end. diff --git a/components/tachart/demo/charteditor/cecharteditor.lfm b/components/tachart/demo/charteditor/cecharteditor.lfm index 4711fa6c11..a35e70f8bb 100644 --- a/components/tachart/demo/charteditor/cecharteditor.lfm +++ b/components/tachart/demo/charteditor/cecharteditor.lfm @@ -10,7 +10,7 @@ object ChartEditorForm: TChartEditorForm OnCloseQuery = FormCloseQuery OnCreate = FormCreate OnDestroy = FormDestroy - LCLVersion = '2.1.0.0' + LCLVersion = '2.3.0.0' object ButtonPanel: TButtonPanel Left = 6 Height = 34 @@ -41,6 +41,7 @@ object ChartEditorForm: TChartEditorForm Constraints.MinWidth = 120 Images = ChartImagesDM.ChartImages TabOrder = 1 + OnChanging = TreeChanging OnDeletion = TreeDeletion OnSelectionChanged = TreeSelectionChanged end @@ -110,7 +111,6 @@ object ChartEditorForm: TChartEditorForm Font.Color = clWindow Font.Height = -16 Font.Style = [fsBold] - ParentColor = False ParentFont = False end object Image1: TImage diff --git a/components/tachart/demo/charteditor/cecharteditor.pas b/components/tachart/demo/charteditor/cecharteditor.pas index ea4011646c..ad411709bd 100644 --- a/components/tachart/demo/charteditor/cecharteditor.pas +++ b/components/tachart/demo/charteditor/cecharteditor.pas @@ -29,6 +29,8 @@ type procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); procedure OKButtonClick(Sender: TObject); + procedure TreeChanging(Sender: TObject; Node: TTreeNode; + var AllowChange: Boolean); procedure TreeDeletion(Sender: TObject; Node: TTreeNode); procedure TreeSelectionChanged(Sender: TObject); private @@ -57,6 +59,7 @@ type procedure SaveChartToStream; procedure SelectNode(ANode: TTreeNode); procedure RestoreChartFromStream; + function Validate(ANode: TTreeNode; out AMsg: String; out AControl: TWinControl): boolean; public procedure SelectAxis(AxisIndex: Integer; APage: TChartAxisEditorPage); procedure SelectFooter; @@ -210,8 +213,17 @@ begin end; procedure TChartEditorForm.ApplyButtonClick(Sender: TObject); +var + msg: String; + C: TWinControl; begin - RestoreChartFromStream; + if not Validate(Tree.Selected, msg, C) then + begin + C.SetFocus; + MessageDlg(msg, mtError, [mbOK], 0); + ModalResult := mrNone; + end else + RestoreChartFromStream; end; procedure TChartEditorForm.FormActivate(Sender: TObject); @@ -338,8 +350,31 @@ begin end; procedure TChartEditorForm.OKButtonClick(Sender: TObject); +var + msg: String; + C: TWinControl; begin - FOKClicked := true; + if not Validate(Tree.selected, msg, C) then + begin + C.SetFocus; + MessageDlg(msg, mtError, [mbOK], 0); + ModalResult := mrNone; + end else + FOKClicked := true; +end; + +procedure TChartEditorForm.TreeChanging(Sender: TObject; Node: TTreeNode; + var AllowChange: Boolean); +var + msg: String; + C: TWinControl; +begin + if not Validate(Tree.Selected, msg, C) then + begin + C.SetFocus; + MessageDlg(msg, mtError, [mbOk], 0); + AllowChange := false; + end; end; procedure TChartEditorForm.PopulateAxes(AChart: TChart); @@ -559,5 +594,19 @@ begin ChartImagesDM.ChartImages.GetBitmap(Tree.Selected.ImageIndex, Image1.Picture.Bitmap); end; +function TChartEditorForm.Validate(ANode: TTreeNode; out AMsg: String; + out AControl: TWinControl): Boolean; +begin + if ANode = nil then + exit(true); + + if TObject(ANode.Data) is TChartAxisFrame then + begin + Result := TChartAxisFrame(ANode.Data).Validate(AMsg, AControl); + if not Result then exit; + end; + Result := true; +end; + end. diff --git a/components/tachart/demo/charteditor/charteditordemo.lpi b/components/tachart/demo/charteditor/charteditordemo.lpi index b1ef559ef0..f3897f390d 100644 --- a/components/tachart/demo/charteditor/charteditordemo.lpi +++ b/components/tachart/demo/charteditor/charteditordemo.lpi @@ -198,6 +198,7 @@ + diff --git a/components/tachart/fpdoc/tachartliveview.xml b/components/tachart/fpdoc/tachartliveview.xml new file mode 100644 index 0000000000..fb4ce8bbc9 --- /dev/null +++ b/components/tachart/fpdoc/tachartliveview.xml @@ -0,0 +1,19 @@ + +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 vewport.The viewport is updated whenever the full extent of the associated chart changes. However, when property Active is set to false the viewport is fixed -- this is a setting which allows the user to review older data, or change the extent while still new data are coming at the same time. + + Chart on which the LiveView operates + Width of the visible viewport on the x axis, in graph unitsIf set to 0, the width of the current LogicalExtent is used instead. + + Allows to turn the scrolling controlled by the component ON and OFF. + Mode determining how the vertical y axis is displayed in the scrolling viewport +

lveAuto: adjusts the height to the visible data range automatically. This works also when the chart contains several y axes. +

lveFull: freezes the height to the full extent of the chart.

lveLogical: freezes the height to the logical extent of the chart.

lveMultiAxisRange tries to use a predefined Range of the y axes in case of a multi-axis chart. Use the Range.Min, Range.Max, Range.UseMin and Range.UseMax 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., axis.Range.UseMin=false and axis.Range.UseMax=false) the behaviour is similar to lveAuto.

+
+
Constructor of the TChartLiveView + + Destructor of the TChartLiveView + + +
+
+
diff --git a/components/tachart/fpdoc/tacustomseries.xml b/components/tachart/fpdoc/tacustomseries.xml index 2f3a816bbf..1183591dde 100644 --- a/components/tachart/fpdoc/tacustomseries.xml +++ b/components/tachart/fpdoc/tacustomseries.xml @@ -7,7 +7,7 @@ - Return number of points in the series. + Returns the number of points in the series. Add new point to the right of the series. @@ -32,7 +32,7 @@ EEditableSourceRequired - Verify that the data source is editable and return it. + Verifies that the data source is editable and returns it (or nil if the source is not a TListChartSource) Remove all points from the series. @@ -40,12 +40,10 @@ - - EEditableSourceRequired + EEditableSourceRequired - Remove point by index. - -

Requires editable data source.

+ Removes a data point by index. +

Requires editable data source.

@@ -55,6 +53,8 @@ Mark parameters. + Marks are the text labels drawn for each data point. + Called before the drawing of each mark. @@ -81,7 +81,7 @@ from a chart source Return the label for the point number AIndex formatted as per Marks property - Return the bounding rectangle for all points in the serie. + Returns the bounding rectangle for all points in the serie. TCustomChartSource.Extent @@ -192,6 +192,20 @@ per data points than specified by G By default, i.e. DepthBrightnessDelta=0, the series sides have the same color as the series itself.
+ Finds the y maximum and minimum of the series data having an x between AXMin and AXMax + Returns true when the series contains no data. + Returns the index of the last data point. + Returns the largest x value + Returns the smallest x value + Returns the largest y value + Returns the smallest y value + Returns the x value of the data point with the given index + Returns the y value of the data point with the given index + If a source provides data points with multiple x values, the x value with the index AXIndex is returned for the data point at index AIndex + + If a source provides data points with multiple y values, the y value with the index AYIndex is returned for the data point at index AIndex + + diff --git a/components/tachart/fpdoc/tagraph.xml b/components/tachart/fpdoc/tagraph.xml index 9970c1193b..7039ce2153 100644 --- a/components/tachart/fpdoc/tagraph.xml +++ b/components/tachart/fpdoc/tagraph.xml @@ -268,10 +268,7 @@ For example, pie series displays a separat
Converts the Y coordinate of a point from image units to graph units. - - - - + @@ -316,7 +313,9 @@ chart margin and the space reserved for series marks. - + This overridden background painting method does nothing because the background will be painted over anyway.The inherited behaviour is ignored. + + @@ -326,10 +325,10 @@ chart margin and the space reserved for series marks. - Draws the chart usung the given drawing back-end + Draws the chart using the given drawing back-end This method draws the chart by means of the given drawing back-end, Drawer. Unlike -PaintOnCanvas the chart can be drawn on non-canvas devices, such as a svg file. +PaintOnCanvas the chart can be drawn on non-canvas devices, such as a svg file or OpenGL context. @@ -659,6 +658,15 @@ as a replacement. Proportional, when set to true, applies the same scaling factor to the x and y axis. + Indicates whether the scaling parameters for the conversion between graph and image coordinates have valid values. + Returns the index of the currently active chart tool in the toolset attached to the chart. + + The inherited handler for MouseDown events is overridden to dispatch the event to the ChartToolset assigned to the chart. + The inherited handler for MouseMove events is overridden to dispatch the event to the ChartToolset assigned to the chart. + The inherited handler for MouseUp events is overridden to dispatch the event to the ChartToolset assigned to the chart. + + Determines the data range covered by all series assigned to the specified axis. Minimum and maximum are returned in axis units. + diff --git a/components/tachart/fpdoc/tatools.xml b/components/tachart/fpdoc/tatools.xml new file mode 100644 index 0000000000..f1f69ad4ae --- /dev/null +++ b/components/tachart/fpdoc/tatools.xml @@ -0,0 +1,8 @@ + +Tool for zooming while dragging the mouse over the area to be magnified + Cancels the current operation of the chart tool by pressing the ESC keyWhen the property EscapeCancels is true the current operation of the currently active chart tool, e.g. dragging the mouse for zooming, can be aborted by pressing the ESC key. + + + + + diff --git a/components/tachart/tachartliveview.pas b/components/tachart/tachartliveview.pas index 7d31a3cc01..3247326d22 100644 --- a/components/tachart/tachartliveview.pas +++ b/components/tachart/tachartliveview.pas @@ -1,3 +1,24 @@ +{ + /*************************************************************************** + TAChartLiveView.pas + ------------------- + + 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. + + It was created based on 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 + + See the file COPYING.modifiedLGPL.txt, included in this distribution, + for details about the license. + ***************************************************************************** + + Author: Werner Pamler +} + unit TAChartLiveView; {$mode objfpc}{$H+} @@ -8,21 +29,33 @@ uses Classes, SysUtils, TAGraph, TAChartUtils; type - TChartLiveViewExtentY = (lveAuto, lveFull, lveLogical); + TChartLiveViewExtentY = (lveAuto, lveFull, lveLogical, lveMultiAxisRange); + + { TChartLiveView } TChartLiveView = class(TComponent) + private + type + TLVAxisRange = record + Min, Max: double; + UseMin, UseMax: Boolean; + end; private FActive: Boolean; FChart: TChart; FExtentY: TChartLiveViewExtentY; FListener: TListener; FViewportSize: Double; + FAxisRanges: Array of TLVAxisRange; procedure FullExtentChanged(Sender: TObject); procedure SetActive(const AValue: Boolean); procedure SetChart(const AValue: TChart); procedure SetExtentY(const AValue: TChartLiveViewExtentY); procedure SetViewportSize(const AValue: Double); procedure UpdateViewport; + protected + procedure RestoreAxisRanges; + procedure StoreAxisRanges; public constructor Create(AOwner: TComponent); override; destructor Destroy; override; @@ -39,7 +72,7 @@ procedure Register; implementation uses - Math, TACustomSeries; + Math, TAChartAxis, TAChartAxisUtils, TACustomSeries; constructor TChartLiveView.Create(AOwner: TComponent); begin @@ -53,6 +86,8 @@ begin inherited; end; +{ A new data point has been added to the chart so that the full extent changes. + As a consequence the viewport of the live view must be updated. } procedure TChartLiveView.FullExtentChanged(Sender: TObject); begin if (not FActive) or (FChart = nil) then @@ -60,14 +95,46 @@ begin UpdateViewport; end; +procedure TChartLiveView.RestoreAxisRanges; +var + i: Integer; + ax: TChartAxis; +begin + if FChart = nil then + exit; + + for i := 0 to FChart.AxisList.Count-1 do + begin + ax := FChart.AxisList[i]; + ax.Range.Max := FAxisRanges[i].Max; + ax.Range.Min := FAxisRanges[i].Min; + ax.Range.UseMax := FAxisRanges[i].UseMax; + ax.Range.UseMin := FAxisRanges[i].UseMin; + end; +end; + +{ Activates the live view mode. Because the Range of the y axes can be changed + their current Range is stored before activating, and restored after + deactivating the mode. } procedure TChartLiveView.SetActive(const AValue: Boolean); begin if FActive = AValue then exit; FActive := AValue; + if FChart <> nil then + begin + if FActive then + StoreAxisRanges + else + RestoreAxisRanges; + end; + FullExtentChanged(nil); end; +{ Attaches the chart on which the liveview operates. Installs a "listener" + object so that the liveview can be notified of a change in the chart's full + extent when a new data point has been added (method FullExtentChanged). } procedure TChartLiveView.SetChart(const AValue: TChart); begin if FChart = AValue then exit; @@ -77,6 +144,7 @@ begin FChart := AValue; if FChart <> nil then FChart.FullExtentBroadcaster.Subscribe(FListener); + StoreAxisRanges; FullExtentChanged(Self); end; @@ -84,30 +152,54 @@ procedure TChartLiveview.SetExtentY(const AValue: TChartLiveViewExtentY); begin if FExtentY = AValue then exit; FExtentY := AValue; + RestoreAxisRanges; FullExtentChanged(nil); end; procedure TChartLiveView.SetViewportSize(const AValue: Double); begin if FViewportSize = AValue then exit; - FViewportSize := AValue; FullExtentChanged(nil); end; +procedure TChartLiveView.StoreAxisRanges; +var + i: Integer; + ax: TChartAxis; +begin + SetLength(FAxisRanges, FChart.AxisList.Count); + for i := 0 to FChart.AxisList.Count-1 do + begin + ax := FChart.AxisList[i]; + FAxisRanges[i].Max := ax.Range.Max; + FAxisRanges[i].Min := ax.Range.Min; + FAxisRanges[i].UseMax := ax.Range.UseMax; + FAxisRanges[i].UseMin := ax.Range.UseMin; + end; +end; + +{ "Workhorse" method of the component. It calculates the logical extent and + the axis ranges needed to display only the recent data values in the + given viewport. } procedure TChartLiveView.UpdateViewport; var - fext, lext: TDoubleRect; + fext, lext: TDoubleRect; // "full extent", "logical extent" variables w: double; - i: Integer; + i, j: Integer; ymin, ymax: Double; + ygmin, ygmax: Double; + ser: TChartSeries; + ax: TChartAxis; begin if not FChart.ScalingValid then exit; + if Length(FAxisRanges) = 0 then + StoreAxisRanges; + fext := FChart.GetFullExtent(); lext := FChart.LogicalExtent; - w := lext.b.x - lext.a.x; if FViewportSize = 0 then w := lext.b.x - lext.a.x else @@ -119,19 +211,6 @@ begin lext.b.x := lext.a.x + w; end; case FExtentY of - lveAuto: - begin - ymin := Infinity; - ymax := -Infinity; - for i := 0 to FChart.SeriesCount-1 do - if FChart.Series[i] is TChartSeries then - TChartSeries(FChart.Series[i]).FindYRange(lext.a.x, lext.b.x, ymin, ymax); - if (ymin <> Infinity) and (ymax <> -Infinity) then - begin - lext.a.y := ymin; - lext.b.y := ymax; - end; - end; lveFull: begin lext.a.y := fext.a.y; @@ -139,6 +218,57 @@ begin 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; diff --git a/components/tachart/tacustomseries.pas b/components/tachart/tacustomseries.pas index e006c81c03..b9bd90b4f9 100644 --- a/components/tachart/tacustomseries.pas +++ b/components/tachart/tacustomseries.pas @@ -441,13 +441,17 @@ function TCustomChartSeries.GetAxisBounds(AAxis: TChartAxis; out AMin, AMax: Double): Boolean; var ex: TDoubleRect; + axIndexX, axIndexY: Integer; begin - if (AAxis.Index = AxisIndexX) or (AAxis.Index = AxisIndexY) then begin + axIndexX := GetAxisX.Index; + axIndexY := GetAxisY.Index; + + if (AAxis.Index = axIndexX) or (AAxis.Index = axIndexY) then begin ex := EmptyExtent; GetBounds(ex); with ex do begin - UpdateBoundsByAxisRange(FChart.AxisList, AxisIndexX, a.X, b.X); - UpdateBoundsByAxisRange(FChart.AxisList, AxisIndexY, a.Y, b.Y); + UpdateBoundsByAxisRange(FChart.AxisList, axIndexX, a.X, b.X); + UpdateBoundsByAxisRange(FChart.AxisList, axIndexY, a.Y, b.Y); if IsRotated then begin Exchange(a.X, a.Y); Exchange(b.X, b.Y); diff --git a/components/tachart/tagraph.pas b/components/tachart/tagraph.pas index ab77eaab67..59992d38fb 100644 --- a/components/tachart/tagraph.pas +++ b/components/tachart/tagraph.pas @@ -352,6 +352,7 @@ type procedure Draw(ADrawer: IChartDrawer; const ARect: TRect); procedure DrawLegendOn(ACanvas: TCanvas; var ARect: TRect); procedure EnableRedrawing; + procedure GetAllSeriesAxisLimits(AAxis: TChartAxis; out AMin, AMax: Double); function GetFullExtent: TDoubleRect; function GetLegendItems(AIncludeHidden: Boolean = false): TChartLegendItems; procedure Notify(ACommand: Integer; AParam1, AParam2: Pointer; var AData); override; @@ -1140,6 +1141,15 @@ begin AClass := nil; end; +procedure TChart.GetAllSeriesAxisLimits(AAxis: TChartAxis; out AMin, AMax: Double); +var + interval: TDoubleInterval; +begin + interval := GetAxisBounds(AAxis); + AMin := interval.FStart; + AMax := interval.FEnd; +end; + function TChart.GetAxisBounds(AAxis: TChartAxis): TDoubleInterval; var s: TBasicChartSeries; diff --git a/components/tachart/tatextelements.pas b/components/tachart/tatextelements.pas index 2410703923..600f9ceb3a 100644 --- a/components/tachart/tatextelements.pas +++ b/components/tachart/tatextelements.pas @@ -365,7 +365,8 @@ begin ADrawer.ClippingStop; DrawLink(ADrawer, ADataPoint, ALabelCenter); - ADrawer.Brush := GetLabelBrush; + with GetLabelBrush do + ADrawer.SetBrushParams(Style, ColorToRGB(Color)); if IsMarginRequired then begin if GetFrame.Visible then ADrawer.Pen := GetFrame diff --git a/components/tachart/tatypes.pas b/components/tachart/tatypes.pas index ec9d1b86ef..082c815b08 100644 --- a/components/tachart/tatypes.pas +++ b/components/tachart/tatypes.pas @@ -751,6 +751,7 @@ begin diag := -ADrawer.Scale(Round(Sqrt(Sqr(Length) + Sqr(Width)))); pt1 := AEndPos + RotatePointX(diag, AAngle - da)*sgn; pt2 := AEndPos + RotatePointX(diag, AAngle + da)*sgn; + ADrawer.SetPenParams(psSolid, FPColorToTColor(APen.FPColor), APen.Width); if BaseLength > 0 then begin ptBase := AEndPos + RotatePointX(-ADrawer.Scale(BaseLength), AAngle)*sgn; ADrawer.SetBrushParams(bsSolid, FPColorToChartColor(APen.FPColor));