Get X and Y values under mouse pointer - teechart

I have a simple chart using only TLineSeries with a single Y axis. As the mouse pointer moves over the chart, I’d like to get the X and Y values associated with the pointer’s position independent of any series.
I can handle the MouseMove event and get the screen X and Y coordinates, but the only way I’ve found to convert them is via the Series->XValues->Locate and Series->YValue->Locate methods.
There are two problems with this:
1 - The value returned from Series->YValue->Locate is always -1 regardless of whether the pointer is over a series line or not.
2 – The value returned from Series->XValue->Locate is -1 unless the pointer is over a part of the chart containing a series line.
Why does Series->YValue->Locate always return -1?
More importantly, how can I get the values regardless of whether the pointer is over a part of the chart with series lines or not?
I’m using the version of TeeChart that ships with Rad Studio XE3.

Why does Series->YValue->Locate always return -1?
That's because Locate uses a series value and returns its point index in the series. OnMouseMove provides screen pixel coordinates, not series values.
More importantly, how can I get the values regardless of whether the
pointer is over a part of the chart with series lines or not?
You can use axes as a reference instead of series, for example:
procedure TForm2.Chart1MouseMove(Sender: TObject; Shift: TShiftState; X,
Y: Integer);
var
XVal: double;
YVal: double;
begin
XVal:=Chart1.Axes.Bottom.CalcPosPoint(X);
YVal:=Chart1.Axes.Left.CalcPosPoint(Y);
Chart1.Title.Text[0]:=FormatFloat('#.##', XVal) + ' - ' + FormatFloat('#.##', YVal);
end;

Related

Find cursor's coordinates in NSTextView

I have wrapping NSTextView instances stacked vertically, for example:
The quick brown fox
jumps over the lazy dog
Jackdaws love my big
sphinx of quartz
I need to move between them with up/down arrows. For example, when the cursor is positioned after the l in "lazy" and the user presses the down arrow, the cursor should jump right after the y in "my" – like it would do if these sentences were in the same text view.
By default, when the down arrow is pressed while the cursor is at the last wrapped line, a text view moves it to the end of that line. While I can use textView(_:doCommandBy:) in NSTextViewDelegate to detect the "arrow down" selector and override the default behavior, there are two problems:
I can determine if the cursor is at the last line by getting its position via the selectedRanges property and then checking for the newline character after this position, but it is not possible to know if it is at the last wrapped line, i.e. near the border of the current text view.
I need to know the X coordinate of the cursor to place it at approximately the same X coordinate in another text view (the fonts won't necessarily be fixed-width, so I can't rely on the character count).
I suppose both of them can be resolved via NSLayoutManager, but I can't wrap my head around all of its available methods.
It turned out to be relatively easy, here's what I've done (the examples are in C#). First, boundingRect(forGlyphRange:in:) gets the cursor's location in the current view:
var cursorLocation = new NSRange(CurrentTextView.SelectedRange.Location, 0);
var cursorCoordinates = CurrentTextView.LayoutManager.BoundingRectForGlyphRange(cursorLocation, CurrentTextView.TextContainer).Location;
Then if the second text view is below, the insertion point will be at 0 on the Y axis:
var insertionPoint = new CGPoint(cursorCoordinates.X, 0);
And if it is above, then another view's height should be used (reduced by 1, otherwise the resulting character index will be incorrect and the cursor will be placed at the end of the line):
var insertionPoint = new CGPoint(cursorCoordinates.X, AnotherTextView.Bounds.Size.Height - 1);
After getting the insertion point, another view needs to become the first responder and then characterIndexForInsertion(at:) does the job:
Window.MakeFirstResponder(AnotherTextView);
var index = AnotherTextView.CharacterIndex(insertionPoint);
AnotherTextView.SelectedRange = new NSRange(index, 0);

TeeChart Firemonkey LineSeries NULL Points

I am creating a Firemonkey (FMX) application. I use TChart with a Line Series (TLineSeries). I need to draw a chart line that is not continuous. The x-axis is date time and I want the line to be drawn say between 10 and 11AM and then again between 3PM and 4PM. I tried the following but it always draws a continuous line from the first point (X1) to the last point (X4):
Series.TreatNulls:=TTreatNullsStyle.tnDontPaint;
Series.AddXY(X1,1);
Series.AddXY(X2,1);
Series.AddNullXY(X2,1);
Series.AddNullXY(X3,1);
Series.AddXY(X3,1);
Series.AddXY(X4,1);
Where
X1=date value at 10AM
X2=date value at 11AM
X3=date value at 3PM
X4=date value at 4PM
I tried various combinations of TreatNulls but still no success. I am using TeeChart Version 2021.32
Any suggestions would be appreciated.
I've given it a try and it seems to work fine for me here with the code below.
Note a single null point is enough to avoid drawing two sectors (A-null-B)
uses Series, Constants;
procedure TForm1.FormCreate(Sender: TObject);
var X1, X2, X3, X4: TDateTime;
begin
Caption:=TeeMsg_Version;
X1:=Date+DateTimeStep[dtOneHour]*10;
X2:=Date+DateTimeStep[dtOneHour]*11;
X3:=Date+DateTimeStep[dtOneHour]*15;
X4:=Date+DateTimeStep[dtOneHour]*16;
with Chart1.AddSeries(TLineSeries) do
begin
XValues.DateTime:=True;
AddXY(X1,1);
AddXY(X2,1);
//AddNullXY(X2,1);
AddNullXY(X3,1);
AddXY(X3,1);
AddXY(X4,1);
end;
Chart1.Legend.Hide;
//Chart1.View3D:=False;
Chart1.Axes.Left.SetMinMax(0,2);
Chart1.Axes.Left.Increment:=0.5;
Chart1.Axes.Bottom.Increment:=DateTimeStep[dtOneHour];
end;

Draw a vertically centered multi-line string using WinAPI's "DrawText" function

I am trying to draw a word-wrapped string within centered both vertically and horizontally within a bitmap using WinAPI's DrawText function.
The problem is that if text is longer than the available space and "END ELLIPSIS" (...) is added to a cropped string, the reported drawing coordinates returned when using the "DT_CALCRECT" report the uncropped number of lines which messes with the vertical centering calculations.
I read many posts on this, and thought that "Delphi - Draw text multiline in the centre of a rect" may hold the answer, but it didn't (screenshot of the code output using the sample in the linked question http://zoomplayer.com/pix/font_vcenter.jpg). The author of the accepted answer suggested I create a new question so here it is.
For quick-reference, here is a slightly simplified (removing unrelated code) text rendering code from the linked accepted answer:
procedure DrawTextCentered(Canvas: TCanvas; const R: TRect; S: String);
var
DrawRect: TRect;
DrawFlags: Cardinal;
begin
DrawRect := R;
DrawFlags := DT_END_ELLIPSIS or DT_NOPREFIX or DT_WORDBREAK or
DT_EDITCONTROL or DT_CENTER;
DrawText(Canvas.Handle, PChar(S), -1, DrawRect, DrawFlags or DT_CALCRECT);
DrawRect.Right := R.Right;
if DrawRect.Bottom < R.Bottom then
OffsetRect(DrawRect, 0, (R.Bottom - DrawRect.Bottom) div 2)
else
DrawRect.Bottom := R.Bottom;
DrawTextEx(Canvas.Handle, PChar(S), -1, DrawRect, DrawFlags, nil);
end;
As you can see from the screenshot, the problem is after the initial call to DrawText with the "DT_CALCRECT" flag to measure the output height for later vertical centering, rendering the string "Trending in: Worldwide" returns a DrawRect.Bottom value representing 3 lines of text even though only 2 lines are drawn, breaking the vertical centering code.

GetAxisLabel always passes -1 for ValueIndex

I need to be able to format the X Axis labels on a TeeChart Standard 2012 chart. I’m handling the GetAxisLabel event, but the ValueIndex is always -1.
I found this bit of documentation:
Axis Labels are Values.
In this case, the Series parameter will be nil, and the ValueIndex will be -1.
Axis Labels are Series points.
The Series parameter will be a valid TChartSeries, and the ValueIndex will be the current Series point position.
The problem is that I can find no way to set the Axis Labels to series points.
Can someone help me out?
You need to set LabelStyle to be:
Chart1.Axes.Bottom.LabelStyle:=talPointValue;
or
Chart1.Axes.Bottom.LabelStyle:=talText;
in your chart. Then you'll be able to do something like this:
procedure TForm2.Chart1GetAxisLabel(Sender: TChartAxis; Series: TChartSeries;
ValueIndex: Integer; var LabelText: string);
begin
if ((Series <> nil) and (ValueIndex <> -1)) then
begin
LabelText:=FormatFloat('#.00', Series.XValue[ValueIndex]);
end;
end;
However, it's much easier to achieve what the method above does using AxisValuesFormat property:
Chart1.Axes.Bottom.AxisValuesFormat:='#.00';

Viewing Part of a figure

I made a simulation of 10000 times and want to view part of simulation between 5000-5200. I am able to view it with the code below, but the x-axis says 0-250. I want the x-axis to display the exact figure of 5000-5200. Also there seems to be a small gap at the end of the figure as the axis runs up to 250 for some reason. I just want to view the figure in for this set time with the x-axis showing the exact labels and without the gap at the end.
Thanks
N=10000;%Number of simulation
P=0.02;
Q = zeros(N,1); %current value of queue
X=zeros(N,1);%simulation data
Ci=0;
L=0.9;
Bu=zeros(N,1);
Bs=30;
Bd1=50;
Bd2=270;
Ti=0;
for Ti=2:N
U=rand(1);
a=log10(U);
b=log10(1-P);
c=(a/b);
d=1+c;
X(Ti)=round(d);
Ci=Ci+1;
if X(Ti)< (L)*(Bs)
Bu(Ti)=Bs;
else if X(Ti) < (L)*(Bs+Bd1)
Bu(Ti)=Bs+Bd1;
else
Bu(Ti)=Bs+Bd1+Bd2;
end
end
Ti=Ti+1;
end
plot(X(5000:5200,1),'r');
set (gca,'ylim',[0 400]);
hold on;
plot(Bu(5000:5200,1),'b');
set (gca,'ylim',[0 400]);
hold off
Plot expects two inputs, the first depicting the horizontal axis and the second depicting the vertical axis. When you do not supply two inputs, then it computes the length of the single input (in this case that length is 5200-5000 = 200), and it just uses 1 through that length (1:200 in this case) as if it is the values for the horizontal axis variable.
I think you want to issue the command:
plot(5000:5200, X(5000:5200,1), 'r')
Often Matlab will adjust plot axes for better default views, so it's probably showing the axis out to the index 250 just by virtue of some default plotting convention. You can similarly use set(gca, 'xlim', [5000 5200]) if you wish.

Resources