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)
Related
I am now really desperate. I just can't get any further and in principle it shouldn't be that difficult. I tried solving it the last 2 weeks.
The following task/problem:
I have a canvas in my C# WPF project called “MarkPointsMap”, on which there are different kind of mark points (3 different kind of rectangles with different colors). One MarkPointsMap can contain round about 3000-4000 different MarkPoints (Children of that canvas) all over across the MarkPointsMap.
The positions of that MarkPoints are loaded from a .tab-file, which is converted into a list called “MarkPoints” and added as Children on the “MarkPointsMap” canvas after loading the respective .tab-file.
This a screenshot of the MarkPointsMap:
There are e.g. mostly small rectangles (the green ones), a few middle rectangles (the yellow ones) and two big rectangles (the red ones). Mulitple markpoints should be selected with a selection-rectangle. In my project I can already draw the selection rectangle, but the “if this.selectRect.Contains(MarkPointsMap.Children)” (see below) part does not work.
The selection-rectangle looks as follows:
The following method “drawMarkPoints”, which is called after loading the .tab file in the program, draws the markpoints on the canvas “MarkPointsMap”:
public void drawMarkPoints()
{
MarkPointsMap.Children.Clear();
foreach (MarkPoint markpoint in this.Marks.markpoints)
{
if (markpoint.type.relevant)
{
Rectangle markpointArea = new Rectangle();
markpointArea.Tag = markpoint;
//!!
SolidColorBrush mySolidColorBrush = new SolidColorBrush();
mySolidColorBrush.Color = markpoint.fillColor;
markpointArea.Fill = mySolidColorBrush;
markpointArea.StrokeThickness = 0.2;
markpointArea.Stroke = Brushes.Black;
markpointArea.Width = markpoint.symbol.Width;
markpointArea.Height = markpoint.symbol.Height;
//set the markpoints in canvas
Canvas.SetLeft(markpointArea, getCanvasCoordinates(markpoint.center).X - 0.5 * markpoint.symbol.Width);
Canvas.SetTop(markpointArea, getCanvasCoordinates(markpoint.center).Y - 0.5 * markpoint.symbol.Height);
MarkPointsMap.Children.Add(markpointArea);
}
}
}
The selection-rectangle is drawn in the canvas “MarkPointsMap” with the three Mouse-Events (MarkPointsMap_MouseDown, MarkPointsMap_MouseMove, MarkPointsMap_MouseUp):
public Rect itemRect;
public Rect selectRect;
private point anchorPoint;
private Rectangle manualDragRect = new Rectangle();
private void MarkPointsMap_MouseDown(object sender, MouseButtonEventArgs e)
{
if (e.ChangedButton == MouseButton.Left)
{
isLeftMouseButtonDownOnWindow = true;
anchorPoint = e.MouseDevice.GetPosition(MarkPointsMap);
MarkPointsMap.CaptureMouse();
e.Handled = true;
manualDragRect = new Rectangle
{
Stroke = Brushes.Red,
StrokeThickness = 2
};
MarkPointsMap.Children.Add(manualDragRect);
}
}
private void MarkPointsMap_MouseMove(object sender, MouseEventArgs e)
{
if (!MarkPointsMap.IsMouseCaptured)
return;
Point location = e.MouseDevice.GetPosition(MarkPointsMap);
double minX = Math.Min(location.X, anchorPoint.X);
double minY = Math.Min(location.Y, anchorPoint.Y);
double maxX = Math.Max(location.X, anchorPoint.X);
double maxY = Math.Max(location.Y, anchorPoint.Y);
Canvas.SetTop(manualDragRect, minY);
Canvas.SetLeft(manualDragRect, minX);
double height = maxY - minY;
double width = maxX - minX;
manualDragRect.Height = Math.Abs(height);
manualDragRect.Width = Math.Abs(width);
Point xy_2 = new Point((Canvas.GetLeft(manualDragRect) + 0.5 * width), (Canvas.GetTop(manualDragRect) + 0.5 * height));
this.selectRect = new Rect(xy_2.X, xy_2.Y, width, height);
}
private void MarkPointsMap _MouseUp(object sender, MouseButtonEventArgs e)
{
MarkPointsMap.ReleaseMouseCapture();
foreach (MarkPoint markpoint in this.Marks.markpoints)
{
Rect itemRect = new Rect(markpoint.center.X, markpoint.center.Y, markpoint.symbol.Width, markpoint.symbol.Height);
if (selectRect.Contains(itemRect))
{
MessageBox.Show("Test!"); // it does not reach this code, if I circle several markpoints with the red selection-rectangle called “selectRect”
}
}
}
Why doesn’t this work? I guess it has to do with the converting from rectangle (System.Windows.Shapes using derictive) to struct Rect : IFormattable.
The “if (selectRect.Contains(itemRect)”, which is not reached should in perspective color all mark points, which are inside the selection-rectangle in red, then compute the Point (x-Coordinate, y-Coordinate) of the middle-point of the selection-rectangle and add this middle point back to the original .tab-file.
Any ideas or hints, how I could continue? Thanks in advance.
Best regards!
It looks like your calculations are wrong. You are offsetting the selectRect. You are centering it relative to the rendered manualDragRect. This way you are testing a totally different area than the one you have defined by the rendered selection bounds.
Additionally, you are capturing the mouse on the Canvas but release the capture on some different control. It should be released on the MarkPointsMap too. Also be careful with marking mouse events as handled. If the input handling is not part of a custom control, you should generally avoid it.
You should consider to use an ItemsControl with a Canvas as panel. You can then move the drawing to the markup and define a DataTemplate for the MarkPoint data.
I recommend to use the original Rect returned from the manualDragRect shape. This means you should remove the selectRect field and related computations:
private void MarkPointsMap_MouseUp(object sender, MouseButtonEventArgs e)
{
MarkPointsMap.ReleaseMouseCapture();
foreach (MarkPoint markpoint in this.Marks.markpoints)
{
Rect itemRect = new Rect(markpoint.center.X, markpoint.center.Y, markpoint.symbol.Width, markpoint.symbol.Height);
Rect selectionBounds = manualDragRect.RenderedGeometry.Bounds;
selectionBounds.Offset(Canvas.GetLeft(manualDragRect), Canvas.GetTop(manualDragRect));
if (selectionBounds.Contains(itemRect))
{
MessageBox.Show("Test!");
}
// TODO::Calculate center of selectionBounds
Point selectionBoundsCenter = ...;
}
}
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;
I have a project at here. I used matrixtransform for zooming and panning. And they work as I expect. There are two trouble when I run it.
I found a something wrong when I zooming all of objects on canvas and then I panning them. The distance moving cursor were not same the distance of moving all of objects when panning. I think that my mistake is matrixtransform used in my project (maybe the factor of scale effect to matrixtransform of panning function), but I can't resolve it.
Code-behind:
-This is my code for panning function:
private void canvas01_MouseMove(object sender, MouseEventArgs e)
{
if (e.MiddleButton == MouseButtonState.Pressed)
{
canvas01.Cursor = Cursors.SizeAll; //set mouse shape is panning type
var lsPoint = e.GetPosition(this);
var res = lsPoint - firstPoint;
foreach (UIElement element in canvas01.Children)
{
var transform = element.RenderTransform as MatrixTransform;
var matrix = transform.Matrix;
matrix.TranslatePrepend(res.X, res.Y);
transform.Matrix = matrix;
}
//udate first point
firstPoint = lsPoint;
}
}
-And this is my code for zoom in/out function:
private void canvas01_MouseWheel(object sender, MouseWheelEventArgs e)
{
foreach (UIElement element in canvas01.Children)
{
//var element = sender as UIElement;
var position = e.GetPosition(element);
var transform = element.RenderTransform as MatrixTransform;
var matrix = transform.Matrix;
var scale = e.Delta >= 0 ? 1.1 : (1.0 / 1.1); // choose appropriate scaling factor
matrix.ScaleAtPrepend(scale, scale, position.X, position.Y);
transform.Matrix = matrix;
}
}
I want to display all of objects on canvas by a button (if an object is hidden by zooming). But I don't know how to write a zoom extent click function (you can see it in 56 line in MainWindow.xaml) in C#.
Please advise me in these cases and Thanks for your advice!
I believe what you need is a layout transform or a render transform. Here is a comparison of the two:
http://blog.scottlogic.com/2008/12/19/layouttransform-vs-rendertransform-whats-the-difference.html
They behave slightly different but they should serve your needs and resolve in your project.
I need to change the RenderTransformOrigin property of some annotation objects, so that they always rotate around the center (y) of the other side (x).
So let's say the user clicks on the right hand side of the object, then the rotation point would be (0.0, 0.5), but if the user wishes to change the rotation from the other side of the item, then he can click on the left hand side of it, in which case the rotation point becomes (1.0, 0.5).
The problem: the reason I am asking for help is that whenever this happens, the item jumps on the screen. How much it moves, depends on the angle of the item (if it is not rotated at all, then it won't move at all). The angle itself does not change during the movement, only the position. (I realise that it is a bit difficult to understand the problem without images, but due to stackoverflow's rules, I am not allowed to attach images to make things easier.) So this only happens when selecting the other side (e.g. clicking on the left hand side after the right hand side), and the movement/jump depends on the angle (the larger the angle, the larger the jump).
What I would like to do is to change the mentioned property for the rotation to happen around the selected edge of the object, without the object keep jumping up and down when changing side.
Thank you for your help!
Edit [2015-03-10_12-17-21]
class XYZ
{
...
private void RotateThumb_DragStarted(object sender, DragStartedEventArgs e) {
m_rotateTransform = m_designerItem.RenderTransform as RotateTransform;
if (m_rotateTransform == null) {
m_designerItem.RenderTransform = new RotateTransform(0);
m_rotateTransform = m_designerItem.RenderTransform as RotateTransform;
}
Point positionWithinAnnotation = Mouse.GetPosition(m_designerItem);
var selectedThumb = GetThumbPosition(m_designerItem, positionWithinAnnotation);
double relativeCenterX = ((selectedThumb == ThumbPosition.Left) ? (1.0D) : (0.0D));
double relativeCenterY = 0.5D;
m_designerItem.RenderTransformOrigin = new Point(relativeCenterX, relativeCenterY);
m_transformOrigin = m_designerItem.RenderTransformOrigin;
m_rotationPoint = m_designerItem.TranslatePoint(
new Point(relativeCenterX * m_designerItem.ActualWidth,
relativeCenterY * m_designerItem.ActualHeight),
m_canvas);
Point startPoint = Mouse.GetPosition(m_canvas);
m_startVector = Point.Subtract(startPoint, m_rotationPoint);
}
private void handleRotate(DragDeltaEventArgs e) {
Point currentPoint = Mouse.GetPosition(m_canvas);
Vector deltaVector = Point.Subtract(currentPoint, m_rotationPoint);
double angle = Vector.AngleBetween(m_startVector, deltaVector);
m_rotateTransform.Angle += Math.Round(angle, 0);
m_startVector = deltaVector;
m_designerItem.InvalidateMeasure();
}
...
}
Without a good, minimal, complete code example it's difficult to know for sure what you need to do to fix the code.
That said, from the description it sounds to me like you should be using the RotateTransform.CenterX and RotateTransform.CenterY properties, instead of setting RenderTransformOrigin.
For example:
private void RotateThumb_DragStarted(object sender, DragStartedEventArgs e) {
m_rotateTransform = m_designerItem.RenderTransform as RotateTransform;
if (m_rotateTransform == null) {
m_designerItem.RenderTransform = new RotateTransform();
m_rotateTransform = m_designerItem.RenderTransform as RotateTransform;
}
Point positionWithinAnnotation = Mouse.GetPosition(m_designerItem);
var selectedThumb = GetThumbPosition(m_designerItem, positionWithinAnnotation);
double relativeCenterX = ((selectedThumb == ThumbPosition.Left) ? (1.0D) : (0.0D));
double relativeCenterY = 0.5D;
m_rotateTransform.CenterX = relativeCenterX;
m_rotateTransform.CenterY = relativeCenterY;
// Without a complete example, I'm not sure I know what this part is
// supposed to be doing. I think it's fine, but maybe it needs to be
// fixed too?
m_rotationPoint = m_designerItem.TranslatePoint(
new Point(relativeCenterX * m_designerItem.ActualWidth,
relativeCenterY * m_designerItem.ActualHeight),
m_canvas);
Point startPoint = Mouse.GetPosition(m_canvas);
m_startVector = Point.Subtract(startPoint, m_rotationPoint);
}
I'm busy with a small application in which I want to display information at the location of the cursor when it hoovers over a Canvas. The Canvas in question is a custom one (inherited from Canvas) which provides functionality to add DrawingVisuals (as shown in basically every tutorial on displaying large amounts of geometric shapes on a canvas).
I would like to display a vertical line and horizontal line as well as the local coordinates (p in the code below) which are directly derived from the canvas coordinates (v). At the moment I'm rendering these objects at position (0,0) and use offset during the OnMouseMove event to update their location.
The horizontal and vertical lines are rendered in the DrawingVisual _cursor and the location in local y,z-coordinates in _info.
private void oCanvas_MouseMove(object sender, MouseEventArgs e)
{
#region 1. Get location data
System.Windows.Vector v = (System.Windows.Vector)e.GetPosition(oCanvas);
// point in YZ coordinates
BSMath.DoubleXY p = new BSMath.DoubleXY();
p.X = (oCanvas.OriginY - v.Y) / oCanvas.ZoomFactor;
p.Y = (oCanvas.OriginX - v.X) / oCanvas.ZoomFactor;
#endregion
#region 2. Update cursor and info
if (oSettings.ShowInformation)
{
_info.Info = p.X.ToString("0.0") + " | " + p.Y.ToString("0.0");
_info.Render(0, 0);
_info.Visual.Offset = v;
}
// move cursor
_cursor.Visual.Offset = v;
}
Using the mousemove event seems to be creating a lot of overhead and I can see that there are issues tracking the mouse movements when I move the mouse quickly.
Can anyone recommend a better way of creating the same effect?
example http://www.iccg.be/test/images/canvas.jpg
Edit:
I investigated it a bit further and the problem seems to occur when the resolution of the canvas is bigger. If it is a 600x400 canvas then there is no delay, but when it is around 1000x800 I get the problem with delays when hoovering. The performance also improves if I use user drawn crosshairs instead of the lines that have the full width/ height of the canvas.
I recently have built something similar and haven't had any performance issues.
Did it the very simple way by adding the stuff directly on the canvas.
The drawn items are in a second canvas behind the mouse position canvas. Both reside in a Grid.
This is for sure not the most sophisticated way to solve this, but it works quite well for me.
Here's the code:
private Point _previous;
private Point _current;
private Line _xLine;
private Line _yLine;
private TextBlock _displayTextBlock;
private void Canvas_MouseMove(object sender, MouseEventArgs e)
{
_current = e.GetPosition(myCanvas);
if (_previous != _current)
{
if (_xLine == null)
{
_xLine = new Line() {X1 = 0, X2 = myCanvas.ActualWidth, Stroke = new SolidColorBrush(Colors.Black)};
_yLine = new Line() {Y1 = 0, Y2 = myCanvas.ActualHeight, Stroke = new SolidColorBrush(Colors.Black)};
_displayTextBlock = new TextBlock();
myCanvas.Children.Add(_xLine);
myCanvas.Children.Add(_yLine);
myCanvas.Children.Add(_displayTextBlock);
}
_displayTextBlock.SetValue(Canvas.TopProperty, _current.Y);
_displayTextBlock.SetValue(Canvas.LeftProperty, _current.X);
_displayTextBlock.Text = _current.X.ToString() + " | " + _current.Y.ToString();
_xLine.Y1 = _current.Y;
_xLine.Y2 = _current.Y;
_yLine.X1 = _current.X;
_yLine.X2 = _current.X;
_previous = _current;
}
}