In mschart, I use line annotation with SetAnchor(point 8, point 12).
If I scroll chart and hide point 8, I cannot see remaining line annotation(point 9 to 12).
I want to see remaining line annotation. help me!
my reference ; Samples Environments for Microsoft Chart Controls
--> Chart Features --> Annotations --> Annotation Anchoring
private void AddLineAnnotation()
{
// create a line annotation
LineAnnotation annotation = new LineAnnotation();
// setup visual attributes
annotation.StartCap = LineAnchorCapStyle.Arrow;
annotation.EndCap = LineAnchorCapStyle.Arrow;
annotation.LineWidth = 3;
annotation.LineColor = Color.OrangeRed;
annotation.ShadowOffset = 2;
annotation.ClipToChartArea = "Default";
// prevent moving or selecting
annotation.AllowMoving = false;
annotation.AllowAnchorMoving = false;
annotation.AllowSelecting = false;
if(Chart1.Series[0].Points.Count > 13)
{
// Use the Anchor Method to anchor to points 8 and 12...
annotation.SetAnchor(Chart1.Series[0].Points[8], Chart1.Series[0].Points[12]);
}
// add the annotation to the collection
Chart1.Annotations.Add(annotation);
}
This is a tricky one.
The bad news is: I don't think it is possible. I think MSChart will omit all annotations that start outside of the visible area. Maybe the reasoning was to avoid clutter, but who can tell..?
A workaround would have to take into acount the case when both endpoints are outside and we still would like to see the annotation..
The good news is that with ownerdrawing one can code a workaround that will indeed draw the lines for both cases.
The following example shows the drawing code. Make sure to separate the modes for dragging to zoom and dragging to draw a new annotation. I use a checkbox and its CheckChanged event.
Let's first see it in action:
When the start of an annotation is scrolled off, the line drawing sets in. Pretty hard to notice..
Here is the code for a xxxPaint event:
private void chart1_PostPaint(object sender, ChartPaintEventArgs e)
{
// loop only over line annotations:
List<LineAnnotation> annos =
chart1.Annotations.Where(x => x is LineAnnotation)
.Cast<LineAnnotation>().ToList();
if (!annos.Any()) return;
// a few short references
Graphics g = e.ChartGraphics.Graphics;
ChartArea ca = chart1.ChartAreas[0];
Axis ax = ca.AxisX;
Axis ay = ca.AxisY;
// we want to clip the line to the innerplotposition excluding the scrollbar:
Rectangle r = Rectangle.Round(InnerPlotPositionClientRectangle(chart1, ca));
g.SetClip(new Rectangle(r.X, r.Y, r.Width, r.Height - (int)ax.ScrollBar.Size));
g.InterpolationMode = InterpolationMode.NearestNeighbor; // pick your mode!
foreach (LineAnnotation la in annos)
{
if (Double.IsNaN(la.Width)) continue; // *
// calculate the coordinates
int x1 = (int)ax.ValueToPixelPosition(la.AnchorX);
int y1 = (int)ay.ValueToPixelPosition(la.AnchorY);
int x2 = (int)ax.ValueToPixelPosition(la.AnchorX + la.Width);
int y2 = (int)ay.ValueToPixelPosition(la.AnchorY + la.Height);
// now we draw the line if necessary:
if (x1 < r.X || x1 > r.Right)
using (Pen pen = new Pen(la.LineColor, 0.5f)) g.DrawLine(pen, x1, y1, x2, y2);
}
// reset the clip to allow the system drawing a scrollbar
g.ResetClip();
}
A few notes:
The code assumes (*) that the Annotations are all anchored with AnchorX/Y and have a Width/Height set. If you have used a different ways of anchoring you need to adapt the code.
For the clipping part we need to know the pixel size/positon of the InnerPlotPosition. For this you can use the code in e.g. at the bottom of this link.
I didn't code anything but a straight line. If you have adorned your annotation you may need to expand on the code;
Related
How do you create an Annotation on-the-run and how do you enable end-user placement with Annotation.BeginPlacement()? I've tried to do this in multiple ways, but cannot get it working. It should render itself in real-time after the BeginPlacement() has been called.
Documentations on this subject is little to none - and mostly none - so I'm not able to find any help for this problem.
What I've tried so far, is to create an annotation and place it with AnchorX/Y, set all Allow- flags to true and called BeginPlacement() while mouse is moving, but cannot see the annotation while placing it nor will it go in it's place accordingly. For example, LineAnnotation starts in right position, but doesn't end where I left it. When I move it so it starts from my ChartAreas {0,0}, it will hit the end-point.
What I want to know, is when and how to use these tools available? What I am trying to do, is to let the user draw annotations on a chart and use as tools when analyzing the charts.
You need to calculate the right positions. Remember that the MouseMove will not give you positions (percentages) or values(data) but pixels. You can transform them using the various axis functions. Officially they only work in a xxxPaint event, but during mouse events they also work fine.
Update: There two ways to do the anchoring:
Either by using the 'Positions', i.e. the percentages or the 'Values', i.e. the data values.
Here is an example of the 1st kind:
LineAnnotation laNew = null;
private void chart1_MouseDown(object sender, MouseEventArgs e)
{
if (cbx_drawAnnotation.Checked)
{
Axis ax = chart1.ChartAreas[0].AxisX;
Axis ay = chart1.ChartAreas[0].AxisY;
laNew = new LineAnnotation();
chart1.Annotations.Add(laNew);
double vx = ax.ValueToPosition(ax.PixelPositionToValue(e.X));
double vy = ay.ValueToPosition(ay.PixelPositionToValue(e.Y));
laNew.X = vx;
laNew.Y = vy;
}
}
private void chart1_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button.HasFlag(MouseButtons.Left) && cbx_drawAnnotation.Checked)
{
Axis ax = chart1.ChartAreas[0].AxisX;
Axis ay = chart1.ChartAreas[0].AxisY;
double vx = ax.ValueToPosition(ax.PixelPositionToValue(e.X))- laNew.X;
double vy = ay.ValueToPosition(ay.PixelPositionToValue(e.Y)) - laNew.Y;
laNew.Width = Math.Min(100, vx);
laNew.Height = Math.Min(100, vy);
laNew.LineColor = rb_green.Checked ? Color.Green : Color.Red;
laNew.AllowMoving = true; // optional
}
}
This works fine unles you need to rescale the axis in some way, like changing the axis minimum and/or maximum values.
In the case you need to anchor to data values.
First we need to relate the Annotation to the Axes and also set IsSizeAlwaysRelative to false. Then we can calculate the anchor and size values:
private void chart1_MouseDown(object sender, MouseEventArgs e)
{
if (cbx_drawAnnotation.Checked)
{
Axis ax = chart1.ChartAreas[0].AxisX;
Axis ay = chart1.ChartAreas[0].AxisY;
laNew = new LineAnnotation();
chart1.Annotations.Add(laNew);
laNew.IsSizeAlwaysRelative = false;
laNew.AxisX = ax;
laNew.AxisY = ay;
laNew.AnchorX = ax.PixelPositionToValue(e.X);
laNew.AnchorY = ay.PixelPositionToValue(e.Y);
laNew.LineColor = rb_green.Checked ? Color.Green : Color.Red;
laNew.AllowMoving = true;
}
}
private void chart1_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button.HasFlag(MouseButtons.Left) && cbx_drawAnnotation.Checked)
{
Axis ax = chart1.ChartAreas[0].AxisX;
Axis ay = chart1.ChartAreas[0].AxisY;
laNew.Width = ax.PixelPositionToValue(e.X) - laNew.AnchorX; // values
laNew.Height = ay.PixelPositionToValue(e.Y) - laNew.AnchorY;
}
}
Note how I now can scale the maximum and also still resize the the chart and the annotations stay with the data points..:
Update: To restrict the line to the ChartArea add this to the definition in the MouseDown event:
laNew.ClipToChartArea = chart1.ChartAreas[0].Name;
To prevent an exception from leaving the Chart, add this to the condition in the MouseMove..:
.. && chart1.ClientRectangle.Contains(e.Location)
Tweaking MS Charts. I have successfully draw a dynamic chart but need to draw a line (Yellow) across the chart. How will i draw (yellow) line. I have X and Y values.
Here is an example you can play with:
// we create a general LineAnnotation, ie not Vertical or Horizontal:
LineAnnotation lan = new LineAnnotation();
// we use Axis scaling, not chart scaling
lan.IsSizeAlwaysRelative = false;
lan.LineColor = Color.Yellow;
lan.LineWidth = 5;
// the coordinates of the starting point in axis measurement
lan.X = 3.5d;
lan.Y = 0d;
// the size:
lan.Width = -3.5d;
lan.Height = 5.5d;
// looks like we need an anchor point, no matter which..
lan.AnchorDataPoint = yourSeries.Points[0];
// now we can add the LineAnnotation;
chart1.Annotations.Add(lan);
Just to explain what I'm doing, I draw two selectors on a chart, and the part that will not be selected should appear under that blue rectangle. The part that will be selected will appear in the white area, between the two selectors. The figure below shows only the left selector.
Now, what I'm trying to do is to draw a rectangle inside a chart that always remain inside the plotting area, even when the windows is resized.
To get the top, left and bottom bounds, to draw the rectangle as shown in the figure below, I do the following:
(...)
int top = (int)(Chart.Height * 0.07);
int bottom = (int)(Chart.Height - 1.83 * top);
int left = (int)(0.083 * Chart.Width);
Brush b = new SolidBrush(Color.FromArgb(128, Color.Blue));
e.Graphics.FillRectangle(b, left, top, marker1.X - left, bottom - top);
(...)
But that's far from perfect, and it isn't drawn in the right place when the window is resized. I want the blue rectangle to always be bound on the top, left and bottom by the plotting area grid. Is that possible?
You probably want to use StripLine to achieve this.
Look into the Stripline Class Documentation.
Also I recommend downloading the Charting Samples which are a great help to understand the various features.
StripLine stripLine = new StripLine();
stripLine.Interval = 0; // Set Strip lines interval to 0 for non periodic stuff
stripLine.StripWidth = 10; // the width of the highlighted area
stripline.IntervalOffset = 2; // the starting X coord of the highlighted area
// pick you color etc ... before adding the stripline to the axis
chart.ChartAreas["Default"].AxisX.StripLines.Add( stripLine );
This assumes you are wanting something that is not what Cursor already does (see CursorX), such as letting the user mark up areas of the plot which provides some persistence. Combining the Cursor events with the striplines above would be a good way to do that.
So to highlight the start and end of the cursor you could do this
// this would most likely be done through the designer
chartArea1.AxisX.ScaleView.Zoomable = false;
chartArea1.CursorX.IsUserEnabled = true;
chartArea1.CursorX.IsUserSelectionEnabled = true;
this.chart1.SelectionRangeChanged += new System.EventHandler<System.Windows.Forms.DataVisualization.Charting.CursorEventArgs>(this.chart1_SelectionRangeChanged);
...
private void chart1_SelectionRangeChanged(object sender, CursorEventArgs e)
{
chart1.ChartAreas[0].AxisX.StripLines.Clear();
StripLine stripLine1 = new StripLine();
stripLine1.Interval = 0;
stripLine1.StripWidth = chart1.ChartAreas[0].CursorX.SelectionStart - chart1.ChartAreas[0].AxisX.Minimum;
stripLine1.IntervalOffset = chart1.ChartAreas[0].AxisX.Minimum;
// pick you color etc ... before adding the stripline to the axis
stripLine1.BackColor = Color.Blue;
chart1.ChartAreas[0].AxisX.StripLines.Add(stripLine1);
StripLine stripLine2 = new StripLine();
stripLine2.Interval = 0;
stripLine2.StripWidth = chart1.ChartAreas[0].AxisX.Maximum - chart1.ChartAreas[0].CursorX.SelectionEnd;
stripLine2.IntervalOffset = chart1.ChartAreas[0].CursorX.SelectionEnd;
// pick you color etc ... before adding the stripline to the axis
stripLine2.BackColor = Color.Blue;
chart1.ChartAreas[0].AxisX.StripLines.Add(stripLine2);
}
Somehow I suspect you may not have discovered the cursor yet, and doing so will make all this irrelevant. But anyway, the above code will do what you described.
I would like to draw two shapes in WPF and merge them together. Then, I'd like to attach a drag/drop event to ONE of the original shapes.
So basically, you can only drag if you click on a certain part of the shape, but it will drag the entire shape with you.
Here is some code:
// Set up some basic properties for the two ellipses
Point centerPoint = new Point(100, 100);
SolidColorBrush ellipseColor_1 = new SolidColorBrush(Color.FromArgb(255, 0, 0, 255));
double width_1 = 10; double height_1 = 10;
SolidColorBrush ellipseColor_2 = new SolidColorBrush(Color.FromArgb(50, 255, 0, 0));
double width_2 = 200; double height_2 = 200;
// Create the first ellipse: A small blue dot
// Then position it in the correct location (centerPoint)
Ellipse ellipse_1 = new Ellipse() { Fill = ellipseColor_1, Width = width_1, Height = height_1 };
ellipse_1.RenderTransform = new TranslateTransform(point.X - width_1 / 2, point.Y - height_1 / 2);
// Create the second ellipse: A large red, semi-transparent circle
// Then position it in the correct location (centerPoint)
Ellipse ellipse_2 = new Ellipse() { Fill = ellipseColor_2, Width = width_2, Height = height_2 };
ellipse_2.RenderTransform = new TranslateTransform(point.X - width_2 / 2, point.Y - height_2 / 2);
// ???
// How should I merge these?
// ???
// Now apply drag drop behavior to ONLY ellipse_1
MouseDragElementBehavior dragBehavior = new MouseDragElementBehavior();
dragBehavior.Attach(ellipse_1); // This may change depending on the above
// ...
// Add new element to canvas
This code creates two circles (a big one and a small one). I would like to only be able to drag if the small one is clicked, but I'd like to have them attached so they'll move together without having to manually add code that will take care of this.
If you put them both in a Grid (or Canvas, StackPanel, etc.), and set the drag behavior on the panel, they will be "merged". If you set IsHitTestVisible to false on ellipse_2, it won't respond to any mouse events, so effectively it won't be draggable.
i have no previous experience in plotting in winforms, in one form i want to plot ecg. or lets say a sin wave or any wave function in a specific area, but what i am doing is e.c.g.. rest of the form will be normal form with buttons and labels,
can anybody be nice enough to through in a tutorial
:)
You have few choices, you can write your own control, that will process data and render it. For more complicated plots, that can be a bit complicated, but the basics are always the same, setting X and Y values ranges and then just draw a line using GDI going from left to right, nothing fancy.
As this can get a bit complicated for more advanced features, you could use some charting controls, I'd read this post or check codeproject.com, I remember, that I saw few attempts to write some decent charting controls, which are open source, new articles will probably be coded in WPF, but you should find something older as well.
Edit:
Some links that you can find useful: Graph plotting lib that's main goal is to simulate ECG or another graph plotting lib
You need to create a custom control.
public class MyECGDrawer : Control{}
In it, you override the OnPaint event
protect override OnPaint(PaintEventArgs pe ){}
Then in the paint function, you draw your graphics the way you want it, let's say sin(x)
// refresh background
pe.Graphics.FillRectangle( Brushes.White, 0, 0, Width, Height );
int prevX = -1, prevY = -1;
for(int x = 0; x < Width; x++ )
{
if( prevX >= 0 )
{
pe.Graphics.DrawLine( Pens.Black, prevX, prevY, x, Math.sin(x) );
}
prevX = x;
prevY = Math.sin(x);
}
To force the ECG to redraw, you call the .Invalidate() function on the control. You should be able to drag and drop the control in your form from the designer.
In the end, the class would look like
public class MyECGDrawer : Control{}
In it, you override the OnPaint event
public class MyECGDrawer : Control
{
protect override OnPaint(PaintEventArgs pe )
{
// refresh background
pe.Graphics.FillRectangle( Brushes.White, 0, 0, Width, Height );
int prevX = -1, prevY = -1;
for(int x = 0; x < Width; x++ )
{
if( prevX >= 0 )
pe.Graphics.DrawLine( Pens.Black, prevX, prevY, x, Math.sin(x) );
prevX = x;
prevY = Math.sin(x);
}
}
}
I wrote up the following and tested it. It seems to do what you want, but note that it is simply plotting sin(x) in a loop with no delay - i.e. the plot for sin(x) streams off the left edge so fast you can hardly see it. You can, however, put a break on any line inside the loop and then step through the loop with F5 to see it work slowly - presumably your streaming ECG data will only arrive at some fixed speed so this should not be a problem in your implementation.
In the following, monitor is a PictureBox on a winforms form. Everything else is local.
private void drawStream(){
const int scaleX = 40;
const int scaleY = 40;
Point monitorTopLeft = new Point(0, 0);
Point MonitorTopLeftMinus1 = new Point(-1, 0);
int halfX = monitor.Width / 2;
int halfY = monitor.Height / 2;
Size size = new Size(halfX + 20, monitor.Height);
Graphics g = monitor.CreateGraphics();
g.TranslateTransform(halfX, halfY);
g.ScaleTransform(scaleX, scaleY);
g.Clear(Color.Black);
g.ResetClip();
float lastY = (float)Math.Sin(0);
float y = lastY;
Pen p = new Pen(Color.White, 0.01F);
float stepX = 1F / scaleX;
for (float x = 0; x < 10; x += stepX) {
g.CopyFromScreen(monitor.PointToScreen(monitorTopLeft), MonitorTopLeftMinus1, size, CopyPixelOperation.SourceCopy);
y = (float)Math.Sin(x);
g.DrawLine(p, -stepX, lastY, 0, y);
lastY = y;
}
}
Some additional info that may be helpful:
The origin in a picture box starts
out at the top left corner.
TranslateTransform allows you to
translate (i.e. move) the origin.
In the example, I translate it by
half the picture box's width and
half its height.
ScaleTransform changes the magnification of the picturebox - note that it even magnifies the width of the pen used to draw on the picturebox - this is why the pen's width is set to 0.01.
CopyFromScreen performs a bitblt. Its source point is relative to the screen, the destination is relative to the picturebox and the size of the rectangle to move disregards any transforms (like the scale and translation transforms we added).
Notice that the X coordinates in the DrawLine method are -stepx and 0. All drawing basically occurs right on the y axis (i.e. x = 0) and then CopyFromScreen moves the drawn portion to the left so that it "streams" off to the left.
Unless you are doing this as a learning experience, you may want to consider looking at the free Microsoft Chart Controls for .NET available here.
http://www.microsoft.com/downloads/details.aspx?FamilyID=130f7986-bf49-4fe5-9ca8-910ae6ea442c&displaylang=en#QuickInfoContainer
That being said, I would offer the following guidelines if you want to roll your own.
Create a user control to encapsulate the plot rendering rather than render directly on the form.
In your control, expose properties to get/set the data you wish to render and add any other properties you want to control the rendering (scaling, panning, colors, etc.)
In you control, either override the OnPaint method or create an event handler for the Paint event. These methods will have a PaintEventArgs object passed to them, which contains a Graphics object as a property. The methods of the Graphics object are used to render points, lines, etc onto the control when it needs to be painted. Most of the drawing operations require either a pen (outlines / lines) or a brush (filled areas). You can use stock objects for these operations (e.g. Pens.Black or Brushes.Red) or you can create your own (see documentation). If you create you own objects, make sure you dispose of them after using them (e.g. using the "using" statement or by calling Dispose).
There are a couple good books on GDI+. I suggest picking one up if you are going in deep.