My task is to draw a graph in chart control using mouse and retrieve the (X,Y) points from the Graph.
I tried of drawing a graph with mouse.
Here is the normal Graph looks like.
After drawing with mouse, it looks like :
The code which i used to draw graph is :
private void Form1_Load(object sender, EventArgs e)
{
chart1.ChartAreas[0].AxisX.Minimum =0170101;
chart1.ChartAreas[0].AxisX.Maximum =0175951;
chart1.ChartAreas[0].AxisY.Minimum=0780101;
chart1.ChartAreas[0].AxisY.Maximum=0785951;
double range = chart1.ChartAreas[0].AxisX.Maximum - chart1.ChartAreas[0].AxisX.Minimum;
chart1.ChartAreas[0].AxisX.Interval = range / 5;
range = chart1.ChartAreas[0].AxisY.Maximum - chart1.ChartAreas[0].AxisY.Minimum;
chart1.ChartAreas[0].AxisY.Interval = range / 5;
}
private void chart1_MouseMove(object sender, MouseEventArgs e)
{
if (!(FirstPoint == null))
{
Graphics g = chart1.CreateGraphics();
Pen ErasePen = new Pen(Color.Transparent);
g.DrawLine(ErasePen, FirstPoint, TempPoint);
TempPoint = new Point(e.X, e.Y);
this.Refresh();
}
}
private void chart1_MouseDown_1(object sender, MouseEventArgs e)
{
FirstPoint = new Point(e.X, e.Y);
TempPoint = new Point(e.X, e.Y);
}
private void chart1_MouseUp_1(object sender, MouseEventArgs e)
{
LineEndPoints Line = new LineEndPoints
{
StartPoint = FirstPoint,
endPont = new Point(e.X, e.Y)
};
LinesList.Add(Line);
// FirstPoint = null;
this.Refresh();
}
private void chart1_Paint_1(object sender, PaintEventArgs e)
{
foreach (LineEndPoints line in LinesList)
{
e.Graphics.DrawLine(Pens.Green, line.StartPoint, line.endPont);
}
if (!(FirstPoint == null))
{
e.Graphics.DrawLine(Pens.Red, FirstPoint, TempPoint);
}
}
When I used to draw a graph it is moving away from the max and min values of the chart control.
Now what I need to know is:
1) My graph should not move away from the X and Y axis points of the chart control.
2) I need to know the X,Y points of the graph which is drawn with respect to chart axis but not with form axis.
I use C# VS 2010 Win-forms.
Chart uses a different coordinate system for its content than its Control surface, ie the mouse loacation; there are conversion functions but they come with a caveat: They are only guaranteed to work in the Paint events..
PixelPositionToValue
ValueToPixelPosition
Here is an example that translates the pixel points to chart point values. You can see the two graphics overlaying very nicely: The DataPoints are connected in blue lines and the pixel points by dotted red lines..:
public Form1()
{
InitializeComponent();
chart1.Series[0].ChartType = SeriesChartType.Line;
chart1.ChartAreas[0].AxisX.Minimum = 0;
chart1.ChartAreas[0].AxisX.Maximum = 500;
chart1.ChartAreas[0].AxisY.Minimum = 0;
chart1.ChartAreas[0].AxisY.Maximum = 500;
}
List<Point> points = new List<Point>();
private void chart1_MouseClick(object sender, MouseEventArgs e)
{
points.Add(e.Location);
chart1.Invalidate();
}
private void chart1_Paint(object sender, PaintEventArgs e)
{
chart1.Series[0].Points.Clear();
foreach(Point pt in points)
{
double dx = chart1.ChartAreas[0].AxisX.PixelPositionToValue(pt.X);
double dy = chart1.ChartAreas[0].AxisY.PixelPositionToValue(pt.Y);
chart1.Series[0].Points.AddXY(dx, dy);
}
if (points.Count > 1)
using (Pen pen = new Pen(Color.Red, 2.5f))
e.Graphics.DrawLines(pen, points.ToArray());
}
Note that this will always clear the DataPoints and recreate them from the pixel points list, according to the current chart layout using the PixelPositionToValue method. The layout will always change when things like label sizes, other scaling, other minimum/maximum values etc change.
Maybe you really want to work the other way round, that is change the clicked points using the ValueToPixelPosition.
Here is the modified example that keeps the DataPoints and recalculates the pixel points:
List<Point> points = new List<Point>();
Point lastPoint = Point.Empty;
private void chart1_MouseClick(object sender, MouseEventArgs e)
{
lastPoint = e.Location;
chart1.Invalidate();
}
private void chart1_Paint(object sender, PaintEventArgs e)
{
// if we have a new point, convert to DataPoint and add to Series.Points:
if (lastPoint != Point.Empty)
{
double dx = chart1.ChartAreas[0].AxisX.PixelPositionToValue(lastPoint.X);
double dy = chart1.ChartAreas[0].AxisY.PixelPositionToValue(lastPoint.Y);
chart1.Series[0].Points.AddXY(dx, dy);
}
lastPoint = Point.Empty;
// now recalculate all pixel points:
points.Clear();
foreach (DataPoint pt in chart1.Series[0].Points)
{
double x = chart1.ChartAreas[0].AxisX.ValueToPixelPosition(pt.XValue);
double y = chart1.ChartAreas[0].AxisY.ValueToPixelPosition(pt.YValues[0]);
points.Add(new Point((int)x, (int)y));
}
if (points.Count > 1)
using (Pen pen = new Pen(Color.Red, 2.5f))
{
pen.DashStyle = System.Drawing.Drawing2D.DashStyle.Dot;
e.Graphics.DrawLines(pen, points.ToArray());
}
}
This makes a lot more sense, since the DataPoints are always bound to the chart's scaling, so they are the 'real thing'. When you resize the Chart the DataPoints and the Graphic they make up are scaled as usual and the drawn pixel points follow perfectly:
(When you resize the first version you can see how nothing is being scaled up or down and only the chart's grid lines change..)
Note that I set up a few things to start with, so that not every point I add enforces too many layout changes. Also note that sometimes there still occurs a feedback loop when the new points change e.g. the label sizes, which enforces a layout change and the paint loop.. To fix this you should probably control the labels' formats!
Also note that both conversion methods only work (correctly) in the Paint event(s), probably because only then the current layout is being settled.
Related
How to draw a line like the windows Paint does, single click for a fixed first point, and the second point (and the line) moves with mouse, another click fixes the line.
int x = 0, y = 0;
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
// Create the graphics object
Graphics g = CreateGraphics();
// Create the pen that will draw the line
Pen p = new Pen(Color.Navy);
// Create the pen that will erase the line
Pen erase = new Pen(Color.White);
g.DrawLine(erase, 0, 0, x, y);
// Save the mouse coordinates
x = e.X; y = e.Y;
g.DrawLine(p, 0, 0, x, y);
}
The clicking event part is fine, but with this method above, the erase line is actually white lines, which overlaps on other background image and previously plotted blue lines.
Is there a more manageable way to make it happen? Thanks
Any drawing on the form client area should be implemented in the OnPaint event to avoid any strange effects.
Consider the following code fragment:
Point Latest { get; set; }
List<Point> _points = new List<Point>();
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
// Save the mouse coordinates
Latest = new Point(e.X, e.Y);
// Force to invalidate the form client area and immediately redraw itself.
Refresh();
}
protected override void OnPaint(PaintEventArgs e)
{
var g = e.Graphics;
base.OnPaint(e);
if (_points.Count > 0)
{
var pen = new Pen(Color.Navy);
var pt = _points[0];
for(var i=1; _points.Count > i; i++)
{
var next = _points[i];
g.DrawLine(pen, pt, next);
pt = next;
}
g.DrawLine(pen, pt, Latest);
}
}
private void Form1_MouseClick(object sender, MouseEventArgs e)
{
Latest = new Point(e.X, e.Y);
_points.Add(Latest);
Refresh();
}
Don't try to erase the lines by drawing on top of them. You'll be better off if you draw to an off-screen buffer and on each draw call paint that bitmap to the control. That way you'll get flicker-free graphics and a clean line that works just the way you want it to.
Take a look at this forum post for a good explanation of how you should use the Graphics class and do drawing in general. There's also a good example program in the end of the post. I suggest you to take a look at that source code after going through the instructions.
I have an X-Y plot in a .NET 4.0 WinForms chart control. I am trying to implement rubber-band selection, so that the user could click and drag the mouse to create a rectangle on the plot, thus selecting all the points that lie within this rectangle.
While I was able to code up the drawing of the rectangle, I am now trying to identify the Datapoints that lie within this rectangle. Here is the relevant code:
public partial class Form1 : Form
{
System.Drawing.Point _fromPosition;
Rectangle _selectionRectangle;
public Form1()
{
InitializeComponent();
}
private void chart1_MouseMove(object sender, MouseEventArgs e)
{
// As the mouse moves, update the dimensions of the rectangle
if (e.Button == MouseButtons.Left)
{
Point p = e.Location;
int x = Math.Min(_fromPosition.X, p.X);
int y = Math.Min(_fromPosition.Y, p.Y);
int w = Math.Abs(p.X - _fromPosition.X);
int h = Math.Abs(p.Y - _fromPosition.Y);
_selectionRectangle = new Rectangle(x, y, w, h);
// Reset Data Point Attributes
foreach (DataPoint point in chart1.Series[0].Points)
{
point.BackSecondaryColor = Color.Black;
point.BackHatchStyle = ChartHatchStyle.None;
point.BorderWidth = 1;
}
this.Invalidate();
}
}
private void chart1_MouseDown(object sender, MouseEventArgs e)
{
// This is the starting position of the rectangle
_fromPosition = e.Location;
}
private void chart1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.DrawRectangle(new Pen(Color.Blue, 2), _selectionRectangle);
foreach (DataPoint point in chart1.Series[0].Points)
{
// Check if the data point lies within the rectangle
if (_selectionRectangle.Contains(???))))
{
// How do I convert DataPoint into Point?
}
}
}
}
What I am trying to do is query each DataPoint in the series and check if it lies within the Rectangle. Here, I am unable to transform each DataPoint into its corresponding Point. It seems pretty straightforward, so I am either missing something basic here or approaching the problem incorrectly.
I should also add that I referred to similar questions here and here, but they do not talk about how to actually identify DataPoints within the rectangle.
Any direction would be appreciated!
I have shown how to cheat the Chart into helping to get at the coordinates of DataPoints in the Paint event here.
But as you want to pick them up in the Paint event anyway, no cheating is needed..:
I define a List to collect the lassoed DataPoints:
List<DataPoint> dataPoints = new List<DataPoint>();
And I clear it on each new selection:
void chart1_MouseDown(object sender, MouseEventArgs e)
{
_fromPosition = e.Location;
dataPoints.Clear();
}
At the end I can write out the results:
void chart1_MouseUp(object sender, MouseEventArgs e)
{
foreach(DataPoint pt in dataPoints)
Console.WriteLine("found:" + pt.ToString() +
" at " + chart1.Series[0].Points.IndexOf(pt));
}
And in the Paint event we make use of the ValueToPixelPosition method of the two axes:
void chart1_Paint(object sender, PaintEventArgs e)
{
using (Pen pen = new Pen(Color.Blue, 2) // dispose of my Pen
{DashStyle = System.Drawing.Drawing2D.DashStyle.Dot})
e.Graphics.DrawRectangle(pen, _selectionRectangle);
foreach (DataPoint point in chart1.Series[0].Points)
{ // !! officially these functions are only reliable in a paint event!!
double x = chart1.ChartAreas[0].AxisX.ValueToPixelPosition(point.XValue);
double y = chart1.ChartAreas[0].AxisY.ValueToPixelPosition(point.YValues[0]);
PointF pt = new PointF((float)x,(float)y);
// Check if the data point lies within the rectangle
if (_selectionRectangle.Contains(Point.Round(pt)))
{
if (!dataPoints.Contains(point)) dataPoints.Add(point);
}
}
}
This question already has answers here:
Draw a rectangle on mouse click
(4 answers)
Closed 7 years ago.
So I've been trying lately to make a paint application to practice C#.
My problem for the past 2 days is in the creation of rectangles.
I made a panel so all the drawing are going there. The user selects the shape he wants to draw using a menu and he can start drawing with the mouse.
I encounter 2 problems which are the following:
1) Even though my starting point was inside the panel, I moved the mouse and went outside the panel and the rectangle was drawn outside the panel as shown in the picture below.
2) After I create this rectangle and I try to draw another, the previous one is deleted. So in a way I can't draw 2 rectangles at once.
Here's a part of my source code.
Graphics mygraphics;
Pen lPen = new Pen(Color.Black); //Left Pen
Pen rPen = new Pen(Color.White); //Right pen
Point sp = new Point(0, 0);
private bool isRectangle;
private bool isLeft, isRight; //isLeft -- Left Click, isRight -- Right Click
private void drawPanel_MouseMove(object sender, MouseEventArgs e)
{
if (isRectangle == true)
{
if (e.Button == MouseButtons.Left)
{
isLeft = true;
Point p = e.Location;
int x = Math.Min(sp.X, p.X);
int y = Math.Min(sp.Y, p.Y);
int w = Math.Abs(p.X - sp.X);
int h = Math.Abs(p.Y - sp.Y);
mRect = new Rectangle(x, y, w, h);
this.Invalidate();
}
}
}
private void drawPanel_MouseDown(object sender, MouseEventArgs e)
{
sp = e.Location;
}
private void drawPanel_MouseUp(object sender, MouseEventArgs e)
{
isLeft = false;
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
e.Graphics.DrawRectangle(lPen, mRect);
}
What I want to accomplish is my rectangles not getting deleted after I try to draw another one and to draw them inside the panel.
What do you guys suggest?
Here are the minimal changes I suggest:
List<Rectangle> rectangles = new List<Rectangle>();
Rectangle mRect = Rectangle.Empty;
private void drawPanel_MouseUp(object sender, MouseEventArgs e)
{
isLeft = false;
rectangles.Add(mRect);
mRect = Rectangle.Empty;
drawPanel.Invalidate();
}
private void drawPanel_Paint(object sender, PaintEventArgs e)
{
foreach (Rectangle rect in rectangles) e.Graphics.DrawRectangle(lPen, rect );
e.Graphics.DrawRectangle(Pens.Orange, mRect); // or whatever..
}
Note that the Paint event now is the one of the Panel. Do make sure you hook it up with the Panel!!
Also note how I draw the current rectangle mRect in a different color than the list of the other rectangles; this is of course optional..
I have an application which uses the mouse to free-draw a rectangle on a picbox image. However the rectangle only shows up behind the picbox, rather than on top of it. Is there a property i can set which can fix this? (show rect on top of picbox image rather than behind it). Here is the code:
System.Drawing.Graphics picboxGraphics;
bool mDown = false;
int mouseX;
int mouseY;
private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
{
mDown = true;
mouseX = e.X;
mouseY = e.Y;
}
private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
if (mDown == true)
{
this.Refresh();
Pen drawPen = new Pen(Color.Red, 5);
int width = e.X - mouseX, height = e.Y - mouseY;
Rectangle rect = new Rectangle(mouseX, mouseY, width * Math.Sign(width), height * Math.Sign(height));
picboxGraphics = this.CreateGraphics();
picboxGraphics.DrawRectangle(drawPen, rect);
}
}
private void pictureBox1_MouseUp(object sender, MouseEventArgs e)
{
mDown = false;
}
You creating graphics from form which is behind the picbox
you can create graphics from picbox's image and draw someting. But if you want layer system you can draw your thins on an transparent image and combine them. with this vay you can make an undo or delete layer system.
There are several problems in your code: dispose, global variable and wrong control to create graphics from. Make it this way:
using(var graphics = (sender as Control).CreateGraphics())
graphics.DrawRectangle(drawPen, rect);
But honestly, you have to organize it differently (assuming you are going to mimik Paint):
create new object (when mouse is down)
in Paint event draw object (if any)
during mouse move event, update edited object properties and call Invalidate
when you minimize/restore your form, the object will be still there (while in your example, it will get lost).
You can support List of objects to have storage for many rectangles and add history support (as TC Alper Tokcan answer suggesting) to at least Undo last object.
I want to click inside a square and then an "X" should appear, but I'm not sure what to put inside the Form1_MouseDown, Form1_Paint and Form1_MouseUp events. How can I implement this is C#?
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace VTest
{
public partial class Form1 : Form
{
Rectangle rect; // single rect
int sqsize, n;
int margin;
public Form1()
{
n = 3;
margin = 25;
sqsize = 50;
rect = new Rectangle(10, 10, 150, 150);
InitializeComponent();
}
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
// what goes here?
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
// what goes here?
}
private void Form1_MouseUp(object sender, MouseEventArgs e)
{
// what goes here?
}
// ...
In your MouseDown event, determining whether the click has occurred within your rectangle is easy:
if (rect.Contains(e.Location))
{
// the user has clicked inside your rectangle
}
Drawing the "X" on the form is also easy:
Graphics g = this.CreateGraphics();
g.DrawString("X", this.Font, SystemBrushes.WindowText,
(float)e.X, (float)e.Y);
However, the "X" in this case will not be persistent, meaning that if you drag another form over your form and then move it away, the "X" will not be there anymore. To draw a persistent "X", create a form-level Point variable like this:
private Point? _Xlocation = null;
Use your MouseDown event to set this variable if the user clicks in your Rectangle:
if (rect.Contains(e.Location))
{
_Xlocation = e.Location;
this.Invalidate(); // this will fire the Paint event
}
Then, in your form's Paint event, draw the "X":
if (_Xlocation != null)
{
e.Graphics.DrawString("X", this.Font, SystemBrushes.WindowText,
(float)e.X, (float)e.Y);
}
else
{
e.Graphics.Clear(this.BackColor);
}
If you want the "X" to then disappear when the user lets go of the mouse button, just put this code in the MouseUp event:
_Xlocation = null;
this.Invalidate();
You can make this as much more complicated as you like. With this code, the "X" will be drawn just below and to the right of wherever you click on the form. If you want the "X" to be centered on the click location, you can use the Graphics object's MeasureString method to determine how high and how wide the "X" will be, and offset the DrawString location accordingly.
You don't need both a mousedown and mouseup event handler.
Pick one to react to, I tend to react to the MouseDown event instead.
But, when you will want to look at the MouseEventArgs properties and you should be able to determine if you are inside the square.
You will probably want to call:
System.Diagnostics.Debug.WriteLine(...)
using the x and y properties in MouseEventArgs, so you can see where the mouse clicks are, and determine when you are in the square.
Once you are there, then you can draw the X.
You may want to write a function to draw an X and test it by having it draw an X at 300,300 so that you can ensure it looks as you want, while you are experimenting with MouseDown.
Update: I like the Rectangle.contains(location) method demonstrated by MusiGenesis.
public partial class formDemo : Form
{
Rectangle rec;
public formDemo() => InitializeComponent();
private void formDemo_Load(object sender, EventArgs e) =>
rec = new Rectangle(150,100,100,100);
private void frmDemo_Paint(object sender, PaintEventArgs e)
{
var p = new Pen(Color.Blue);
var g = e.Graphics;
g.DrawRectangle(p, rec);
}
private void formDemo_MouseMove(object sender, MouseEventArgs e) =>
Cursor = rec.Contains(e.Location) ? Cursors.Cross : Cursors.Default;
private void formDemo_MouseDown(object sender, MouseEventArgs e)
{
if (rec.Contains(e.Location))
{
// Mouse position adjust for window postion and border size.
// You may have to adjust the borders depending your
// Windows theme
int x = MousePosition.X - this.Left - 4;
int y = MousePosition.Y - this.Top - 29;
var g = this.CreateGraphics();
var p = new Pen(Color.Black);
var p1 = new Point(x - 10, y - 10);
var p2 = new Point(x + 10, y + 10);
var p3 = new Point(x - 10, y + 10);
var p4 = new Point(x + 10, y - 10);
g.DrawLines(p, new Point[] { p1, p2 });
g.DrawLines(p, new Point[] { p3, p4 });
}
}
}