Crosshair and Panes lazarus / 55-TAChart ---------------------------------------------------------------------- fpcoder · 2013-12-05 22:08 I now use separate charts for panes as suggested by wp. It makes it easy to resize the panes, and some other things that was difficult to achieve with the panes-in-a-single-chart approach. But a problem I must solve is the crosshair. I used multiple panes within a single chart because the crosshair is then drawn across all panes automatically. As a solution I have a vertical TConstantLine series in every pane. I call it CrossHairExtensionLine  and I want to show it and move it left/right in the panes chart as the crosshair moves in the main chart. So my question is: how to get the right value from the CrossHairTool so I can move the CrossHairExtensionLine to the right position? I guess the moving should be done from a CrossHairTools Event? But which event is best to use? ---------------------------------------------------------------------- fpcoder · 2013-12-05 23:13 After some trial and error, I seem to have found an answer to my own question. The crosshair extension line can be moved from the CrosshairTool OnDraw event: CrossHairExtensionLine.Position:= CrosshairTool.Position.X; Is this approach acceptable? I also have another question about the Crosshair tool. By default it jumps from point to point in a series. I would like the crosshair to move smoothly and not stop just at the series points. Is this possible somehow? ---------------------------------------------------------------------- wp · 2013-12-05 23:51 QuoteCrossHairExtensionLine.Position:= CrosshairTool.Position.X; Is this approach acceptable?Sure, that's the way how I would do it. But doesn't the CrosshairExtensionLine run across the entire chart height? I thought you wanted to restrict it to the pane height only. QuoteI would like the crosshair to move smoothly and not stop just at the series points. Is this possible somehow?The DatapointCrosshairTool locks at the data points only. Therefore, I fear, no. There is one of the spline series, either cubic spline or B-spline, however, which calculates the data along the interpolating line - if I remember correctly. This one should follow the curve smoothly. If you have only one series per pane, there might be another solution: instead of using the DatapointCrosshairTool you could grab the OnMouseMove event of the chart. The location of the mouse cursor passed as a parameter is in "image coordinates" of the entire chart. Use the chart's XImageToGraph and YImageToGraph functions to calculate the x and y positions of the mouse cursor in "Graph coordinates" - please have a look at our tutorial http://wiki.lazarus.freepascal.org/ TAChart_Tutorial:_Multiple_Panes_in_one_Chart if you don't remember what that is. Use the y graph coordinate of the mouse cursor and compare it with the range between Minvalue and Maxvalue of each AutoScaleTransformation that you had set up to create the panes. This way you can determine in which pane the mouse cursor is. Since you have only one series per pane you know now the series which is close to the mouse cursor. Now you can move your CrosshairextensionLine to the mouse x coordinate that you calculated by xImageToGraph. You could even look for the neighboring data points in the series' chartsource and calculate the interpolated y value at the x position of the mouse cursor and display that value in the status line or a label. I guess it should work - but never tried. But let me think about it, maybe there is another solution within the ChartTools framework. ---------------------------------------------------------------------- fpcoder · 2013-12-06 17:23 Good info, wp. I have now a "smooth" CrossHair by using XImageToGraph and YXImageToGraph to move two TConstantLines in the main chart. The panes crosshair extension is moved the same way: Quote    VerticalCrossHairLine.Position:= Chart1.XImageToGraph(X);     HorizontalCrossHairLine.Position:= Chart1.YImageToGraph(Y);     CrossHairExtensionLine.Position:=Chart1.XImageTo Graph(X); A demo is attached. Press Ctrl and move mouse in main chart to see the crosshair in action. But some things need fixing: How to hide the crosshair lines when Ctrl is released? Also, the crosshair moves even when the mouse is outside the chart frame and  if the crosshair is moved too far past an axis, the series scrolls away and almost out of sight. For some reason, clicking around in main chart or pane makes the charts shift a pixel or so. The main chart and the pane is linked by a TChartExtentLink, maybe that has something to do with it. ---------------------------------------------------------------------- wp · 2013-12-06 18:46 QuoteHow to hide the crosshair lines when Ctrl is released?Set the property Active of the crosshairline series to false. Since the chart does not have an OnKeyUp event you can use that of the Form; for safety you can also add that code to the mouseMove event in the case that Ctrl is not pressed. Code: [Select]procedure TForm1.Chart1MouseMove(Sender: TObject; Shift: TShiftState;   X, Y: Integer); begin   { If Ctrl is not pressed, then hide crosshair lines and exit }   if not (ssCTRL in Shift)  then begin     VerticalCrosshairLine.Active := false;     HorizontalCrosshairLine.Active := false;     CrosshairExtensionLine.Active := false;     Exit;   end;   { Show cross hair lines }   VerticalCrosshairLine.Active := true;   HorizontalCrosshairLine.Active := true;   CrosshairExtensionLine.Active := true;   { Move the ConstantLines to simulate crosshair and crosshair extension }   VerticalCrossHairLine.Position:= Chart1.XImageToGraph(X);;   HorizontalCrossHairLine.Position:= Chart1.YImageToGraph(Y);;   CrossHairExtensionLine.Position:=Chart1.XImageToGraph(X);   { Show coordinates in statusbar panels}   StatusBar1.Panels[0].Text:='X: ' +IntToStr(X);   StatusBar1.Panels[1].Text:='Y: ' +IntToStr(Y); end; procedure TForm1.FormKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState); begin   if not (ssCTRL in Shift)  then begin     VerticalCrosshairLine.Active := false;     HorizontalCrosshairLine.Active := false;     CrosshairExtensionLine.Active := false;   end; end; Quoteclicking around in main chart or pane makes the charts shift a pixel or so. The main chart and the pane is linked by a TChartExtentLink, maybe that has something to do with it.Yes. You are using the Mode elmXY of the ChartExtentLink. But I think you only want to link the x axes. Therefore, elmOnlyX is the better choice. With that, the shifts are gone. [EDIT] Ah - I forgot about your issue when moving the mouse out of the chart: Chart1LineSeries.Source.Extent.a.X and ChartLineSeries.Source.Extent.b.X are the minimum and maximum of the x values. Therefore you have to make sure that the result of XImageToGraph stays within this interval - you can use the function EnsureRange of the math unit easily: Code: [Select]  VerticalCrosshairLine.Position := EnsureRange(     Chart1.XImageToGraph(X),     Chart1LineSeries1.Source.Extent.a.X,     Chart1LineSeries1.Source.Extent.b.X   ); ---------------------------------------------------------------------- wp · 2013-12-06 19:40 Maybe you want the horizontal line to follow the data curve instead of the mouse cursor? Here's the interpolation code that you have to add to MouseMove: Code: [Select]procedure TForm1.Chart1MouseMove(Sender: TObject; Shift: TShiftState;   X, Y: Integer);   function InterpolateY(x: Double): Double;   var     i: Integer;     x1, x2, y1, y2: Double;   begin     for i:=0 to Chart1LineSeries1.Source.Count-2 do begin       x1 := Chart1LineSeries1.Source.Item[i]^.X;       x2 := Chart1LineSeries1.Source.Item[i+1]^.X;       if (x1 = x2) and (x1 = x) then begin         y1 := Chart1LineSeries1.Source.Item[i]^.Y;         y2 := Chart1LineSeries1.Source.Item[i+1]^.Y;         result := (y1 + y2) / 2;         exit;       end;       // Let's assume that data points are sorted by x in ascending order       if InRange(x, x1, x2) then begin         y1 := Chart1LineSeries1.Source.Item[i]^.Y;         y2 := Chart1LineSeries1.Source.Item[i+1]^.Y;         Result := y1 + (x - x1) * (y2 - y1) / (x2 - x1);         exit;       end;     end;     Result := NaN;   end; var   xvalue: Double;   yvalue: Double; begin   { If Ctrl is not pressed, then hide crosshair lines and exit }   if not (ssCTRL in Shift)  then begin     VerticalCrosshairLine.Active := false;     HorizontalCrosshairLine.Active := false;     CrosshairExtensionLine.Active := false;     Exit;   end;   { Show cross hair lines }   VerticalCrosshairLine.Active := true;   HorizontalCrosshairLine.Active := true;   CrosshairExtensionLine.Active := true;   { Move the ConstantLines to simulate crosshair and crosshair extension }   xvalue := EnsureRange(     Chart1.XImageToGraph(X),     Chart1LineSeries1.Source.Extent.a.X,     Chart1LineSeries1.Source.Extent.b.X   );   VerticalCrossHairLine.Position:= xvalue;   yvalue := InterpolateY(xvalue);   if IsNaN(yvalue) then     HorizontalCrossHairline.Active := false   else     HorizontalCrossHairLine.Position := yvalue;   CrossHairExtensionLine.Position:=xvalue;   { Show coordinates in statusbar panels}   StatusBar1.Panels[0].Text:='X: ' +IntToStr(X);   StatusBar1.Panels[1].Text:='Y: ' +IntToStr(Y); end; ---------------------------------------------------------------------- fpcoder · 2013-12-06 21:53 The trick with EnsureRange saved my day! Now the crosshair is confined inside the chart frame only. Many thanks!! Tomorrow I will try to polish the crosshair a bit more. I also tested your interpolation code and it works perfectly. Its saved for future use. QuoteAh - I forgot about your issue when moving the mouse out of the chart: Chart1LineSeries.Source.Extent.a.X and ChartLineSeries.Source.Extent.b.X are the minimum and maximum of the x values. Therefore you have to make sure that the result of XImageToGraph stays within this interval - you can use the function EnsureRange of the math unit easily: Code: [Select]  VerticalCrosshairLine.Position := EnsureRange(     Chart1.XImageToGraph(X),     Chart1LineSeries1.Source.Extent.a.X,     Chart1LineSeries1.Source.Extent.b.X   ); ---------------------------------------------------------------------- fpcoder · 2013-12-11 18:10 Today I got the idea to put all the code we discussed into a standalone class: TExtendedCrossHair. It can show/hide the crosshair and add/remove crosshair extensions in other charts. The crosshair works when I test, but can be improved. Check out the demo app. ---------------------------------------------------------------------- fpcoder · 2013-12-11 19:33 The idea is that the TExtendedCrossHair should handle any number of crosshair extensions needed. Thats why there is a TCrossHairExtensionList to store TCrossHairExtensions. Also, when another pane is clicked the TargetChart should change so the Crosshair is shown in the new chart and the old chart gets a crosshair extension instead. But thats not implemented yet. ---------------------------------------------------------------------- wp · 2013-12-11 23:19 Nice work. I'll bookmark this thread - maybe I'll need your idea some time.