The best way to have lineseries with snapping cursors (pointers)? lazarus / 55-TAChart ---------------------------------------------------------------------- CM630 · 2012-09-19 06:26 Here is the goal: I want to have one (probably more) series (graphic). I will need let's say 10 cursors (pointers) on it. According to the needs, these cursors might need to snap to the graphic, i.e. when I move them (left and right) their Y value shall be the same as the one of the graphic (they will snap). I intend to use the Data Point Drag tool. First I intended to use separate series for each cursor, but maybe I will use one series with disabled line, still I have not checked if it can be detected which exactly point of the curve is dragged. Here is the major question: I do not expect such a feature to exist, but since I have already reimplemented something existing before (horizontal sync) this time I am asking, if there is some ready solution for snapping cursors? If there is not such a feature, I intend to use something like: Code: [Select]ondrag (or on mouseup- it will look worse, but might be faster) if cursor.snap=true then cursor.Y= graphic.Y But in order to do that, I need to know which value of graphic is closer to the X of the cursor. Shall I use getnearestpoint or something else. I searched the demos, but there is not a single occurrence of getnearestpoint there. Any proposal is welcome. I noticed, that Data Point Drag is not moving very smoothly so speed might be important. ---------------------------------------------------------------------- Ask · 2012-09-19 10:00 I recommend you to create your own descendant of TLineSeries and override MovePoint procedure, including "snapping" logic into it. To find a point to snap to, you should indeed use GetNearestPoint procedure, it should also work for function series. All your pointers can indeed be contained in a single series. At this moment, I am not sure if this is general enough feature to merit inclusion in TAChart, however parallel discussion about functional series and discontinuity pointers makes me this it might be. ---------------------------------------------------------------------- CM630 · 2012-09-19 11:16 I attached a demo to illustrate exactly what I need. It seems to be working properly, but is absolutely not optimized. I couldn't find info how to use GetNearestPoint, I would appreciate some help with it. Edit: I just read in the other thread (http://lazarus.freepascal.org/index.php/topic,18257.0.html), that @ask mentioned adding a property OnIsSeriesAffected. It would be nice to know exactly which series was affected (as written in one of the TODOs). ---------------------------------------------------------------------- Ask · 2012-09-19 14:20 Yes, the demo is a good way to explain what you want. I recommend: 1) Set Chart1.DoubleBuffered := true 2) Set MarkPositions := lmpPositive for both "cursors" 3) Remove Cursor1DrawPointer, it does nothing (although it actually exposed a minor bug in TAChart, fixed in r38754) 4) Remove SnapCursor and GetClosestToX procedures 5) Replace ChartToolset1DataPointDragTool1AfterMouseMove with the following: Code: [Select]procedure TForm1.ChartToolset1DataPointDragTool1AfterMouseMove(   ATool: TChartTool; APoint: TPoint); var   p: TNearestPointParams;   r: TNearestPointResults;   dt: TDataPointDragTool; begin   p.FDistFunc := @PointDist;   p.FPoint := APoint;   p.FRadius := 15;   if Wavedata.GetNearestPoint(p, r) then begin     dt := ATool as TDataPointDragTool;     if dt.Series = nil then exit;     with dt.Series as TLineSeries do begin       SetXValue(0, r.FValue.X);       SetYValue(0, r.FValue.Y);       SetText(0, Format('U=%3.3f V', [r.FValue.Y]));     end;   end; end; 6) Replace the constant "1000" in GenSin function by 100 or lower. Note that you are currently drawing more then 10 data points per pixel(!). Which is, of course, slow. 7) If you really want to draw a function (as opposed to some measurement data, with function only for demonstration purposes), use TFuncSeries. ---------------------------------------------------------------------- Ask · 2012-09-20 14:04 Since r38765, you can write even simpler code: Code: [Select]procedure TForm1.ChartToolset1DataPointDragTool1Drag(   ASender: TDataPointDragTool; var APoint: TPoint); var   p: TNearestPointParams;   r: TNearestPointResults; begin   p.FDistFunc := @PointDist;   p.FPoint := APoint;   p.FRadius := 15;   if Wavedata.GetNearestPoint(p, r) then     APoint := r.FImg; end; and set Marks.Format := 'U=%0:3.3f V' Also, there is no need to set properties of "cursors" at run-time, you can set them all in the designer. ---------------------------------------------------------------------- CM630 · 2012-09-21 06:49 Quote from: Ask on September 19, 2012, 04:20:05 pmI recommend: 1) Set Chart1.DoubleBuffered := true I found no documentation about this, but if I get it right, it will speed up work with the chart? Quote from: Ask on September 19, 2012, 04:20:05 pm2) Set MarkPositions := lmpPositive for both "cursors" I have already seen this property outside is usually better, when you have longer duration and respectively more sinusoids in the screen- the oscillogram will look rather like a black bar. You can try this in the demo, by changing SinData:=GenSin(10,50,15); to SinData:=GenSin(10,50,300); Quote from: Ask on September 19, 2012, 04:20:05 pm5) Replace ChartToolset1DataPointDragTool1AfterMouseMove with the following: Here is a question. Usually all the higher speed oscillograms will have the same distance between the points. For example, if 1 second contains 10 000 points, I can easily calculate that 0,35 seconds will correspond to point №0,35.10 000-1=3 499. I might just check one point on the left and one on the right. I do not know which method uses GetNearestPoint (probably some numeric method), but in you opinion, which solution will be faster in this particular case? Quote from: Ask on September 19, 2012, 04:20:05 pm6) Replace the constant "1000" in GenSin function by 100 or lower. Note that you are currently drawing more then 10 data points per pixel(!). Which is, of course, slow. In real cases I will have between 200 and 400 point per period, but with a much longer duration (say 10 seconds). I hope that this won't be a big problem. Quote from: Ask on September 19, 2012, 04:20:05 pm7) If you really want to draw a function (as opposed to some measurement data, with function only for demonstration purposes), use TFuncSeries. I'll search some info about it, if it can generate sinusoids (or y:=sin(x)) it might be useful. For now this is not a higher priority for me, and I could not find how to use it, but at least I found a bug. Here is how it happened. I added "Function series" to a chart.  I started editing parameters in the object inspector. While editing it I clicked in the Series editor „Remove" and I got "Invalid type cast...." error. I tried several other cases- it occurs, that this message is displayed while editing "AxisIndexX" and "AxisIndexY", in some other cases (properties) there is no problem. Quote from: Ask on September 20, 2012, 04:04:50 pmAlso, there is no need to set properties of "cursors" at run-time, you can set them all in the designer. If you mean colour, crosshair shape, etc. I have several reasons to do it at run-time: 1. The number of the charts will vary, so they themselves will be generated. 2. Maybe the number of the cursor will vary. 3. It will be nice to adjust properties during program settings. Or maybe you mean something else? ---------------------------------------------------------------------- Ask · 2012-09-21 18:03 QuoteQuote from: AskSet Chart1.DoubleBuffered := trueI found no documentation about this, but if I get it right, it will speed up work with the chart? It is not a TAChart-specific feature, but a property of TWinControl. If set, it will actually slow down chart drawing a little, but reduce flickering. Google "double buffering" for more info. Quote I started editing parameters in the object inspector. While editing it I clicked in the Series editor „Remove" and I got "Invalid type cast...." error.Hm, it seems that some incompatible changes were made to designer code, and I did not know about them. Should be fixed in r38792 -- please test. QuoteIf you mean colour, crosshair shape, etc. I have several reasons to do it at run-timeSure, these are perfectly good reasons -- I just mentioned it in case it was accidental decision. ---------------------------------------------------------------------- CM630 · 2012-09-27 08:27 To no surprise, when writing your code I got: Identifier not found: PointDist; I suppose I should have a function with the same name, too? You did not answer my previous question, so I'd ask again, I hope it is not too hard to answer: QuoteUsually all the higher speed oscillograms will have the same distance between the points. For example, if 1 second contains 10 000 points, I can easily calculate that 0,35 seconds will correspond to point №0,35.10 000-1=3 499. I might just check one point on the left and one on the right. I do not know which method uses GetNearestPoint (probably some numeric method), but in you opinion, which solution will be faster in this particular case? ---------------------------------------------------------------------- Ask · 2012-09-27 12:24 QuoteIdentifier not found: PointDist; Add TAGeometry unit to the uses clause. QuoteI do not know which method uses GetNearestPoint (probably some numeric method), but in you opinion, which solution will be faster in this particular case? GetNearestPoint basically uses a simple loop, because it must work with an arbitrary distance function. In r38853 I have added some optimization for the case when the distance function is superlinear of X coordinate. Note that the source must be sorted to get the full effect. However, the extreme optimization you propose (only consider two point nearest by X) is only correct for the case of PointDistX function, which ignores Y coordinate entirely. You can easily see the problem on the example your provided -- with your original code, the pointer jumped up and down near the steep slopes of the function, while with GetNearestPoint it snaps to the actual nearest point. Also, the main source of slowness with extremely large number of points is probably not the distance calculation, but chart redrawing. You can test that by adding a crosshair tool, which on Windows does not require full chart redraw to move. Note that switching CrossHairTool.DrawingMode between tdmNormal and tdmXor significantly affects performance in this case. ---------------------------------------------------------------------- CM630 · 2012-09-28 08:26 Quote from: Ask on September 27, 2012, 02:24:37 pmGetNearestPoint basically uses a simple loop, because it must work with an arbitrary distance function. In r38853 I have added some optimization for the case when the distance function is superlinear of X coordinate. Note that the source must be sorted to get the full effect.I will install r38853 and check, because with my current version  GetNearestPoint seems to work even slower than mine simple loop. I thought that GetNearestPoint uses Newton- Raphson or something similar. Quote from: Ask on September 27, 2012, 02:24:37 pmHowever, the extreme optimization you propose (only consider two point nearest by X) is only correct for the case of PointDistX function, which ignores Y coordinate entirely. Google gave me nothing about PointDistX , but I was thinking, that really data about the X scale is quite useless, I just need correct display of the scale. I'll check if I can add only Y values to series and then maybe I could use the transformation tool. Quote from: Ask on September 27, 2012, 02:24:37 pmAlso, the main source of slowness with extremely large number of points is probably not the distance calculation, but chart redrawing. Shall I understand, that when I move only the cursors, all other series are redrawn, including the one with the many points? Quote from: Ask on September 27, 2012, 02:24:37 pmYou can test that by adding a crosshair tool, which on Windows does not require full chart redraw to move. Note that switching CrossHairTool.DrawingMode between tdmNormal and tdmXor significantly affects performance in this case. I'll test these, as soon as I get somewhere with some other issues, that occurred recently. A question, that is bothering me is if there are 10 000 points on a 200 pixels wide chart- how many points are actually drawn on the chart? Are they 200 or all the 10 000? If only the 200 are displayed, won't this increase performance, since the other 9 800 are useless (until a zoom in)? ---------------------------------------------------------------------- wp · 2012-09-28 10:18 Quotehow many points are actually drawn on the chart? As many as there are in the ChartSource, so I fear it's the full 10 000. QuoteIf only the 200 are displayed, won't this increase performance, since the other 9 800 are useless (until a zoom in)?Certainly. You could draw only every, say, 10th data point, and the speed increase will we dramatic. If you plot data from a UserDefined ChartSource, you could do something like this: Code: [Select]procedure TForm1.UserDefinedChartSource1GetChartDataItem(   ASource:TUserDefinedChartSource; AIndex:Integer; var AItem:TChartDataItem); begin   AIndex := Min(AIndex*10, High(DataArray);   AItem.X := DataArray[AIndex].x;   AItem.Y := DataArray[AIndex].y; end; and set the UserDefinedChartSource's PointsNumber to Length(DataArray) div 10. But you must be aware that very narrow features in your data curve will be lost or distorted. ---------------------------------------------------------------------- CM630 · 2012-09-28 11:05 Quote from: wp on September 28, 2012, 12:18:52 pmBut you must be aware that very narrow features in your data curve will be lost or distorted. I was thinking about this solution, but this way when I will have to resend the draw data when zooming. If would be much nicer, if TChard did this automatically (if this could improve performance, of course). Also, the idea is that the Chart should know, that the point exists, even if it is not displayed, so I could find it, when I search it. Yet I have another question. Here is an event for the point drag tool: Quoteprocedure TForm1.ChartToolset1DataPointDragTool1Drag(ASender: TDataPointDragTool; var APoint: TPoint); So I have ASender: TDataPointDragTool and var APoint: TPoint. How am I to detect which is the series, to which the dragged point belongs and what is it's index in the series? In fact, the Toolset is used for several charts, but I detect the chart from MouseDown. Yet, another issue arise, which seems more important to me. I have a Chart, the chart has let's say 5 line series. But when I try to adress them as Chart.Series[0] they are addressed as TBasicChartSeries, so I cannot do Chart.Series[0].GetXValue(0). Is there a way out? ---------------------------------------------------------------------- wp · 2012-09-28 11:49 QuoteHow am I to detect which is the series, to which the dragged point belongs and what is it's index in the series?The datapoint drag tool has properties "Series" and "PointIndex". There is no need for a separate treatment of MouseDown by you because all that is handled by the tool. BTW: in your source code, hold the CTRL key down while clicking on any identifier. Lazarus will open the unit where this identifier is implemented and jump to its definition. In this way you will be guided to the corresponding source units where you can see all properties of the class. QuoteI have a Chart, the chart has let's say 5 line series. But when I try to adress them as Chart.Series[0] they are addressed as TBasicChartSeries, so I cannot do Chart.Series[0].GetXValue(0). Is there a way out?Type-cast the series to TChartSeries -- this is the common ancestor of all series types that provide GetXValue, i.e. do Code: [Select]TChartSeries(Chart.Series[0]).GetXValue(0) ---------------------------------------------------------------------- CM630 · 2012-09-28 12:15 Just a remark, before I go on making out rthe other stuff: you probably did not mean TChartSeries(Chart.Series[0]).GetXValue(0), but  TLineSeries(Chart.Series[0]).GetXValue(0) ---------------------------------------------------------------------- wp · 2012-09-28 12:22 No - TLineSeries inherits from TChartSeries, i.e. all published and public properties of TChartSeries are also available for TLineSeries. Using TChartSeries is  more general than using TLineSeries. If you once would decide to use a TAreaSeries (which inherits from TChartSeries as well) instead of a TLineSeries the expression with the TChartSeries would still compile while that with TLineSeries won't. ---------------------------------------------------------------------- CM630 · 2012-09-28 12:56 Hm, my Lazarus does not know TChartSeries, it only knows TChartSeriesList. On the other hand, TlineSeries seems to work fine (so far)... ---------------------------------------------------------------------- wp · 2012-09-28 13:09 QuoteHm, my Lazarus does not know TChartSeries Lazarus definitely knows, just add Code: [Select]uses   TACustomSeries; ---------------------------------------------------------------------- Ask · 2012-09-28 17:08 Quote from: paskalI thought that GetNearestPoint uses Newton- Raphson or something similar. I do not see how Newton-Raphson method is applicable to the nearest- point problem (except perhaps for a quite specific case of monotonic function series). Quote from: wpCertainly. You could draw only every, say, 10th data point, and the speed increase will we dramatic.Quote from: paskalI was thinking about this solution, but this way when I will have to resend the draw data when zooming. If would be much nicer, if TChart did this automatically (if this could improve performance, of course). Also, the idea is that the Chart should know, that the point exists, even if it is not displayed, so I could find it, when I search it.First, note that you do not have to write extra code to resend data on redraw, chart series request their sources for data as needed. I did consider automatic adjustment of series details depending in the scale, but there are some issues to be solved: 1) While working on "fast line" series optimization, I have already tried to eliminate sequential points with the same X coordinate during the drawing. It turned out to decrease speed, at least on Windows, presumably because system code performs similar optimization while drawing polylines, and is more efficient than is possible to achieve in Pascal. At the same time, axis and graph transformation still needed to be performed for every point. 2) So, I see two viable ways: 2.1) Pre-select points without actually accessing them all (essentially what wp suggested). To do this in general case, we need: 2.1.1) a method of of density estimation (simply selecting with a fixed step might fail for non-uniformly distributed series) 2.1.2) as wp already noted, a method of local extent estimation (so as not to lose local peaks) 2.1.3) either a method to change source values based on the current extent, which is contrary to the current source/series architecture, or a decision to implement this for each series separately. If we are willing to ignore all the above issues, the it is quite simple to add TSparseChartSource with Origin and IndexStep properties. However, as we have seen, the same effect can already be easily achieved with user-defined source. So, in order to have a feature worth building, we need to answer the above questions. 2.2) Caching. This is a big topic -- caching may be performed on various levels -- source points, source totals and extents, graph- converted points, image-converted points, raster images, etc. Each of these levels might bring benefits for speed of certain operations. However, they come with the price of substantial code complexity. Some basic caching is already implemented in various sources and series (see, for example, TListSource.Extent and corresponding code in SetXValue/SetYValue to update it efficiently). Nevertheless, I do plan to add more caches in the future. Currently, the main obstacle for this is a too simplistic notification system used in TAChart. I hoped to improve it based on Observer pattern support which was recently added to FPC, but the follow-up discussion seems to indicate that it may be insufficient for my goals... 3) There is another solution, which, while not "automatic", may be appropriate for your case: 3.1) Create several sources with your data averaged over different windows. 3.2) Create a separate line series for each source 3.3) Display only one of series depending on the current scale 3.4) [Optionally] search only the full-data series to the drag tool Besides the optimization, this solution will also look much better for the small zoom levels. The downside is that it requires some coding at the application level. If you like it, I shall see how I can make it easier from the TAChart side. ---------------------------------------------------------------------- CM630 · 2012-09-29 05:55 I'll think about your ideas on a later stage. Meanwhile I found something, that I'm maybe right, maybe- not. If I use LineSeries.SetYValue(0,5); in an OnDrag event, SetYValue won't work- the point will stay on the position of the mouse pointer. Meanwhile LineSeries.GetYValue returns the previously set result, i.e. 5. I have attached a lightly modified version of the previous tool. If the point of the series does not move during SetYvalues, then GetYValue shall return the actual value of the point. And if the Y value should be programically  changed while dragging- this is question of view point- which one should have higher priority- user (mouse) or program. Maybe the best decisions (as always) is to have this property adjustable? ---------------------------------------------------------------------- Ask · 2012-09-29 06:36 OnDrag event has AGraphPoint parameter which you should change to modify the position of the dragged point. ---------------------------------------------------------------------- CM630 · 2012-09-29 07:56 I found no AGraphPoint parameter, I only found APoint, so I started examining your code again, and I just understood what you meant by that: Quote from: Ask on September 27, 2012, 02:24:37 pmYou can easily see the problem on the example your provided -- with your original code, the pointer jumped up and down near the steep slopes of the function, while with GetNearestPoint it snaps to the actual nearest point.My code works exactly as I want-  the point moves on the curve, ignoring the Y value of the drag (mouse) pointer. Your code searches a point in the vicinity, having the same Y as the mouse pointer, but I do not want that (though I might use it, i.e. for snapping on the zero values). As far as AGraphPoint is it the same as APoint? ---------------------------------------------------------------------- Ask · 2012-09-29 10:37 QuoteAs far as AGraphPoint is it the same as APoint?Not quite. I have changed the definition of OnDrag event slightly since my previous post. It now accepts a point in graph, rather then image, coordinates. Quotethe point moves on the curve, ignoring the Y value of the drag (mouse) pointerUse PointDistX instead of PointDist function. The handler should now read: Code: [Select]procedure TForm1.ChartToolset1DataPointDragTool1Drag(   ASender: TDataPointDragTool; var AGraphPoint: TDoublePoint); var   p: TNearestPointParams;   r: TNearestPointResults; begin   p.FDistFunc := @PointDistX;   p.FOptimizeX := true;   p.FPoint := Chart1.GraphToImage(AGraphPoint);   p.FRadius := 15;   if Wavedata.GetNearestPoint(p, r) then     AGraphPoint := Chart1.ImageToGraph(r.FImg); end; ---------------------------------------------------------------------- CM630 · 2012-09-29 12:13 Error: identifier idents no member "FOptimizeX" I need later Lazarus (I'm currently with the latests available snapshot) or Uses something? I have no idea what "FOptimizeX"does, so I removed it, but without it sometimes the cursor is not located on the sine curve, but at some distance away from it. BTW, can I remove the crosshair (the four arrows)? ---------------------------------------------------------------------- Ask · 2012-09-29 14:39 QuoteError: identifier idents no member "FOptimizeX" I need later Lazarus (I'm currently with the latests available snapshot) or Uses something?Yes, update your Lazarus. Make sure you are using trunk, not fixes branch. Quotethe cursor is not located on the sine curve, but at some distance away from it.Can't reproduce. Does it happen for you in the demo project you posted? Quotecan I remove the crosshair (the four arrows)?You mean how to change cursor shape? Set ActiveCursor property. ---------------------------------------------------------------------- CM630 · 2012-10-02 05:49 There are still no later win32 builds for Lazarus. If FOptimizeX is not related, I have attached the source and some snapshots to show the problem, that @ask cannot reproduce. ActiveCursor:=0 is exactly what I need, thanks! ---------------------------------------------------------------------- Ask · 2012-10-02 06:35 QuoteI have attached the source and some snapshots to show the problem,That's very helpful, thanks. The key point you did to mention in the previous reports is the fact that the "inexact" snapping is visible only after zooming in. This is caused simply by rounding error -- if you look at the code, you may notice that that it performs conversion from graph to image coordinates. You can replace the last line of handler with Code: [Select]    AGraphPoint := Wavedata.AxisToGraph(r.FValue); ---------------------------------------------------------------------- CM630 · 2012-10-04 06:12 Now it looks fine, besides cursors move very smoothly. ---------------------------------------------------------------------- CM630 · 2012-10-12 08:58 I think that it won't be bad if I remind something, because IMHo the issue still is open: I wrote: Quote from: paskal on September 29, 2012, 07:55:37 amIf I use LineSeries.SetYValue(0,5); in an OnDrag event, SetYValue won't work- the point will stay on the position of the mouse pointer. Meanwhile LineSeries.GetYValue returns the previously set result, i.e. 5. And I got this answer: Quote from: Ask on September 29, 2012, 08:36:19 amOnDrag event has AGraphPoint parameter which you should change to modify the position of the dragged point. Anyway, if SetYValue does not change the Y values, the result given by LineSeries.GetYValue shall not change, too? Is this a bug or there is some purpose? ---------------------------------------------------------------------- Ask · 2012-10-12 09:42 Point coordinates are changed by the drag tool immediately after a call to the OnDrag event. Perhaps AGraphPoint parameter should be renamed to ANewGraphPoint for clarity? ---------------------------------------------------------------------- CM630 · 2012-10-12 10:24 It seems that we cannot understand each other, so I try again. So when I do LineSeries.SetYValue(0,5); the point is not moved to Y=5. I suppose that the reason for that is the fact that after that the point from dragging is taken (i.e. after running LineSeries.SetYValue(0,5); the point gets moved by the mouse to let's say Y=4. Whatever the reason is, after all is done, when I run LineSeries.GetYValue the result should be 4, but it is 5 instead. I could try to find an app to take a video, if necessary? P.S. Currently I do not use LineSeries.SetYValue(0,5);, I use aGraphPoint, but I think that the issue remained open. ---------------------------------------------------------------------- Ask · 2012-10-12 12:43 I do understand you. You are trying to call SetYValue from the inside of OnDrag event handler. It does work (if you call GetYValue immediately, you will see the effect). However, just after return from the handler, point position is moved to the value determined by AGraphPoint. ---------------------------------------------------------------------- CM630 · 2012-10-12 13:03 So right after return from the handler, point position is moved to the value determined by AGraphPoint and this is okay. But shouldn't GetYValue return the value, set by AGraphPoint? I'll check again on Monday, if it really behaves so, maybe I got confused by the synchronization. ---------------------------------------------------------------------- Ask · 2012-10-12 13:43 Quoteshouldn't GetYValue return the value, set by AGraphPoint?Yes, it should, but (1) you should check it after the handler, not inside of it and (2) if you active have axis transformations, they will of course affect the Y value. ---------------------------------------------------------------------- CM630 · 2012-10-15 06:23 So here it is: Code: [Select]procedure TForm1.ChartToolset1DataPointDragTool1Drag( ASender: TDataPointDragTool; var AGraphPoint: TDoublePoint); var   p: TNearestPointParams;   r: TNearestPointResults; begin   p.FDistFunc := @PointDistX;   p.FOptimizeX := true;   p.FPoint := Chart1.GraphToImage(AGraphPoint);   p.FRadius := 15;   Cursor1.SetYValue(0,7);   if Wavedata.GetNearestPoint(p, r) then AGraphPoint := Wavedata.AxisToGraph(r.FValue);   Cursor1.SetText(0,'U='+ FloatToStrF(Cursor1.GetYValue(0),ffFixed,3,3) + ' V') ; //Comment this line, if you are using Lazarus build less than  38732.   Label2.Caption:= FloatToStrf(Cursor1.GetYValue(0),ffFixed,3,3) + '   ' + FloatToStrf(Cursor2.GetYValue(0),ffFixed,3,3); end; If the code is run this way, both label2.caption and the cursor caption show „7“. But if I do Cursor1.GetYValue(0) in a procedure run after procedure TForm1.ChartToolset1DataPointDragTool1Drag( ASender: TDataPointDragTool; var AGraphPoint: TDoublePoint); then I get the exact Y value. If I get you right, the idea is that the behaviour shall be exactly this? Couldn't and shouldn't running AGraphPoint := Wavedata.AxisToGraph(r.FValue); change the result of GetYValue immediately, instead of waiting the whole procedure to finish? Or probably the concept is that with the current implementation the designer will know both the values in the beginning and during the execution process (current value) of the procedure. It is sure, that breaking compatibility with Delphi is unwanted. ---------------------------------------------------------------------- Ask · 2012-10-15 07:08 QuoteIf I get you right, the idea is that the behaviour shall be exactly this?Yes. QuoteCouldn't and shouldn't running AGraphPoint := Wavedata.AxisToGraph(r.FValue); change the result of GetYValue immediately, instead of waiting the whole procedure to finish?That is a simple variable assignment, it can not immediately change value of another variable. Also, note the point I made in the previous post about axis transformation -- AGraphPoint is in graph coordinates, but GetYValue returns axis coordinates.