I want to be able to grab a datapoint drawn in a chart and to move it and change its position by dragging it over the chart control.
How can I ..
..grab the specific series point (series name ="My Series")
When released the series point should change its position/ values
It's like making series points movable with drag event.
Here the color dots (points) should be able to move:
There are some charts like devExpress chart which perform this task but I want to do it in normal MS chart.
Moving a DataPoint is not a built-in feature of the Chart control. We need to code it..
The problem with interacting with a Chart by mouse is that there are not one but three coordinate systems at work in a Chart:
The chart elements, like a Legend or an Annotation are measured in percentages of the respective containers. Those data make up an ElementPosition and usually go from 0-100%.
The Mouse coordinates and all graphics drawn in one of the three Paint events, all work in pixels; they go from 0-Chart.ClientSize.Width/Height.
The DataPoints have an x-value and one (or more) y-values(s). Those are doubles and they can go from and to anywhere you set them to.
For our task we need to convert between mouse pixels and data values.
DO see the Update below!
There are several ways to do this, but I think this is the cleanest:
First we create a few class level variables that hold references to the targets:
// variables holding moveable parts:
ChartArea ca_ = null;
Series s_ = null;
DataPoint dp_ = null;
bool synched = false;
When we set up the chart we fill some of them:
ca_ = chart1.ChartAreas[0];
s_ = chart1.Series[0];
Next we need two helper functions. They do the 1st conversion between pixels and data values:
// two helper functions:
void SyncAllPoints(ChartArea ca, Series s)
{
foreach (DataPoint dp in s.Points) SyncAPoint(ca, s, dp);
synched = true;
}
void SyncAPoint(ChartArea ca, Series s, DataPoint dp)
{
float mh = dp.MarkerSize / 2f;
float px = (float)ca.AxisX.ValueToPixelPosition(dp.XValue);
float py = (float)ca.AxisY.ValueToPixelPosition(dp.YValues[0]);
dp.Tag = (new RectangleF(px - mh, py - mh, dp.MarkerSize, dp.MarkerSize));
}
Note that I chose to use the Tag of each DataPoints to hold a RectangleF that has the clientRectangle of the DataPoint's Marker.
These rectangles will change whenever the chart is resized or other changes in the Layout, like sizing of a Legend etc.. have happend, so we need to re-synch them each time! And, of course you need to initially set them whenever you add a DataPoint!
Here is the Resize event:
private void chart1_Resize(object sender, EventArgs e)
{
synched = false;
}
The actual refreshing of the rectangles is being triggered from the PrePaint event:
private void chart1_PrePaint(object sender, ChartPaintEventArgs e)
{
if ( !synched) SyncAllPoints(ca_, s_);
}
Note that calling the ValueToPixelPosition is not always valid! If you call it at the wrong time it will return null.. We are calling it from the PrePaint event, which is fine. The flag will help keeping things efficient.
Now for the actual moving of a point: As usual we need to code the three mouse events:
In the MouseDown we loop over the Points collection until we find one with a Tag that contains the mouse position. Then we store it and change its Color..:
private void chart1_MouseDown(object sender, MouseEventArgs e)
{
foreach (DataPoint dp in s_.Points)
if (((RectangleF)dp.Tag).Contains(e.Location))
{
dp.Color = Color.Orange;
dp_ = dp;
break;
}
}
In the MouseMove we do the reverse calculation and set the values of our point; note that we also synch its new position and trigger the Chart to refresh the display:
private void chart1_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button.HasFlag(MouseButtons.Left) && dp_ != null)
{
float mh = dp_.MarkerSize / 2f;
double vx = ca_.AxisX.PixelPositionToValue(e.Location.X);
double vy = ca_.AxisY.PixelPositionToValue(e.Location.Y);
dp_.SetValueXY(vx, vy);
SyncAPoint(ca_, s_, dp_);
chart1.Invalidate();
}
else
{
Cursor = Cursors.Default;
foreach (DataPoint dp in s_.Points)
if (((RectangleF)dp.Tag).Contains(e.Location))
{
Cursor = Cursors.Hand; break;
}
}
}
Finally we clean up in the MouseUp event:
private void chart1_MouseUp(object sender, MouseEventArgs e)
{
if (dp_ != null)
{
dp_.Color = s_.Color;
dp_ = null;
}
}
Here is how I have set up my chart:
Series S1 = chart1.Series[0];
ChartArea CA = chart1.ChartAreas[0];
S1.ChartType = SeriesChartType.Point;
S1.MarkerSize = 8;
S1.Points.AddXY(1, 1);
S1.Points.AddXY(2, 7);
S1.Points.AddXY(3, 2);
S1.Points.AddXY(4, 9);
S1.Points.AddXY(5, 19);
S1.Points.AddXY(6, 9);
S1.ToolTip = "(#VALX{0.##} / #VALY{0.##})";
S1.Color = Color.SeaGreen;
CA.AxisX.Minimum = S1.Points.Select(x => x.XValue).Min();
CA.AxisX.Maximum = S1.Points.Select(x => x.XValue).Max() + 1;
CA.AxisY.Minimum = S1.Points.Select(x => x.YValues[0]).Min();
CA.AxisY.Maximum = S1.Points.Select(x => x.YValues[0]).Max() + 1;
CA.AxisX.Interval = 1;
CA.AxisY.Interval = 1;
ca_ = chart1.ChartAreas[0];
s_ = chart1.Series[0];
Note that I have set both the Minima and Maxima as well as the Intervals for both Axes. This stops the Chart from running wild with its automatic display of Labels, GridLines, TickMarks etc..
Also note that this will work with any DataType for X- and YValues. Only the Tooltip formatting will have to be adapted..
Final note: To prevent the users from moving a DataPoint off the ChartArea you can add this check into the if-clause of the MouseMove event:
RectangleF ippRect = InnerPlotPositionClientRectangle(chart1, ca_);
if (!ippRect.Contains(e.Location) ) return;
For the InnerPlotPositionClientRectangle function see here!
Update:
On revisiting the code I wonder why I didn't choose a simpler way:
DataPoint curPoint = null;
private void chart1_MouseUp(object sender, MouseEventArgs e)
{
curPoint = null;
}
private void chart1_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button.HasFlag(MouseButtons.Left))
{
ChartArea ca = chart1.ChartAreas[0];
Axis ax = ca.AxisX;
Axis ay = ca.AxisY;
HitTestResult hit = chart1.HitTest(e.X, e.Y);
if (hit.PointIndex >= 0) curPoint = hit.Series.Points[hit.PointIndex];
if (curPoint != null)
{
Series s = hit.Series;
double dx = ax.PixelPositionToValue(e.X);
double dy = ay.PixelPositionToValue(e.Y);
curPoint.XValue = dx;
curPoint.YValues[0] = dy;
}
}
download Samples Environments for Microsoft Chart Controls
https://code.msdn.microsoft.com/Samples-Environments-for-b01e9c61
Check this:
Chart Features -> Interactive Charting -> Selection -> Changing Values by dragging
Related
I'm trying to create a sort of copy and paste function with the data on my graph and I was wondering if there was any way to get the x position of a point on the chart when it is clicked?
Basically, the idea is to be able to click a portion of the graph and drag to select an area, which I will then process accordingly.
So, I need to be able to figure out where on the graph the user has clicked to determine the what the first point of the selected area will be.
I looked through the chart API, but I couldn't seem to find anything useful for this type of problem..
For directly clicking on a DataPoint you can do a HitTest. But for tiny points or for a selection of a range this will not work well.
The necessary functions are hidden in the Axes methods.
This solution uses a regular rubber-band rectangle to select the points caught:
Point mdown = Point.Empty;
List<DataPoint> selectedPoints = null;
private void chart1_MouseDown(object sender, MouseEventArgs e)
{
mdown = e.Location;
selectedPoints = new List<DataPoint>();
}
private void chart1_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == System.Windows.Forms.MouseButtons.Left)
{
chart1.Refresh();
using (Graphics g = chart1.CreateGraphics())
g.DrawRectangle(Pens.Red, GetRectangle(mdown, e.Location));
}
}
private void chart1_MouseUp(object sender, MouseEventArgs e)
{
Axis ax = chart1.ChartAreas[0].AxisX;
Axis ay = chart1.ChartAreas[0].AxisY;
Rectangle rect = GetRectangle(mdown, e.Location);
foreach (DataPoint dp in chart1.Series[0].Points)
{
int x = (int)ax.ValueToPixelPosition(dp.XValue);
int y = (int)ay.ValueToPixelPosition(dp.YValues[0]);
if (rect.Contains(new Point(x,y))) selectedPoints.Add(dp);
}
// optionally color the found datapoints:
foreach (DataPoint dp in chart1.Series[0].Points)
dp.Color = selectedPoints.Contains(dp) ? Color.Red : Color.Black;
}
static public Rectangle GetRectangle(Point p1, Point p2)
{
return new Rectangle(Math.Min(p1.X, p2.X), Math.Min(p1.Y, p2.Y),
Math.Abs(p1.X - p2.X), Math.Abs(p1.Y - p2.Y));
}
Note that this will work for Line, FastLine and Point charts. For other types you would have to adapt the selection criterium!
My program can draw lines using canvas.Drawline(). How to click line and change this color (select line)?
private List<Point> coordFirst = new List<Point>();
private List<Point> coordLast = new List<Point>();
public Graphics canvas;
private void Form1_Load(object sender, EventArgs e)
{
canvas=panel1.CreateGraphics();
}
Coordinate line stored in coordFirs & coodLast.
Here is a suitable Line class:
class Line
{
public Color LineColor { get; set; }
public float Linewidth { get; set; }
public bool Selected { get; set; }
public Point Start { get; set; }
public Point End { get; set; }
public Line(Color c, float w, Point s, Point e)
{ LineColor = c; Linewidth = w; Start = s; End = e; }
public void Draw(Graphics G)
{ using (Pen pen = new Pen(LineColor, Linewidth)) G.DrawLine(pen, Start, End); }
public bool HitTest(Point Pt)
{
// test if we fall outside of the bounding box:
if ((Pt.X < Start.X && Pt.X < End.X) || (Pt.X > Start.X && Pt.X > End.X) ||
(Pt.Y < Start.Y && Pt.Y < End.Y) || (Pt.Y > Start.Y && Pt.Y > End.Y))
return false;
// now we calculate the distance:
float dy = End.Y - Start.Y;
float dx = End.X - Start.X;
float Z = dy * Pt.X - dx * Pt.Y + Start.Y * End.X - Start.X * End.Y;
float N = dy * dy + dx * dx;
float dist = (float)( Math.Abs(Z) / Math.Sqrt(N));
// done:
return dist < Linewidth / 2f;
}
}
Define a List for the lines, probably at class level:
List<Line> lines = new List<Line>();
Here is how you can initialize it with a few lines:
for (int i = 0; i < 20; i++) lines.Add(new Line(Color.Black, 4f,
new Point(R.Next(panel1.Width), R.Next(panel1.Height)),
new Point(R.Next(panel1.Width), R.Next(panel1.Height))));
Here is the result of clicking on a crossing:
Whenever you add, change or remove a line you need to make the Panel reflect the news by triggering the Paint event:
panel1.Invalidate();
Here is the Paint event of the Panel:
private void panel1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
foreach (Line L in lines) L.Draw(e.Graphics);
}
In the MouseClick event you do the test:
private void panel1_MouseClick(object sender, MouseEventArgs e)
{
foreach(Line L in lines)
L.LineColor = L.HitTest(e.Location) ? Color.Red : Color.Black;
panel1.Invalidate();
}
To avoid flicker don't use the basic Panel class as it isn't doublebuffered. Instead use either a PictureBox or a Label (with AutoSize=false) or a doublebuffered Panel subclass:
class DrawPanel : Panel
{ public DrawPanel () { DoubleBuffered = true; } }
Notes:
There is no such thing as a 'Line' in WinForms, only pixels of various colors. So to select a line you need to store it's two endpoints' coordinates and then find out if you have hit it when clicking.
The above example shows how to do it in math.
Instead one could test each line by drawing it onto a bitmap and test the pixel the mouse has clicked. But drawing those bitmaps would have to do math behind the scenes as well and also allocate space for the bitmaps, so the math will be more efficient..
Yes the Line class looks a little long for such a simple thing a s a line but look how short all the event codes now are! That's because the responsiblities are where they belong!
Also note the the first rule of doing any drawing in WinForms is: Never cache or store a Grahics object. In fact you shouldn't ever use CreateGraphics in the first place, as the Graphics object will never stay in scope and the graphics it produces will not persist (i.e. survive a Minimize-maximize sequence)..
Also note how I pass out the e.Graphics object of the Paint event's parameters to the Line instances so they can draw themselves with a current Graphics object!
To select thinner lines it may help to modify the distance check a little..
The Math was taken directly form Wikipedia.
You can change the color of everything on click. By using click event of particular object.
I give you an example for button. If you click on button then panal’s color will be change. You can modify the code as per your requirement.
private List<Point> coordFirst = new List<Point>();
private List<Point> coordLast = new List<Point>();
public Graphics canvas;
private void Form1_Load(object sender, EventArgs e)
{
canvas = panel1.CreateGraphics();
}
private void panel1_Click(object sender, EventArgs e)
{
panel1.BackColor = Color.Blue;
}
private void nonSelectableButton3_Click(object sender, EventArgs e)
{
panel1.BackColor = Color.BurlyWood;
}
I have small problem. I want make program where I can drag generated labels between multiple FlowLayoutPanels. But last few days I have tried to make drag and drop working. I tried many tutorials, examples etc. but it is always something bit diferent and I am not able extract only basic code.
It is similar to this program but it is in Visual Basic and I need it in C#. I know it is maybe very simple, but I am newbie.
Thank you for help.
Real Drag&Drop ist most useful between applications and maybe also between Forms.
Assuming you want to drag Labels between FLPs on the same Form, the code below should get you going..
It takes two FlowLayoutPanels called FLP1 and FLP2 and starts by initializing them with a few Labels.
Note the three mouse events I add to each Label to emulate a Drag&Drop action!
private void Form1_Load(object sender, EventArgs e)
{
// create a few Label with varying Colors for testing..
fillFLP(FLP1, 88);
fillFLP(FLP2, 111);
}
void fillFLP(FlowLayoutPanel FLP, int cc)
{
for (int i = 0; i < 24; i++)
{
Label l = new Label();
// the next 3 lines optional and only are there for testing!
l.AutoSize = false;
l.Text = FLP.Name + " " + i.ToString("00");
l.BackColor = Color.FromArgb(255, cc * 2 - i, 255 - 5 * i, cc + 5 * i);
// add controls and set mouse events:
FLP.Controls.Add(l);
l.MouseDown += l_MouseDown;
l.MouseMove += l_MouseMove;
l.MouseUp += l_MouseUp;
}
}
// the currently moved Label:
Label mvLabel = null;
void l_MouseDown(object sender, MouseEventArgs e)
{
// keep reference
mvLabel = (Label)sender;
}
void l_MouseMove(object sender, MouseEventArgs e)
{
// if we are dragging a label:
if (mvLabel != null)
{
// mouse pos in window coords
Point mvPoint = this.PointToClient(Control.MousePosition);
// the label is still in the FLP, so we start the drg action:
if (mvLabel.Parent != this)
{
mvLabel.Parent = this;
mvLabel.Location = mvPoint;
mvLabel.BringToFront();
}
else
{
// we are already in the form, so we just move
mvLabel.Location = mvPoint;
}
}
}
void l_MouseUp(object sender, MouseEventArgs e)
{
// are we over a FLP? and if so which?
Point MP = Control.MousePosition;
FlowLayoutPanel FLP = null;
Point mLoc1 = FLP1.PointToClient(MP);
Point mLoc2 = FLP2.PointToClient(MP);
if (FLP1.ClientRectangle.Contains(mLoc1)) FLP = FLP1;
else if (FLP2.ClientRectangle.Contains(mLoc2)) FLP = FLP2;
else return; // no! nothing we can do..
// yes, now find out if we are over a label..
// ..or over an empty area
mvLabel.SendToBack();
Control cc = FLP.GetChildAtPoint(FLP.PointToClient(MP));
// if we are over the FLP we can insert at the beginning or the end:
// int mvIndex = 0; // to the beginning
int mvIndex = FLP.Controls.Count; // to the end
// we are over a Label, so we insert before it:
if (cc != null) mvIndex = FLP.Controls.IndexOf(cc);
// move the Label into the FLP
FLP.Controls.Add(mvLabel);
// move it to the right position:
FLP.Controls.SetChildIndex(mvLabel, mvIndex);
// let go of the reference
mvLabel = null;
}
This lets you drag and drop Lables to and fro between two FLPs and also within the FLPs by dropping onto Labels.
Note that you will need a few extra lines if you want to allow dropping between Labels and still position there..
Ok, this could be a little tough to explain. I have a method that computes X and Y values that I want to plot. This method is purely backend and runs inside a backgroundworker called from my main GUI thread.
Separate from my main form I have a form that contains only a zedgraph and a ticker. I use the combination to display the rolling X,Y spit out from my background thread. This works fine, everything goes great here.
When I click a button on my main GUI the backgroundworker is closed, and zedgraph stops updating. Here's where my problem starts
When I click the stop button the graph needs to stay up. It does this just fine... iff it's the very first time it was run. On all future graphs this happens: (Top image is the first graph, second image is the second graph.)
The first graph keeps updating when it isn't supposed to. How do I keep this from happening? Is there a way to "shut down" the first zedgraph and keep it from listening for new data?
Below is my zedgraph code, I'm pretty sure the problem is in here somewhere and not my main GUI code.
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;
using ZedGraph;
namespace RTHERM
{
public partial class Readout : Form
{
// Starting time in milliseconds
public static float Time_old = 0.0f;
public static float Tsurf_old;
public static float Tmidr_old;
public static float Tcent_old;
public static float Tenvi_old;
// Every "redrawInterval" secods plot a new point (if one is available)
public static int redrawInterval;
public int plotRange = 15; // Plot will span "plotRange" minutes
int tickStart = 0;
public Readout()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
//timer1.Equals(0);
GUI gui = new GUI();
GraphPane graph = zedGraph.GraphPane;
graph.Title.Text = GUI.plotTitle;
graph.XAxis.Title.Text = "Time [min]";
graph.YAxis.Title.Text = "Temperature [F]";
graph.Legend.Position = ZedGraph.LegendPos.BottomCenter;
// Save 1200 points. At 50 ms sample rate, this is one minute
// The RollingPointPairList is an efficient storage class that always
// keeps a rolling set of point data without needing to shift any data values
RollingPointPairList surfList = new RollingPointPairList(1200);
//surfList.Clear();
RollingPointPairList midrList = new RollingPointPairList(1200);
//midrList.Clear();
RollingPointPairList centList = new RollingPointPairList(1200);
//centList.Clear();
RollingPointPairList furnList = new RollingPointPairList(1200);
//furnList.Clear();
// Initially, a curve is added with no data points (list is empty)
// Color is blue, and there will be no symbols
LineItem surf = graph.AddCurve("Surface", surfList, Color.DarkBlue, SymbolType.None);
LineItem midr = graph.AddCurve("Mid-Radius", midrList, Color.DarkOliveGreen, SymbolType.None);
LineItem cent = graph.AddCurve("Center", centList, Color.DarkOrange, SymbolType.None);
LineItem furn = graph.AddCurve("Ambient", furnList, Color.Red, SymbolType.None);
surf.Line.Width = 2;
midr.Line.Width = 2;
cent.Line.Width = 2;
furn.Line.Width = 2;
// Check for new data points
timer1.Interval = redrawInterval;
timer1.Enabled = true;
//timer1.Start();
// Just manually control the X axis range so it scrolls continuously
// instead of discrete step-sized jumps
graph.XAxis.Scale.Min = 0;
graph.XAxis.Scale.Max = plotRange;
graph.XAxis.Scale.MinorStep = 1;
graph.XAxis.Scale.MajorStep = 5;
// Scale the axes
zedGraph.AxisChange();
// Save the beginning time for reference
tickStart = Environment.TickCount;
}
// USING A TIMER OBJECT TO UPDATE EVERY FEW MILISECONDS
private void timer1_Tick(object sender, EventArgs e)
{
// Only redraw if we have new information
if (Transfer.TTIME != Time_old)
{
GraphPane graph = this.zedGraph.GraphPane;
// Make sure that the curvelist has at least one curve
if (zedGraph.GraphPane.CurveList.Count <= 0)
return;
// Grab the three lineitems
LineItem surf = this.zedGraph.GraphPane.CurveList[0] as LineItem;
LineItem midr = this.zedGraph.GraphPane.CurveList[1] as LineItem;
LineItem cent = this.zedGraph.GraphPane.CurveList[2] as LineItem;
LineItem furn = this.zedGraph.GraphPane.CurveList[3] as LineItem;
if (surf == null)
return;
// Get the PointPairList
IPointListEdit surfList = surf.Points as IPointListEdit;
IPointListEdit midrList = midr.Points as IPointListEdit;
IPointListEdit centList = cent.Points as IPointListEdit;
IPointListEdit enviList = furn.Points as IPointListEdit;
// If these are null, it means the reference at .Points does not
// support IPointListEdit, so we won't be able to modify it
if (surfList == null || midrList == null || centList == null || enviList == null)
return;
// Time is measured in seconds
double time = (Environment.TickCount - tickStart) / 1000.0;
// ADDING THE NEW DATA POINTS
// format is List.Add(X,Y) Finally something that makes sense!
surfList.Add(Transfer.TTIME, Transfer.TSURF);
midrList.Add(Transfer.TTIME, Transfer.TMIDR);
centList.Add(Transfer.TTIME, Transfer.TCENT);
enviList.Add(Transfer.TTIME, Transfer.TENVI);
// Keep the X scale at a rolling 10 minute interval, with one
// major step between the max X value and the end of the axis
if (GUI.isRunning)
{
Scale xScale = zedGraph.GraphPane.XAxis.Scale;
if (Transfer.TTIME > xScale.Max - xScale.MajorStep)
{
xScale.Max = Transfer.TTIME + xScale.MajorStep;
xScale.Min = xScale.Max - plotRange;
}
}
// Make sure the Y axis is rescaled to accommodate actual data
zedGraph.AxisChange();
// Force a redraw
zedGraph.Invalidate();
}
else return;
}
public void reset()
{
Time_old = 0.0f;
Tsurf_old = 0.0f;
Tmidr_old = 0.0f;
Tcent_old = 0.0f;
Tenvi_old = 0.0f;
}
private void Form1_Resize(object sender, EventArgs e)
{
if (GUI.isRunning)
{
SetSize();
}
}
// Set the size and location of the ZedGraphControl
private void SetSize()
{
// Control is always 10 pixels inset from the client rectangle of the form
Rectangle formRect = this.ClientRectangle;
formRect.Inflate(-10, -10);
if (zedGraph.Size != formRect.Size)
{
zedGraph.Location = formRect.Location;
zedGraph.Size = formRect.Size;
}
}
private void saveGraph_Click(object sender, EventArgs e)
{
GUI.Pause();
zedGraph.DoPrint();
//SaveFileDialog saveDialog = new SaveFileDialog();
//saveDialog.ShowDialog();
}
private void savePlotDialog_FileOk(object sender, CancelEventArgs e)
{
// Get file name.
string name = savePlotDialog.FileName;
zedGraph.MasterPane.GetImage().Save(name);
GUI.Resume();
}
private bool zedGraphControl1_MouseMoveEvent(ZedGraphControl sender, MouseEventArgs e)
{
// Save the mouse location
PointF mousePt = new PointF(e.X, e.Y);
// Find the Chart rect that contains the current mouse location
GraphPane pane = sender.MasterPane.FindChartRect(mousePt);
// If pane is non-null, we have a valid location. Otherwise, the mouse is not
// within any chart rect.
if (pane != null)
{
double x, y;
// Convert the mouse location to X, and Y scale values
pane.ReverseTransform(mousePt, out x, out y);
// Format the status label text
toolStripStatusXY.Text = "(" + x.ToString("f2") + ", " + y.ToString("f2") + ")";
}
else
// If there is no valid data, then clear the status label text
toolStripStatusXY.Text = string.Empty;
// Return false to indicate we have not processed the MouseMoveEvent
// ZedGraphControl should still go ahead and handle it
return false;
}
private void Readout_FormClosed(object sender, FormClosedEventArgs e)
{
}
private void Readout_FormClosing(object sender, FormClosingEventArgs e)
{
//e.Cancel = true;
//WindowState = FormWindowState.Minimized;
}
}
}
The last point is connected to the first point in the graph. I suspect, because all your RollingPointPairList contains the data multiple times. Verify this using a breakpoint in your timer1_Tickfunction.
On private void Form1_Load you will add the complete list to the LineItem.
graph.CurveList.Clear();
LineItem surf = graph.AddCurve("Surface", surfList, Color.DarkBlue, SymbolType.None);
LineItem midr = graph.AddCurve("Mid-Radius", midrList, Color.DarkOliveGreen, SymbolType.None);
LineItem cent = graph.AddCurve("Center", centList, Color.DarkOrange, SymbolType.None);
LineItem furn = graph.AddCurve("Ambient", furnList, Color.Red, SymbolType.None);
My current program allows the user to click a point, then click another point (at least 20 pixels away) and draws a line between the 2 points. I've used a Polyline so that this can be done multiple times. Though the set of all the lines only appear after all the click are done.
void DrawingCanvas_MouseUp(object sender, MouseButtonEventArgs e) {
Point position = e.GetPosition(this);
if (leftList == null) {
//starting a new set
leftList.Add(position);
lastPoint = position;
return;
}
//calculate distance, i.e. end click
double a = lastPoint.X - position.X;
double b = lastPoint.Y - position.Y;
double distance = Math.Sqrt(a * a + b * b);
if (distance > 20) {
//continue to add to list
leftList.Add(position);
lastPoint = position;
} else {
//end of the line
paint();
leftList = new PointCollection();
}
}
private void paint() {
Polyline line = new Polyline();
line.Visibility = System.Windows.Visibility.Visible;
line.StrokeThickness = 2;
line.Stroke = System.Windows.Media.Brushes.Black;
line.Points = leftList;
myCanvas.Children.Add(line);
}
So my question is two-fold:
A) How do I make it so that after each click the new line is immediately added.
B) How do I render a line between the last point and where the mouse cursor is currently at (i.e. just before you choose your next point)
The following simple example starts drawing a new polyline when the left mouse button is pressed and the mouse is moved by the minimum point distance of 20, with the button kept pressed. It draws the last polyline segment (to the current mouse position) in either red or green, depending on its length. If the mouse button is released and the length of the new segment is >= 20, a new point is appended to the polyline. Otherwise the polyline is terminated, and a new polyline can be created.
private Polyline polyline;
private Polyline segment = new Polyline { StrokeThickness = 2 };
private void Canvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (polyline == null)
{
var canvas = (Canvas)sender;
var point = e.GetPosition(canvas);
// create new polyline
polyline = new Polyline { Stroke = Brushes.Black, StrokeThickness = 2 };
polyline.Points.Add(point);
canvas.Children.Add(polyline);
// initialize current polyline segment
segment.Stroke = Brushes.Red;
segment.Points.Add(point);
segment.Points.Add(point);
canvas.Children.Add(segment);
}
}
private void Canvas_MouseMove(object sender, MouseEventArgs e)
{
if (polyline != null)
{
// update current polyline segment
var canvas = (Canvas)sender;
segment.Points[1] = e.GetPosition(canvas);
var distance = (segment.Points[0] - segment.Points[1]).Length;
segment.Stroke = distance >= 20 ? Brushes.Green : Brushes.Red;
}
}
private void Canvas_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (polyline != null)
{
var canvas = (Canvas)sender;
segment.Points[1] = e.GetPosition(canvas);
var distance = (segment.Points[0] - segment.Points[1]).Length;
if (distance >= 20)
{
polyline.Points.Add(segment.Points[1]);
segment.Points[0] = segment.Points[1];
}
else
{
if (polyline.Points.Count < 2)
{
canvas.Children.Remove(polyline);
}
polyline = null;
segment.Points.Clear();
canvas.Children.Remove(segment);
}
}
}
please maintain a collection of points on every click. in collection you can add one class which will have two properties like StartPoint and EndPoint.
when the mouse is clicked first time just add one class object to collection having start point only.
and when you click the mouse next time, ad end point to the last object of the class and meanwhile create a new object and assign this point as its start point and add it to collection, after that call the paint function.