TChart mit zusätzlichen Datatools + Axis Mouse Listener?

Rund um die LCL und andere Komponenten
Antworten
Helios
Lazarusforum e. V.
Beiträge: 106
Registriert: Mi 29. Jun 2011, 22:36
OS, Lazarus, FPC: Lazarus 2.2.4 Windows 10 64Bit / Lazarus 2.0.12 Debian 11.7 „Bullseye" 64Bit
CPU-Target: 64Bit
Wohnort: Leonberg

TChart mit zusätzlichen Datatools + Axis Mouse Listener?

Beitrag von Helios »

Hallo liebe Forenmitglieder,

erneut eine (für mich) tiefergehende Frage zur TChart Komponente:

Basierend auf dem TChart "toolsdemo" (..\Lazarus\components\tachart\demo\tools\) in einer Standardinstallation (Lazarus 1.8, Windws7 64bit)
möchte ich
1. das Intervall der x und y-Achse (wenn mehrere vorhanden, jede y-Achse einzeln) mit der Maus
(rechte Maustaste + Mauswheel up/down) vergrößern oder verkleinern für die jeweils ausgewählte Achse (über MouseOver oder
angeklickte Achse, sofern das überhaupt geht).
Gibt es dazu einen Maus Listener auf den jeweiligen Achsen?
2. in diesem Zusammenhang habe ich mit DataTools experimentiert mit dem ich mir die Funktionswerte meiner Serien
an einer bestimmten X Koordinaten (wo sich gerade meine Maus befindet) anzeigen möchte. Wenn ich selbst z.B. ein DataTool integriere
sind (bekanntermaßen) die schönen voreingestellten panning (draginng) und zooming (Mausklick und Rechteckausschnitt wählen)
Funktionen weg. Wie reaktiviere ich diese korrekterweise bei einem selbst erstellen (zusätzlichen) Datatool?

Zusammengefasst suche ich also eine Möglichkeit diese Funktionen
- "Drag to Zoom" (linke Maustaste gedrückt und Rechteck(ausschnitt) aufziehen -> Standard Zooming)
- "Mouse wheel to zoom" (analog wie im "toolsdemo", hinein und hinauszoomen in einen Chart nur mit dem Mausrad)
- "Panning" (verschieben der Ansicht durch rechte Maustaste drücken und ziehen -> Standard Panning)
- "Mouse wheel to Zoom Axis" (neu?!?) auf den jeweiligen Achse
- Anzeige des (ggf. interpolierten) y-Achsen Funktionswerte meiner Serie an der Mausposition (im Chart) z.B. in einer Statusbar
(ohne auf die oben genannten Funktionen verzichten zu müssen)
alle gleichzeitig in einem Chart umzusetzen. Geht das und wenn ja wie (am besten)?

Vielen Dank im Voraus für jede Antwort
Gruß
Helios

wp_xyz
Beiträge: 4869
Registriert: Fr 8. Apr 2011, 09:01

Re: TChart mit zusätzlichen Datatools + Axis Mouse Listener?

Beitrag von wp_xyz »

Helios hat geschrieben:- "Drag to Zoom" (linke Maustaste gedrückt und Rechteck(ausschnitt) aufziehen -> Standard Zooming)
- "Panning" (verschieben der Ansicht durch rechte Maustaste drücken und ziehen -> Standard Panning)

  • Eine ChartToolSet-Komponente aufs Formular klicken und mit dem Chart verbinden (Property "Toolset")
  • Doppelklick auf dem ChartToolset, ein ZoomDragTool und ein ZoomPanTool hinzufügen
  • das ZoomDragTool wird mit Shift = ssLeft, der ZoomPanTool mit Shift = ssRight aktiviert--> damit hast du wieder den Zustand wie bei den eingebauten Tools. Gleichzeitig hast du aber auch Zugriff auf weitere Parameter um Details des Zoom bzw. Unzoom-Vorgangs zu kontrollieren. Für das folgende rate ich noch beim ZoomTool das Flag zreClick bei "RestoreExtentOn" zu deaktiveren, weil wir den Click allein noch brauchen - siehe unten. Der ungezoomte Zustand wird nun durch einen beliebigen Dragvorgang wiederhergestellt, der nur nicht von links oben nach rechts unten gehen darf (das ist die Richtung, um tiefer hineinzuzoomen)

Helios hat geschrieben:- "Mouse wheel to zoom" (analog wie im "toolsdemo", hinein und hinauszoomen in einen Chart nur mit dem Mausrad)
- "Mouse wheel to Zoom Axis" (neu?!?) auf den jeweiligen Achse

  • Für "Mouse wheel to zoom" ein ZoomMouseWheel-Tool zum Toolset hinzufügen. Für isotropes Zoomen (alle Achsen gleichzeitig) ZoomFactor auf (z.B.) 1.1 (d.h. 10% Zoom pro Mausrad-Schritt)
  • Um nur entlang der x-Achse zu zoomen (bzw. nur entlang der y-Achse) muss man gleichzeit ZoomRatio anpassen. Das Vorgehen ist hier (http://wiki.lazarus.freepascal.org/TACh ... click_tool) für das ZoomClickTool abgeleitet, gilt aber genauso für das Mausrad.
  • TAChart stellt m.E. keine Routinen zur Verfügung, um einen Klick auf einer Achse zu erkennen. Im einfachsten Fall würde ich den Clickpunkt mit Chart.ImageToGraph in Graph-Koordinaten umrechnen und dann prüfen, ob der umgerechnete Punkt (GraphClickPkt) im Chart.LogicalExtent liegt. Wenn ja, erfolgte der Klick im Series-Bereich --> nicht auf einer Achse. Wenn ext := Chart.LogicalExtent und GraphiClickPkt.Y < ext.a.Y dann erfolgte der Klick außerhalb des Series-Bereichs irgendwo im Bereich der x-Achse und man kann die Zoomfactor/ZoomRatio-Werte für horizontales Zoomen einstellen. Andererseits, wenn GraphClickPkt.X < ext.a.x, wurde im Bereich der y-Achse geklickt, und man kann die Zoomfactor/ZoomRatio-werte für vertikales Zoomen einstellen. Schließlich, wenn innerhalb des Series-Bereichs geklickt wurde, könnte man wieder auf isotropes Zoomen zurücksetzen - hier ist es sinnvoll, wenn ein Klick den Zoombereich nicht zurücksetzt - daher die Bemerkung oben wegen RestoreExtentOn.
  • Natürlich, wenn mehrere Achsen vorhanden sind, wird's schwieriger...

Helios hat geschrieben:-Anzeige des (ggf. interpolierten) y-Achsen Funktionswerte meiner Serie an der Mausposition (im Chart) z.B. in einer Statusbar

  • Zur Abfrage der Mausposition bzgl der Series ist wahrscheinlich ein DatapointCrosshairTool am besten geeignet. Falls du den CrossHair-Cursor nicht willst, einfach Size auf 0 setzen. Wenn du kein Shift deklariest, springt der Cursor innerhalb des GrabRadius immer zum nächsten Datenpunkt. Um den Text in die Statuszeile zu schreiben, hängst du dich ins Ereignis "OnDraw" - das ist zwar gedacht, spezielle Cursor-Formen zu erzeugen, aber man kann es natürlich für alles mögliche verwenden.
  • Dass der Cursor den Interpolationslinien folgt, geht bei der TLineSerie noch nicht, dagegen bei einer oder beiden SplineSeries -- das werde ich in einer ruhigen Stunde einmal nachrüsten.

Code: Alles auswählen

unit Unit1;
 
{$mode objfpc}{$H+}
 
interface
 
uses
  Classes, SysUtils, Forms, Controls, Graphics, Dialogs, ComCtrls, ExtCtrls,
  StdCtrls, TAGraph, TASeries, TASources, TATools, Types;
 
type
 
  { TForm1 }
 
  TForm1 = class(TForm)
    Chart1: TChart;
    Chart1LineSeries1: TLineSeries;
    Chart1LineSeries2: TLineSeries;
    ChartToolset1: TChartToolset;
    ChartToolset1DataPointCrosshairTool1: TDataPointCrosshairTool;
    ChartToolset1PanDragTool1: TPanDragTool;
    ChartToolset1ZoomDragTool1: TZoomDragTool;
    ChartToolset1ZoomMouseWheelTool1: TZoomMouseWheelTool;
    Label1: TLabel;
    Panel1: TPanel;
    RandomChartSource1: TRandomChartSource;
    RandomChartSource2: TRandomChartSource;
    StatusBar1: TStatusBar;
    procedure Chart1MouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure ChartToolset1DataPointCrosshairTool1Draw(
      ASender: TDataPointDrawTool);
    procedure ChartToolset1ZoomDragTool1BeforeMouseDown(ATool: TChartTool;
      APoint: TPoint);
  private
    FClickedAxis: String;
 
  public
 
  end;
 
var
  Form1: TForm1;
 
implementation
 
{$R *.lfm}
 
uses
  TAChartUtils,    // TDoubleRect
  TACustomSeries;  // für TChartSeries
 
const
  ZOOM_FACTOR = 1.1;
 
{ TForm1 }
 
procedure TForm1.Chart1MouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
var
  extImg: TRect;
  ext: TDoubleRect;
  P: TDoublePoint;
begin
  ext := Chart1.LogicalExtent;
  P := Chart1.ImageToGraph(Point(X, Y));
  if (P.X < ext.a.X) and (P.Y >= ext.a.Y) and (P.Y <= ext.b.Y) then begin
    FClickedAxis := 'y';
    ChartToolset1ZoomMouseWheelTool1.ZoomFactor := 1.0;
    ChartToolset1ZoomMouseWheelTool1.ZoomRatio := ZOOM_FACTOR;
  end else
  if (P.Y < ext.a.Y) and (P.X >= ext.a.X) and (P.X <= ext.b.X) then begin
    FClickedAxis := 'x';
    ChartToolset1ZoomMouseWheelTool1.Zoomfactor := ZOOM_FACTOR;
    ChartToolset1ZoomMouseWheelTool1.ZoomRatio := 1.0/ ZOOM_FACTOR;
  end else begin
    FClickedAxis := '(none)';
    ChartToolset1ZoomMouseWheelTool1.ZoomFactor := ZOOM_FACTOR;
    ChartToolset1ZoomMouseWheeltool1.ZoomRatio := 1.0;
  end;
  Statusbar1.SimpleText := 'Clicked axis: ' + FClickedAxis;
end;
 
procedure TForm1.ChartToolset1DataPointCrosshairTool1Draw(
  ASender: TDataPointDrawTool);
var
  ser: TChartSeries;
  idx: Integer;
begin
  ser := ASender.Series as TChartSeries;
  idx := ASender.PointIndex;
  if ser = nil then
    Statusbar1.SimpleText := ''
  else
    Statusbar1.SimpleText := Format('Series "%s": x = %.2f, y=%.2f', [ser.Title, ser.XValue[idx], ser.YValue[idx]]);
end;
 
procedure TForm1.ChartToolset1ZoomDragTool1BeforeMouseDown(ATool: TChartTool;
  APoint: TPoint);
begin
  Chart1MouseDown(nil, mbLeft, ATool.Shift, Apoint.X, APoint.Y);
end;
 
end.     

Code: Alles auswählen

 
object Form1: TForm1
  Left = 310
  Height = 481
  Top = 127
  Width = 668
  Caption = 'Form1'
  ClientHeight = 481
  ClientWidth = 668
  LCLVersion = '1.9.0.0'
  object Chart1: TChart
    Left = 0
    Height = 337
    Top = 0
    Width = 668
    AxisList = <   
      item
        Marks.LabelBrush.Style = bsClear
        Minors = <>
        Title.LabelFont.Orientation = 900
        Title.LabelBrush.Style = bsClear
      end   
      item
        Alignment = calBottom
        Marks.LabelBrush.Style = bsClear
        Minors = <>
        Title.LabelBrush.Style = bsClear
      end>
    Foot.Brush.Color = clBtnFace
    Foot.Font.Color = clBlue
    Legend.Visible = True
    Title.Brush.Color = clBtnFace
    Title.Font.Color = clBlue
    Title.Text.Strings = (
      'TAChart'
    )
    Toolset = ChartToolset1
    Align = alClient
    OnMouseDown = Chart1MouseDown
    object Chart1LineSeries1: TLineSeries
      Title = 'Series 1'
      LinePen.Color = clRed
      ShowPoints = True
      Source = RandomChartSource1
    end
    object Chart1LineSeries2: TLineSeries
      Title = 'Series 2'
      LinePen.Color = clBlue
      ShowPoints = True
      Source = RandomChartSource2
    end
  end
  object StatusBar1: TStatusBar
    Left = 0
    Height = 23
    Top = 458
    Width = 668
    Panels = <>
  end
  object Panel1: TPanel
    Left = 0
    Height = 121
    Top = 337
    Width = 668
    Align = alBottom
    AutoSize = True
    BevelOuter = bvNone
    ClientHeight = 121
    ClientWidth = 668
    TabOrder = 2
    object Label1: TLabel
      Left = 8
      Height = 105
      Top = 8
      Width = 374
      AutoSize = False
      BorderSpacing.Left = 8
      BorderSpacing.Top = 8
      BorderSpacing.Bottom = 8
      Caption = 'Drag top-left to bottom-right --> zoom'#13#10'Drag in different direction --> unzoom'#13#10'Mouse-wheel --> zoom'#13#10'- Click near x axis (outside chart area): Mouse wheel zooms horizontally'#13#10'- Click near y axis (outside chart area): Mouse wheel zooms verticlly'#13#10'- Click inside chart aread: Mouse wheel zoom isotropically'#13#10'Move mouse near data point --> display data values in status bar'#13#10'Drag right button --> pan'
      ParentColor = False
    end
  end
  object RandomChartSource1: TRandomChartSource
    PointsNumber = 5
    RandSeed = 1392190234
    XMax = 1
    XMin = 0
    YMax = 1
    YMin = 0
    left = 153
    top = 49
  end
  object ChartToolset1: TChartToolset
    left = 247
    top = 176
    object ChartToolset1ZoomDragTool1: TZoomDragTool
      Shift = [ssLeft]
      OnBeforeMouseDown = ChartToolset1ZoomDragTool1BeforeMouseDown
      Brush.Style = bsClear
      RestoreExtentOn = [zreDragTopLeft, zreDragTopRight, zreDragBottomLeft]
    end
    object ChartToolset1PanDragTool1: TPanDragTool
      Shift = [ssRight]
    end
    object ChartToolset1ZoomMouseWheelTool1: TZoomMouseWheelTool
      ZoomFactor = 1.1
    end
    object ChartToolset1DataPointCrosshairTool1: TDataPointCrosshairTool
      OnDraw = ChartToolset1DataPointCrosshairTool1Draw
      Size = 12
    end
  end
  object RandomChartSource2: TRandomChartSource
    PointsNumber = 10
    RandSeed = 1392190234
    XMax = 1
    XMin = 0
    YMax = 1.5
    YMin = 0.3
    left = 153
    top = 112
  end
end                 

Helios
Lazarusforum e. V.
Beiträge: 106
Registriert: Mi 29. Jun 2011, 22:36
OS, Lazarus, FPC: Lazarus 2.2.4 Windows 10 64Bit / Lazarus 2.0.12 Debian 11.7 „Bullseye" 64Bit
CPU-Target: 64Bit
Wohnort: Leonberg

Re: TChart mit zusätzlichen Datatools + Axis Mouse Listener?

Beitrag von Helios »

Hallo wp_xyz,

entschuldige die sehr, sehr späte Rückmeldung (ich brauchte Zeit für die Umsetzung und für den Umstieg auf Lazarus 1.8.2).
Deine Vorschläge habe ich 1:1 umsetzen können und laufen "out of the box". Gigantisch! Vielen, vielen Dank!
Bei Deinem Hinweis:
- Natürlich, wenn mehrere Achsen vorhanden sind, wird's schwieriger...

hast Du sicherlich meine nächste Frage vorausgeahnt:-)
In dem Beispiel von meinem letzten Thread (https://www.lazarusforum.de/viewtopic.php?f=18&t=11455) hatte ich mehrere Y-Achsen übereinander benötigt.
Dieses Beispiel würde ich gerne nochmal aufgreifen, und meine Frage ist, wie ich eine ausgewählte Serie (von vielen) bzgl. der Y-Achse mit dem Mausrad skalieren kann
die nicht ausgewählten Serien sollen dabei ihre Skalierung behalten.

Und eine weitere Frage: Kann ich die graphischen Einstellungen der Serien/Achsen/Transformationen ermitteln, damit ich Sie ggf. abspeichern
und aus einer Datei wieder herauslesen kann, damit der Benutzer wieder die gleiche Ansicht erhält, wie er Sie vor dem Verlassen der Applikation
eingestellt waren?

Vielen Dank und Gruß

Helios

wp_xyz
Beiträge: 4869
Registriert: Fr 8. Apr 2011, 09:01

Re: TChart mit zusätzlichen Datatools + Axis Mouse Listener?

Beitrag von wp_xyz »

Das ist ein ziemlicher Brocken...

Alle die Chart-Extent-Tools (Zoom, Pan) orientieren sich am "Extent" des Chart, d.h. dem gesamten Achsenbereich zwischen Min und Max; Zoomen eines Teilbereichs ist nicht vorgesehen. Das heißt natürlich nicht, dass das nicht möglich ist - Man braucht ein UserDefined ChartTool, muss berechnen, auf welchem Achsenpanel sich die Maus befindet, wenn das Rad gedreht wird (OnAfterMouseWheelUp/Down, oder in OnBeforeMouseWheelUp/Doen) und dann muss man die Achsenkoordinaten, die auf die entsprechenden Panel-Grenzen (t.Minvalue, t.MaxValue) abgebildet werden, auseineinanderziehen bzw. zusammenstauchen - nur habe ich gerade keine Idee, wie man die Grenzen des sichtbaren Achsenbereichs ablegen kann ohne eine Riesenzusatzdatenstruktur anlegen zu müssen.

Auch für die zweite Frage, nach dem Speichern/Laden eines Chart, habe ich keine abschließende Antwort. Gemeint ist natürlich nicht das Speichern in Graphikformaten, sondern so wie in einer lfm-Datei. Hier würde ich den Streaming-Vorgang für Formularkomponenten kapern, das allgemeine Vorgehen ist im wiki beschrieben: http://wiki.freepascal.org/Streaming_co ... o_a_stream. Der folgende Code legt einen Chart als Ausschnitt einer lfm-Datei ab:

Code: Alles auswählen

procedure TForm1.Button1Click(Sender: TObject);
var
  stream: TFileStream;
begin
  stream := TFileStream.Create('Chart.dat', fmCreate);;
  try
    WriteComponentAsTextToStream(stream, Chart);
  finally
    stream.Free;
  end;
end;

Wenn du dir diese Datei im Text-Editor ansiehst, fällt dir vielleicht auf, dass keine Series enthalten sind - klar, in deinem Beispielcode weiter oben ("ButtonAddClick") erhalten die Series beim Erzeugen den Owner "self", also das Formular; mein Code speichert aber nur den Chart ab. Es spricht aber nichts dagegeben, den Chart als Owner zu nehmen. Die Transformations habe ich noch nicht in die Datei schreiben können, eigentlich sollte es genauso funktionieren wie bei den Series, aber irgendwas scheint da anders zu sein.

Auch das Einlesen habe ich noch nicht hingekriegt. Du kannst es ja mal selbst versuchen - alles wesentliche müsste in dem genannten wiki-Artikel stehen.

Helios
Lazarusforum e. V.
Beiträge: 106
Registriert: Mi 29. Jun 2011, 22:36
OS, Lazarus, FPC: Lazarus 2.2.4 Windows 10 64Bit / Lazarus 2.0.12 Debian 11.7 „Bullseye" 64Bit
CPU-Target: 64Bit
Wohnort: Leonberg

Re: TChart mit zusätzlichen Datatools + Axis Mouse Listener?

Beitrag von Helios »

Hallo wp_xyz,

Danke für Deine Antwort zu noch so später Stunde.
Dann gibt es somit noch eine weites Feld für mich zum forschen und ausprobieren:-)
Schöne Restarbeitswoche!

Gruß und gute Nacht!

Helios

Antworten