I have the next problem: I have rectangle with 10 lines and 10 columns. When I press on a square I have to draw the "X" letter. Well, when I press on the right half, or on bottom half of the square, the "X" is drawn in the next right square, respectively in the below square. What should I do so when I press wherever on a square, the "X" to be drawn on the respective square?
My code is:
private void panel2_MouseClick(object sender, MouseEventArgs e)
{
txtX.Text = Convert.ToString(e.X);
txtY.Text = Convert.ToString(e.Y);
var data = File.ReadAllLines(handleClinet.GetPath().Replace("Client","Server"));
if (e.X >= 20 && e.Y >= 20 && e.X <= 220 && e.Y <= 220)
{
var graph = (sender as Panel).CreateGraphics();
const int redInt = 255; //255 is example, give it what u know
const int blueInt = 255; //255 is example, give it what u know
const int greenInt = 255; //255 is example, give it what u know
var p = new Pen(Color.FromArgb(redInt, blueInt, greenInt));
var newEx = (int)Math.Round(
(e.X / (double)20), MidpointRounding.AwayFromZero) * 20;
var newEy = (int)Math.Round(
(e.Y / (double)20), MidpointRounding.AwayFromZero) * 20;
RectangleF rectF1 = new RectangleF(newEx, newEy, 20,20);
graph.DrawString("X", new System.Drawing.Font("Arial", 16), Brushes.Blue, rectF1);
}
}
And my rectangle is like:
Many thanks in advance!
It seems like the issue is where you are using your MidpointRounding.AwayFromZero
It seems to me that if the sum you have to calculate the position you want to draw the 'X' gives the result 6.6, you want to have it in position 6, not position 7, so realistically you would want to replace that line with:
int newEx = Math.Floor((e.X / (double)20)) * 20;
... And then do the same for the Y calculation too
This should ensure that whatever the result it gives, it should not jump to the next box.
Related
I have a working mouse click event on my windows form graph and now I'd like to add data points on each click to make it visible where on the graph it was clicked. Upon the 3rd click, the previous 2 will clear and the 3rd and 4th click will have their own new data points and so on and so on (2 data points at a time to show start and stop locations and the difference/delta is calculated between those to positions).
My current code looks like:
private void chart1_MouseClick(object sender, MouseEventArgs e)
{
HitTestResult result = chart1.HitTest(e.X, e.Y);
if (result.PointIndex >= 0)
{
if (diffCounter == 0)
{
xOne = result.Series.Points[result.PointIndex].YValues[0];
diffCounter++;
//Console.WriteLine("VALY " + xOne);
}
else if (diffCounter == 1)
{
xTwo = result.Series.Points[result.PointIndex].YValues[0];
diffCounter = 0;
//Console.WriteLine("Delta = " + Math.Round(Math.Abs(xTwo - xOne)), 2);
pointDifferenceTextBox.Text = Math.Round((Math.Abs(xTwo - xOne)), 2).ToString();
}
}
}
I cannot find anything anywhere about adding a data point based on where a hit test was performed on a line chart (or any chart for that matter).
Difference Counter is just an int to determine whether its the first or second click.
xOne is to get the first click y-value, xTwo is to get the second click y-value.
EDIT: I'd like to had a circle data point based on where the hit test is performed on.
Since the post was changed a new answer seems warranted.
Here is how one can create two points to be drawn in a Paint event.
First we need to store them:
PointF p1 = PointNull;
PointF p2 = PointNull;
To flag state we also use a static value:
static PointF PointNull = new PointF(-123f, -123f);
You could use some other flag as well in order to control switching between the 1st and 2nd point.
Next we need to store values in the click :
private void chart1_MouseClick(object sender, MouseEventArgs e)
{
Axis ax = chart1.ChartAreas[0].AxisX;
Axis ay = chart1.ChartAreas[0].AxisY;
double x = ax.PixelPositionToValue(e.X);
double y = ay.PixelPositionToValue(e.Y);
y = GetMedianYValue(chart1.Series[0], x);
if (p1 == PointNull ||(p1 != PointNull && p2 != PointNull))
{
p1 = new PointF((float)x, (float)y);
p2 = PointNull;
}
else
{
p2 = new PointF((float)x, (float)y);
}
// values have changed, trigger drawing them!
chart1.Invalidate();
}
Note that I first use the axis functions to get the axis values of the clicked position. Then I overwrite the y-value by a function that calculates the point on the line..:
double GetMedianYValue(Series s, double xval )
{
// Findclosest datapoints:
DataPoint dp1 = s.Points.Where(x => x.XValue <= xval).LastOrDefault();
DataPoint dp2 = s.Points.Where(x => x.XValue >= xval).FirstOrDefault();
// optional
dp1.MarkerStyle = MarkerStyle.Circle;
dp1.MarkerColor = Color.Purple;
dp2.MarkerStyle = MarkerStyle.Circle;
dp2.MarkerColor = Color.Violet;
double dx = dp2.XValue - dp1.XValue;
double dy = dp2.YValues[0] - dp1.YValues[0];
// same point
if (dx == 0) return dp1.YValues[0];
// calculate median
double d = dp1.YValues[0] + dy / dx * ( xval - dp1.XValue) ;
return d;
}
Note that this function marks the neighbouring datapoints for testing only!
Finally we need to draw the two points:
private void chart1_PostPaint(object sender, ChartPaintEventArgs e)
{
Axis ax = chart1.ChartAreas[0].AxisX;
Axis ay = chart1.ChartAreas[0].AxisY;
int x1 = (int)ax.ValueToPixelPosition(p1.X);
int y1 = (int)ay.ValueToPixelPosition(p1.Y);
int x2 = (int)ax.ValueToPixelPosition(p2.X);
int y2 = (int)ay.ValueToPixelPosition(p2.Y);
if (x1 >= 0 && x1 < chart1.Width) // sanity check
if (p1 != PointNull)
e.ChartGraphics.Graphics.DrawEllipse(Pens.LightSeaGreen, x1 - 3, y1 - 3, 6, 6);
if (x2 >= 0 && x2 < chart1.Width) // sanity check
if (p2 != PointNull)
e.ChartGraphics.Graphics.DrawEllipse(Pens.Red, x2 - 3, y2 - 3, 6, 6);
}
Here is the result:
The original post asked for adding a DataPoint at the clicked location. For this HitTest is not useful.
Instead you need one of the the axis functions; PixelPositionToValue will convert the pixels position to an axis value..:
Axis ax = chart1.ChartAreas[0].AxisX;
Axis ay = chart1.ChartAreas[0].AxisY;
double x = ax.PixelPositionToValue(e.X);
double y = ay.PixelPositionToValue(e.Y);
DataPoint dp = new DataPoint(x, y);
dp.Color = Color.Red;
chart1.Series[0].Points.Add(dp);
Note that these function are only valid in either one of the paint or one of the mouse events!
I am creating One Cross Platform Application in Xamarin Forms and try to draw lines from 10 to -10 using below code. But the problem is lines are drawn from 10 to 0 only. Why this is happening I don't have any Idea.
int margin = 20;
int steps = 20;
float start = margin;
float end = width - margin;
float dHeigth = heigth - (margin * 4);
float hStep = dHeigth / Convert.ToSingle(steps);
float textMargin = 30;
// draw the line
for (int i = 10; i >= -10; i--)
{
float xpoint = i * hStep + margin;
if (i.IsOdd())
{
canvas.DrawLine(start + textMargin, xpoint, end, xpoint, LineWhitePaint);
}
else
{
decimal dText = 0;
canvas.DrawLine(start + textMargin, xpoint, end, xpoint, LineGreyPaint);
if (i < 0)
dText = i;
else
dText = (10 - i);
string txt = dText.ToString();
canvas.DrawText(txt, start + margin, xpoint + 15, TextStyleFillPaintX);
}
}
I am attaching screen shot of that
For the positive lines, you are drawing 10 - i, which yields 0 for the first iteration, 2 for the third and so on. Regarding this, you can see, that you are beginning to draw the lines from the middle of the canvas. The tenth iteration will draw the topmost line (the one with the 10). Further lines are drawn, but not on the screen.
You can see this, too, when you are writing xPoint to the debug output. As i gets negative, xPoint will, too. To fix this, you'll have to offset xPoint to always draw on screen
float xpoint = i * hStep + margin + steps / 2 * hStep;
Alternatively, you could loop from 20 to 0 and change how the text is generated.
for (int i = 20; i >= 0; i--)
{
var xPoint = i * hStep + margin;
// ...
var displayedText = GetDisplayedText(i, steps);
// ...
}
string GetDisplayedText(int i, int steps)
{
var displayedValue = i > steps / 2
? steps - i
: -i - steps / 2; // I think this should be the formula
return displayedValue.ToString();
}
Remarks: It would even better to encapsulate the concept of the lines, to separate their calculation from draawing them. You could create a factory that generates the correct line based on the index and the number of steps and then only iterate over the Line objects, and draw them by passing the canvas. This would make your code way cleaner and neater.
UPDATE
Since we have been able to clarify the requirements, I will give another shot.
First of all, I'd define methods to transform graph coordinates to canvas coordinates
private SKPoint ToCanvasCoordinates(SKPoint graphCoordinates)
{
var x = Margin + TextMargin + (_canvas.Width - 2*Margin - TextMargin)*graphCoordinates.X;
var y = (MaxY - graphCoordinates.Y)*(_canvas.Height - 2 * Margin)/(MaxY - MinY) + Margin;
return new SKPoint(x,y);
}
private SKPoint GetLegendCoordinates(int i)
{
var x = Margin;
var y = (MaxY - graphCoordinates.Y)*(_canvas.Height - 2 * Margin)/(MaxY - MinY) + Margin + 15;
return new SKPoint(x,y);
}
_canvas is a private member field in this case, Margin, MaxY and MinY are properties. I've assumed the min of x being 0 and the max bein 1.
Now you can draw your lines like
for(int i = -1; i <= 10; i++)
{
var lineStart = ToCanvasCoordinates(new SKPoint(0, i));
var lineEnd = ToCanvasCoordinates(new SKPoint(1, i));
canvas.DrawLine(lineStart, lineEnd, LineGreyPaint);
var textPosition = GetLegendCoordinates(i);
canvas.DrawText(i.ToString(), textPosition, TextStyleFillPaintX);
}
Furthermore, if you'd like to draw a line between two of the grid lines, you can use the following methods
private void DrawDataLine(SKPoint start, SKPoint end, SKPaint paint)
{
var startTransformed = ToCanvasCoordinates(start);
var endTransformed = ToCanvasCoordinates(end);
_canvas.DrawLine(startTransformed, endTransformed, paint);
}
private void DrawData(SKPaint paint)
{
for(int i=1; i<_data.Length; i++)
{
DrawDataLine(new SKPoint(data[i-1].X, data[i-1].Y), new SKPoint(data[i].X, data[i].Y)); // given that the objects in _data have the properties X and Y
}
}
I am developing a C# win form GUI for controlling a two-motor XY stage. I have drawn a 100 x 100 square grid pattern on a picturebox in which each square, when clicked, represents a coordinate that the two motors must move to. I have studied this link
PictureBox Grid and selecting individual cells when clicked on and this PictureBox- Grids and Filling in squares (Game of Life) for drawing a grid and marking the clicked positions.
Now I have to transform the series of randomly clicked points to actual movement of the two motors.
How shall I translate the click coordinates programmatically to give commands to control the motors?
I know how to move and control the motors without referring to the screen coordinates, i.e. by using eyes.
Thank you very much for your kind help.
Update1:
Hello... I think I am thinking too much in a confusing way to move the motors from one point to another despite Sebastien's great help. I wanted to try some logic below but I appreciate if somebody can enlighten me how best to implement this.
private void pictureBoxGrid_MouseClick(object sender, MouseEventArgs e)
{
//int x = e.X;
int x = cellSize * (e.X / cellSize);
int y = cellSize * (e.Y / cellSize);
int i = x / 8; // To limit the value to below 100
int j = y / 8;
// Reverse the value of fill_in[i, j] - if it was false, change to true,
// and if true change to false
fill_in[i, j] = !fill_in[i, j];
if (fill_in[i, j])
{
//Save the coordinate in a list
filledSq.Add(new Point(i, j));
using (Graphics g = Graphics.FromImage(buffer))
{
g.FillRectangle(Brushes.Black, x + 1, y + 1, 7, 7);
}
}
else
{
//Delete the coordinate in a list
filledSq.Remove(new Point(i, j));
Color customColor = SystemColors.ControlLightLight;
using (Graphics g = Graphics.FromImage(buffer))
using (SolidBrush shadowBrush = new SolidBrush(customColor))
{
g.FillRectangle(shadowBrush, x + 1, y + 1, 7, 7);
}
}
//pictureBoxGrid.BackgroundImage = buffer;
pictureBoxGrid.Invalidate();
}
private void buttonSavePoints_Click(object sender, EventArgs e)
{
// to be implemented...
}
private void buttonRun_Click(object sender, EventArgs e)
{
var noOfDots = filledSq.Count;
filledSq = filledSq.OrderBy(p => p.X).ThenBy(p => p.Y).ToList();
var motor = new Motor();
for (var i = 0; i < noOfDots; i++)
{
motor.Move(filledSq[i].X, filledSq[i].Y); //call the motor to move to X,Y here?
//do sth at each position
}
}
Since you wrote, that you know how to move the motors programmatically this answer will be more theoretical:
Each steppermotor has a predefined anglewidth per step (e.g. 1.8°).
And if you know where your Motors are (for example at a predefined starting point with limitswitches (0|0)) you can calculate where they need to be.
For the precision there are multiple factors like if you are using Belts or threaded rods.
An examplemethod could look like this:
private static float stepwidth = 1.8;
private static beltConverionPerDegree = 0.2; // Or rod
private float currentPositionX = 0;
private float currentPositionY = 0;
public Tuple<int, int> GetSteps(float x, float y) {
// calculate the position relative to the actual position (Vector between two points)
float relativeX = x - currentPositionX;
float relativeY = y - currentPositionY;
return new Tuple<int, int> (relativeX / (stepwidth * beltConverionPerDegree), relativeY / (stepwidth * beltConverionPerDegree));
}
beltConverionPerDegree means how much distance your motor moves the belt for each degree.
I have a user control and I'm using ScaleTransform() to implement zoom.
However, in order to keep the center content in the center after the zoom, it is also necessary to scroll. For example, if I zoom in (make things bigger), the X and Y origin should increase so that most of the content does not move down and to the right. (That is, as I zoom in, some of the content should disappear to the left and top.)
Has anyone worked out the calculations of how much to scroll in the X and Y direction in response to a zoom?
For example:
e.Graphics.ScaleTransform(2.0F, 2.0F);
e.Graphics.TranslateTransform(?, ?);
What would be my arguments to TranslateTransform() be so that the center part of the content remains at the center?
Note: I am not displaying an image. I am drawing the graphic content to the surface of my user control.
Or perhaps there's an even easier way?
This should work and I can't imagine any easier way; it assumes you have decided on the center of the zooming. I have chosen to draw centered on the panel:
float zoom = 1f;
private void drawPanel1_Paint(object sender, PaintEventArgs e)
{
Point c = new Point(drawPanel1.ClientSize.Width / 2, drawPanel1.ClientSize.Height / 2);
// a blue sanity check for testing
e.Graphics.FillEllipse(Brushes.DodgerBlue, c.X - 3, c.Y - 3, 6, 6);
// the offsets you were looking for:
float ox = c.X * ( zoom - 1f);
float oy = c.Y * ( zoom - 1f);
// first move and then scale
e.Graphics.TranslateTransform(-ox, -oy);
e.Graphics.ScaleTransform(zoom, zoom);
// now we can draw centered around our point c
Size sz = new Size(300, 400);
int count = 10;
int wx = sz.Width / count;
int wy = sz.Height / count;
for (int i = 0; i < count; i++)
{
Rectangle r = new Rectangle(c.X - i * wx / 2 , c.Y - i * wy / 2, i * wx, i * wy );
e.Graphics.DrawRectangle(Pens.Red, r );
}
}
Note the order of moving and scaling!
I guess you are using some differet interface, but in my case, that's what got the job done (for leaving the mouse in it's original location on the draw after the mouse wheel event):
private void DrawPb_MouseWheel(object sender, MouseEventArgs e)
{
// e contains current mouse location and the wheel direction
int wheelDirection = e.Delta / Math.Abs(e.Delta); // is 'in' or 'out' (1 or -1).
double factor = Math.Exp(wheelDirection * Constants.ZoomFactor); // divide or multiply
double newX = e.X - e.X / factor; // what used to be x is now newX
double newY = e.Y - e.Y / factor; // same for y
Point offset = new Point((int)(-newX), (int)(-newY)); // the offset of the old point to it's new location
Graph.AddOffset(offset); // apply offset
}
I'm trying to use the position of the mouse to calculate the scaling factor for scaling an image. Basically, the further you get away from the center of the image, the bigger it gets; and the closer to the center you get, the smaller it gets. I have some code so far but it's acting really strange and I have absolutely no more ideas. First I'll let you know, one thing I was trying to do is average out 5 distances to get a more smooth resize animation. Here's my code:
private void pictureBoxScale_MouseMove(object sender, MouseEventArgs e)
{
if (rotateScaleMode && isDraggingToScale)
{
// For Scaling
int sourceWidth = pictureBox1.Image.Width;
int sourceHeight = pictureBox1.Image.Height;
float dCurrCent = 0; // distance between the current mouse pos and the center of the image
float dPrevCent = 0; // distance between the previous mouse pos and the center of the image
System.Drawing.Point imgCenter = new System.Drawing.Point();
imgCenter.X = pictureBox1.Location.X + (sourceWidth / 2);
imgCenter.Y = pictureBox1.Location.Y + (sourceHeight / 2);
// Calculating the distance between the current mouse location and the center of the image
dCurrCent = (float)Math.Sqrt(Math.Pow(e.X - imgCenter.X, 2) + Math.Pow(e.Y - imgCenter.Y, 2));
// Calculating the distance between the previous mouse location and the center of the image
dPrevCent = (float)Math.Sqrt(Math.Pow(prevMouseLoc.X - imgCenter.X, 2) + Math.Pow(prevMouseLoc.Y - imgCenter.Y, 2));
if (smoothScaleCount < 5)
{
dCurrCentSmooth[smoothScaleCount] = dCurrCent;
dPrevCentSmooth[smoothScaleCount] = dPrevCent;
}
if (smoothScaleCount == 4)
{
float currCentSum = 0;
float prevCentSum = 0;
for (int i = 0; i < 4; i++)
{
currCentSum += dCurrCentSmooth[i];
}
for (int i = 0; i < 4; i++)
{
prevCentSum += dPrevCentSmooth[i];
}
float scaleAvg = (currCentSum / 5) / (prevCentSum / 5);
int destWidth = (int)(sourceWidth * scaleAvg);
int destHeight = (int)(sourceHeight * scaleAvg);
// If statement is for limiting the size of the image
if (destWidth > (currentRotatedImage.Width / 2) && destWidth < (currentRotatedImage.Width * 3) && destHeight > (currentRotatedImage.Height / 2) && destWidth < (currentRotatedImage.Width * 3))
{
AForge.Imaging.Filters.ResizeBilinear resizeFilter = new AForge.Imaging.Filters.ResizeBilinear(destWidth, destHeight);
pictureBox1.Image = resizeFilter.Apply((Bitmap)currentRotatedImage);
pictureBox1.Size = pictureBox1.Image.Size;
pictureBox1.Refresh();
}
smoothScaleCount = -1;
}
prevMouseLoc = e.Location;
currentScaledImage = pictureBox1.Image;
smoothScaleCount++;
}
}
EDIT: Thanks to Ben Voigt and Ray everything works well now. The only thing wrong is that with the way I'm doing it the image doesn't keep it's ratio; but I'll fix that later. Here's what I have for those who want to know:
private void pictureBoxScale_MouseMove(object sender, MouseEventArgs e)
{
if (rotateScaleMode && isDraggingToScale)
{
// For Scaling
int sourceWidth = pictureBox1.Image.Width;
int sourceHeight = pictureBox1.Image.Height;
int scale = e.X + p0.X; //p0 is the location of the mouse when the button first came down
int destWidth = (int)(sourceWidth + (scale/10)); //I divide it by 10 to make it slower
int destHeight = (int)(sourceHeight + (scale/10));
if (destWidth > 20 && destWidth < 1000 && destHeight > 20 && destWidth < 1000)
{
AForge.Imaging.Filters.ResizeBilinear resizeFilter = new AForge.Imaging.Filters.ResizeBilinear(destWidth, destHeight);
pictureBox1.Image = resizeFilter.Apply((Bitmap)currentRotatedImage);
pictureBox1.Size = pictureBox1.Image.Size;
pictureBox1.Refresh();
}
currentScaledImage = pictureBox1.Image; // This is only so I can rotate the scaled image in another part of my program
}
}
You're scaling won't be smooth if you use the center of the image. Instead, use the initial mouse down point (call it p0). Also, rather than using the distance from that point to the current drag point (e), just take the difference along one axis (e.g. exp(e.Y - p0.Y)).
It looks to me (from the scaleAvg calculation) like you're rescaling the already-scaled image. This is a really bad idea because scaling is lossy and the errors will accumulate. Instead, keep a copy of the crisp original image and scale the original directly to the current size.
Also, I would suggest using a different norm, perhaps Manhattan distance, instead of the current Cartesian distance which is a two-norm.
If you do continue using the two-norm, consider getting rid of the Math.Pow calls. They are probably such a small part of the overall scaling complexity that it doesn't matter, but multiplying by itself should be much faster than Math.Pow for squaring a number.