Use of GetValueAtPosition with WPF ToolKit Chart - c#

I'm trying to get the X-coordinate position of the mouse on a Chart. Looking at some other code on this site, I have
private void Chart_MouseMove(object sender, MouseEventArgs e)
{
var chart = (Chart)sender;
var xAxisRange = (IRangeAxis)chart.Axes[0];
var mousePositionInPixels = e.GetPosition(chart);
double mouseXPositionInChartUnits = (double)xAxisRange.GetValueAtPosition(new UnitValue(mousePositionInPixels.X, Unit.Pixels));
TextBoxR0.Text = mouseXPositionInChartUnits.ToString(); //temp display textBox for debugging
RaisePropertyChanged();
}
The problem is that the values are shifted to the left by a constant. The X-value for the origin is 0, but GetValueAtPosition is returning, say 15, and all x values returned are 15 greater than they should be. If I expand the chart horizontally, the offset changes, becoming smaller. If I contract the chart horizontally, the offset becomes larger. I've done some extensive searching for an answer, and have looked for some member of Chart that may give me a left margin offset, but with no success.

I know this answer is late, but I had the same problem. My solution was to get the mouse position relative to the series instead of the chart. Its not perfect (it’s off by a pixel or 2 at a resolution of 1366 x 720), but when rounded to 2 places it’s good enough for me.
In my xaml I gave the series a name (“Series”, as unoriginal as that is). I also have a TextBlock that's bound to the string Pos, so I can see the position of the cursor. I've bound the ItemsSource of the series to List<KeyValuePair<Double, Double>> Points
Lastly, don’t forget that the y axis is flipped relative to screen coordinates (the y axis increases as you move up, but screen coordinates increase as you move down), so that’s why there's a 1.0 – yAxisRange in there.
Xaml:
<chartingToolkit:Chart Name="columnChart" MouseMove="Chart_MouseMove">
<chartingToolkit:LineSeries Name="Series" ItemsSource="{Binding Points}" DependentValuePath="Value" IndependentValuePath="Key" />
</chartingToolkit:Chart>
Code:
private void Chart_MouseMove(object sender, MouseEventArgs e)
{
var chart = (Chart)sender;
var mousePositionInPixels = e.GetPosition(Series);
var xAxisRange = (IRangeAxis)chart.ActualAxes[0];
double mouseXPositionInChartUnits = (double)xAxisRange.GetValueAtPosition(new UnitValue(mousePositionInPixels.X, Unit.Pixels));
var yAxisRange = (IRangeAxis)chart.ActualAxes[1];
double mouseYPositionInChartUnits = 1.0 - (double)yAxisRange.GetValueAtPosition(new UnitValue(mousePositionInPixels.Y, Unit.Pixels));
Pos = $"({Math.Round(mouseXPositionInChartUnits, 2)}, {Math.Round(mouseYPositionInChartUnits, 2)})";
}

Related

How to get mouse coordinates between two points in a picture box

Can anyone help me on this?
I have a picture box with a image and this image have some coordinates.
My X starts at 60 and end at 135
My Y stats at 75 and end at 120
Because i have only the first and the last point, i want to calculate and see the coordinates when i mouse over my image.
I started with solving my first problem: i have to delimitate my start and my end.
So i tried a trackbar.
Im trying first get the current X position:
Set my picturebox at position x = 0;
Set my trackbar at position x = -10, so my first pin will be at position 0;
Set my tracbar size.x = picturebox.x + 20, so my last pin will be at end of picture box.
My trackbar have the current properties:
Minimum = 60, Maximum = 135;
Set a mouse move event in my picturebox:
private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
double dblValue;
dblValue = ((double)e.X/ (double)trackBar1.Width) * (trackBar1.Maximum - trackBar1.Minimum);
dblValue = dblValue + 60;
trackBar1.Value = Convert.ToInt32(dblValue);
lblX.Text = dblValue.ToString();
}
It's almost working, but still not very accurate.
Anyone have some idea that may work?
I'm not exactly sure what it is you are trying to do, but if you are trying to get to the coordinates within the picturebox, there is a function on the pictureBox class called PointToClient(Point) that computes the location of the specified screen point into client coordinates. You can use the X and Y coordinates from the MouseEventArgs to create the Point object to pass to the function.
To clarify:
The X and Y properties of the MouseEventArgs in the MouseMove event are the screen coordinates (0,0) being the upper left corner of the screen.
Many controls like the PictureBox control include a PointToClient method that will convert the screen coordinates to the local control's coordinates, where (0,0) will be the upper left corner of the control.
So, for example, if your control is placed on the screen at location (60, 75) and has a bottom right coordinate of (135, 120). If your mouse is over the control, and is 10 pixels from the left and 20 pixels from the top, then the X and Y properties of the MouseEventArgs in the MouseMove event would be: X = 70 and Y = 95. If you convert these to the internal coordinates of the picturebox control using PointToClient, it will indicate that X = 10 and Y = 20.
Now, if you were wanting to have a TrackBar that displays a relative indication of where the X coordinate of the mouse is over some control, you would calculate it as follows:
// Set the minimum and maximum of the trackbar to 0 and 100 for a simple percentage.
trackBar1.Minimum = 0;
trackBar1.Maximum = 100;
// In the pictureBox1_MouseMove event have the following code:
trackBar1.Value = pictureBox1.PointToClient(new Point(e.X, e.Y)).X * 100 / pictureBox1.Width;
If you wanted to have the trackbar use screen coordinates to track the relative position of the X coordinate of the mouse over some control, you would calculate it as follows:
// Set the minimum and maximum values of the track bar to the screen coordinates of the
// control we want to track.
trackBar1.Minimum = pictureBox1.PointToScreen(0,0).X;
trackBar1.Maximum = pictureBox1.PointToScreen(pictureBox1.Width, 0).X;
// In the pictureBox1_MouseMove event have the following code:
trackBar1.Value = e.X;
If you wanted to have the trackbar use the internal coordinates of some control to track the internal position of the X coordinate of the mouse over that control, you would calculate it as follows:
// Set the minimum and maximum values of the track bar to zero and the width of the
// control we want to track.
trackBar1.Minimum = 0;
trackBar1.Maximum = pictureBox1.Width;
// In the pictureBox1_MouseMove event have the following code:
trackBar1.Value = pictureBox1.PointToClient(new Point(e.X, e.Y)).X;
// or - not recommended - read below.
trackBar1.Value = e.X - pictureBox1.Left;
Now, there is one caveat, and that is if you put controls inside other controls, like a panel inside a panel inside a panel, etc. then the 'world' coordinates of a control inside of another control are based upon their location within the parent control. That is why it is a good idea to use the internal coordinates of the control via PointToClient and screen coordinates from internal coordinates via PointToScreen because otherwise you would have to work your way up through all of the containers until you reached the screen, keeping track of Top and Left coordinates all the way.
I hope this answers your question.

WPF Path's Geometry Transform performance gets worse with LineGeometries' length increase

I'm currently trying to create a little plot interactive editor, using WPF.
On maximized window the plot dragging with mouse is not responsive enough because of the plot grid.
I got a path for my plot grid lying inside a Canvas control (render transform just shifts it to the bottom of the canvas)
<Path Name="VisualGrid" RenderTransform="{StaticResource PlotTechnicalAdjust}" Style="{DynamicResource ResourceKey=GridStyle}" Panel.ZIndex="1"/>
Here is how grid is created; _curState has actual camera "viewport" metadata
if (_curState.Changes.ScaleStepXChanged)
{
foreach (TextBlock item in _xLabels)
{
DeleteLabel(item);
}
_xLabels.Clear();
double i = _curState.LeftEdgeLine;
_gridGeom.Children[(int)GridGeomIndexes.VerticalLines] = new GeometryGroup { Transform = _verticalLinesShift};
var verticalLines =(GeometryGroup)_gridGeom.Children[(int)GridGeomIndexes.VerticalLines];
while (i <= _curState.RightEdgeLine * (1.001))
{
verticalLines.Children.Add(new LineGeometry(new Point(i * _plotParameters.PixelsPerOneX, 0),
new Point(i * _plotParameters.PixelsPerOneX,
-_wnd.ContainerGeneral.Height)));
_xLabels.Add(CreateLabel(i, Axis.X));
i += _curState.CurrentScaleStepX;
}
_curState.Changes.ScaleStepXChanged = false;
}
if (_curState.Changes.ScaleStepYChanged)
{
foreach (TextBlock item in _yLabels)
{
DeleteLabel(item);
}
_yLabels.Clear();
double i = _curState.BottomEdgeLine;
_gridGeom.Children[(int)GridGeomIndexes.HorizontalLines] = new GeometryGroup { Transform = _horizontalLinesShift};
var horizontalLines = (GeometryGroup)_gridGeom.Children[(int)GridGeomIndexes.HorizontalLines];
while (i <= _curState.TopEdgeLine * (1.001))
{
horizontalLines.Children.Add(new LineGeometry(new Point(0, -i * _plotParameters.PixelsPerOneY),
new Point(_wnd.ContainerGeneral.Width,
-i * _plotParameters.PixelsPerOneY)));
_yLabels.Add(CreateLabel(i, Axis.Y));
i += _curState.CurrentScaleStepY;
}
_curState.Changes.ScaleStepYChanged = false;
}
Where Transforms are composition of TranslateTransform and ScaleTransform (for vertical lines I only use X components and only Y for horizontal lines).
After beeing created those GeometryGroups are only edited if a new line apears into camera or an existing line exits viewable space. Grid is only recreated when axis graduations have to be changed after zooming.
I have a dragging option implemented like this:
private Point _cursorOldPos = new Point();
private void OnDragPlotMouseMove(object sender, MouseEventArgs e)
{
if (e.Handled)
return;
Point cursorNewPos = e.GetPosition(ContainerGeneral);
_plotView.TranslateShiftX.X += cursorNewPos.X - _cursorOldPos.X;
_plotView.TranslateShiftY.Y += cursorNewPos.Y - _cursorOldPos.Y;
_cursorOldPos = cursorNewPos;
e.Handled = true;
}
This works perfectly smooth with a small window (1200x400 units) for a large amount of points (like 100+).
But for a large window (fullscreen 1920x1080) it happens pretty jittery even without any data-point controls on canvas.
The strange moment is that lags don't appear when I order my GridGenerator to keep around 100+ lines for small window and drag performance suffers when I got less than 50 lines on maximezed. It makes me think that it might somehow depend not on a number of elements inside a geometry, but on their linear size.
I suppose I should mention that OnSizeChanged I adjust the ContainerGeneral canvas' height and width and simply re-create the grid.
Checked the number of lines stored in runtime to make sure I don't have any extras. Tried using Image with DrawingVisual instead of Path. Nothing helped.
Appearances for clearer understanding
It was all about stroke dashes and WPF's unhealthy desire to count them all while getting hit test bounds for DrawingContext.
The related topic is Why does use of pens with dash patterns cause huge (!) performance degredation in WPF custom 2D drawing?

How to do aligned scrolling through two chart areas without using AlignToChartArea?

I have two ChartArea objects in a Chart (System.Windows.Forms.DataVisualization.Charting is what I'm using).
One is a Point graph, and the other is a RangeBar graph. The horizontal axis on the RangeBar graph is actually the Y axis, so I cannot just use something like this:
Chart1.ChartAreas["Chart Area 2"].AlignWithChartArea = "Default";
I've figured out how to zoom both charts and keep them aligned, but when I try to scroll both charts by clicking on the scrollbar on one of the horizontal axes, I can't quite get it to line up. They almost line up, but they're off by maybe a second or so (the horizontal axis in both graphs is time).
Here's what I have:
private void theChart_AxisViewChanged(object sender, ViewEventArgs e)
{
if (e.ChartArea == theChart.ChartAreas["MyPointChartArea"])
{
theChart.ChartAreas["MyRangeBarChartArea"].AxisY.ScaleView.Position = e.NewPosition;
theChart.ChartAreas["MyRangeBarChartArea"].AxisY.ScaleView.Size = e.NewSize;
theChart.ChartAreas["MyRangeBarChartArea"].AxisY.ScaleView.SizeType = e.NewSizeType;
}
if (e.ChartArea == theChart.ChartAreas["MyRangeBarChartArea"])
{
theChart.ChartAreas["MyPointChartArea"].AxisX.ScaleView.Position = e.NewPosition;
theChart.ChartAreas["MyPointChartArea"].AxisX.ScaleView.Size = e.NewSize;
theChart.ChartAreas["MyPointChartArea"].AxisX.ScaleView.SizeType = e.NewSizeType;
}
}
What else do I need to do to get the charts to line up? The physical extent of the charts is the same. It's just the data that are slightly misaligned.
Thanks for any help.

Transforming coordinates from an image control to the image source in WPF

I'm trying to learn WPF, so here's a simple question, I hope:
I have a window that contains an Image element bound to a separate data object with user-configurable Stretch property
<Image Name="imageCtrl" Source="{Binding MyImage}" Stretch="{Binding ImageStretch}" />
When the user moves the mouse over the image, I would like to determine the coordinates of the mouse with respect to the original image (before stretching/cropping that occurs when it is displayed in the control), and then do something with those coordinates (update the image).
I know I can add an event-handler to the MouseMove event over the Image control, but I'm not sure how best to transform the coordinates:
void imageCtrl_MouseMove(object sender, MouseEventArgs e)
{
Point locationInControl = e.GetPosition(imageCtrl);
Point locationInImage = ???
updateImage(locationInImage);
}
Now I know I could compare the size of Source to the ActualSize of the control, and then switch on imageCtrl.Stretch to compute the scalars and offsets on X and Y, and do the transform myself. But WPF has all the information already, and this seems like functionality that might be built-in to the WPF libraries somewhere. So I'm wondering: is there a short and sweet solution? Or do I need to write this myself?
EDIT I'm appending my current, not-so-short-and-sweet solution. Its not that bad, but I'd be somewhat suprised if WPF didn't provide this functionality automatically:
Point ImgControlCoordsToPixelCoords(Point locInCtrl,
double imgCtrlActualWidth, double imgCtrlActualHeight)
{
if (ImageStretch == Stretch.None)
return locInCtrl;
Size renderSize = new Size(imgCtrlActualWidth, imgCtrlActualHeight);
Size sourceSize = bitmap.Size;
double xZoom = renderSize.Width / sourceSize.Width;
double yZoom = renderSize.Height / sourceSize.Height;
if (ImageStretch == Stretch.Fill)
return new Point(locInCtrl.X / xZoom, locInCtrl.Y / yZoom);
double zoom;
if (ImageStretch == Stretch.Uniform)
zoom = Math.Min(xZoom, yZoom);
else // (imageCtrl.Stretch == Stretch.UniformToFill)
zoom = Math.Max(xZoom, yZoom);
return new Point(locInCtrl.X / zoom, locInCtrl.Y / zoom);
}
It would probably be easier if you used a ViewBox. For example:
<Viewbox Stretch="{Binding ImageStretch}">
<Image Name="imageCtrl" Source="{Binding MyImage}" Stretch="None"/>
</Viewbox>
Then when you go and call GetPosition(..) WPF will automatically account for the scaling.
void imageCtrl_MouseMove(object sender, MouseEventArgs e)
{
Point locationInControl = e.GetPosition(imageCtrl);
}

How to get real image pixel point x,y from PictureBox

I have a pictureBox2 and it is set to zoom, I am trying to find out how to to get a real x,y pixel location on the image by Mouse.Click on pictureBox2. but I tried 3 possible ideas I knew of: without/with PointToClient,PointToScreen but I can never get it right.
private void pictureBox2_Click(object sender, EventArgs e)
{
MouseEventArgs me = (MouseEventArgs)e;
txtpictureHeight.Text =(
(OriginalImage.GetImageHeight()*me.Location.Y)/ pictureBox2.Image.Height).ToString();
txtpictureWidth.Text = (
(OriginalImage.GetImageWidth()* me.Location.X)/ pictureBox2.Image.Width).ToString();
}
There must be some factor I need to take care of so I thought to use double result from above and I get closed but there is still 80px off for the height on my test image (1371x2221). As I use Zoom so there are 2 extra spaces on my pictureBox2
Note that with SizeMode set to Zoom, the PictureBox keeps aspect ratio, and centers the image, so on top of calculating the adjusted coordinates, you also have to take padding into account.
My advice, don't use the Click event; it is meant to detect button clicks, not to actually process interaction of the mouse with an object. Use MouseDown instead.
The first thing we need to do is get the width and height of the original image. As I noted in my comment, this is simply the object inside the Image property of the PictureBox.
Next, we need the dimensions and location of the zoomed image. For that, we can start from the dimensions of the ClientRectangle of the PictureBox. Divide those by the image width and height and you'll get the horizontal and vertical zoom values. If the SizeMode would be set to StretchImage, that'd be all we need, but since aspect ratio is conserved, you need the smallest of the two values to have the actual zoom factor.
Once we got that, multiply the original width and height by this zoom factor to get the zoomed width and height, then subtract that from the actual ClientRectangle dimensions and divide it by two to get the padding for both dimensions. This can of course be simplified by checking which of the two possible zoom factors is used, and only calculating the padding for the other one, since the dimension of which the zoom factor was used obviously has 0 padding.
Now you got the padding and zoom factor, the rest is simple: subtract the padding values from the mouse coordinates, and then divide both results by the zoom factor.
private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
{
// Default check: left mouse button only
if (e.Button == MouseButtons.Left)
ShowCoords(e.X, e.Y);
}
private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
// Allows dragging to also update the coords. Checking the button
// on a MouseMove is an easy way to detect click dragging.
if (e.Button == MouseButtons.Left)
ShowCoords(e.X, e.Y);
}
private void ShowCoords(Int32 mouseX, Int32 mouseY)
{
Int32 realW = pictureBox1.Image.Width;
Int32 realH = pictureBox1.Image.Height;
Int32 currentW = pictureBox1.ClientRectangle.Width;
Int32 currentH = pictureBox1.ClientRectangle.Height;
Double zoomW = (currentW / (Double)realW);
Double zoomH = (currentH / (Double)realH);
Double zoomActual = Math.Min(zoomW, zoomH);
Double padX = zoomActual == zoomW ? 0 : (currentW - (zoomActual * realW)) / 2;
Double padY = zoomActual == zoomH ? 0 : (currentH - (zoomActual * realH)) / 2;
Int32 realX = (Int32)((mouseX - padX) / zoomActual);
Int32 realY = (Int32)((mouseY - padY) / zoomActual);
lblPosXval.Text = realX < 0 || realX > realW ? "-" : realX.ToString();
lblPosYVal.Text = realY < 0 || realY > realH ? "-" : realY.ToString();
}
Note, I used sharp pixel zoom here to better show the effect. It's a little trick you can do by subclassing PictureBox and overriding its OnPaint method, to adjust the Graphics object from the PaintEventArgs object and set its InterpolationMode to NearestNeighbor (It's also advised to set PixelOffsetMode to Half; there's a bug where sharp zoom is shifted half a pixel unless you do that). Then you call base.OnPaint() with that adjusted event args object.
I also added some more info on it here, but that's all just stuff you can get from the in-between values of the pixel coordinates calculation process anyway.

Categories

Resources