I have arc in a canvas drawn from Path with the following details. I want to get Point(115,225). Please see screenshot to get more details. Please help me getting the formula to get to point (115,225).
startX=250
startY=250
ArcSegment Size (70,70)
ArcSegment Point (250,200)
Computation
var meanX=(startX+startX)/2-rX;//(250+250)/2-70=180
var meanY=(startY+ArcSegment.Point.Y)/2-rY;//(250+200)/2-70=225
//center Point (180,225)
//What is the formula if I want to get Point(115,225)
XAML
<Canvas Name="canvas" Background="White" Opacity="99">
<Path Stroke="Blue" MouseLeftButtonDown="Path_MouseLeftButtonDown" >
<Path.Data>
<PathGeometry>
<PathFigureCollection>
<PathFigure StartPoint="250,250" IsClosed="True">
<ArcSegment Size="70,70" IsLargeArc="True" SweepDirection="Clockwise" Point="250,200"/>
</PathFigure>
</PathFigureCollection>
</PathGeometry>
</Path.Data>
</Path>
</Canvas>
C#
private void Path_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var curve = sender as Path;
var geometry = curve.Data as PathGeometry;
var figure = geometry.Figures.FirstOrDefault();
var arcSegment = figure.Segments.FirstOrDefault() as ArcSegment;
var startX = figure.StartPoint.X;
var yStart = figure.StartPoint.Y;
var startAngle = arcSegment.Point.X;
var sweepAngle = arcSegment.Point.Y;
var rX = arcSegment.Size.Width;
var rY = arcSegment.Size.Height;
var endAngle = startAngle + sweepAngle;
var meanX = (startX + startAngle) / 2 - rX;
var meanY = (yStart + sweepAngle) / 2 - rY;
}
Screenshot
After hours of research, I found out everything is here.
var geometry = path.Data as PathGeometry;
var leftX=geometry.Bounds.Location.X;
var topY=geometry.Bounds.Location.Y;
Related
so i have this Path and i need to place it rotated by its center in a coordinate. so i have this static Path in .xaml
<Path Stroke="Black" RenderTransformOrigin="0.379,0.494" Canvas.Left="30" Canvas.Top="0">
<Path.RenderTransform>
<TransformGroup>
<RotateTransform Angle="-38.28"/>
<TranslateTransform X="-30" Y="-30"/>
</TransformGroup>
</Path.RenderTransform>
<Path.Data>
<PathGeometry>
<PathGeometry.Figures>
<PathFigureCollection>
<PathFigure StartPoint="75,30">
<PathFigure.Segments>
<PathSegmentCollection>
<LineSegment Point="0,0"/>
<LineSegment Point="12,30"/>
<LineSegment Point="0, 60"/>
<LineSegment Point="75, 30"/>
</PathSegmentCollection>
</PathFigure.Segments>
</PathFigure>
</PathFigureCollection>
</PathGeometry.Figures>
</PathGeometry>
</Path.Data>
</Path>
but then i create it from code the same way and it just doesn't wanna center, it just starts rotating around the point
{
PathGeometry pathGeom = new PathGeometry();
Sprite = new Path
{
Data = pathGeom,
RenderTransformOrigin = new Point(0.379, 0.5),
Stroke = new SolidColorBrush(Color.FromArgb(120, 0, 0, 0)),
Fill = new SolidColorBrush(Color.FromArgb(120, 30, 200, 7))
};
var pf = new PathFigure { StartPoint = new Point(75, 30) };
pf.Segments.Add(new LineSegment { Point = new Point(0, 0) });
pf.Segments.Add(new LineSegment { Point = new Point(12, 30) });
pf.Segments.Add(new LineSegment { Point = new Point(0, 60) });
pf.Segments.Add(new LineSegment { Point = new Point(75, 30) });
pathGeom.Figures.Add(pf);
UpdateRotation();
canvas.Children.Add(Sprite);
}
private void UpdateRotation()
{
Sprite.RenderTransform = new TransformGroup
{
Children = new TransformCollection {
new RotateTransform(Road.Angle + (Direction == -1 ? 90 : 0), -30, -30) , <-- i tried doing this
new TranslateTransform(-30, -30), <-- and this separately, but they both didn't work
}
};
Canvas.SetLeft(Sprite, Loc.X);
Canvas.SetTop(Sprite, Loc.Y);
}
here's what the static path looks like:what it looks like
You may simplify your drawing by defining the pivot point as origin - with coordinates (0,0). There is no need for a TranslateTransform or a centered RotateTransform. You would also not have to create new transforms on each direction update. Just set the Angle property of the existing RotateTransform.
private Path Sprite { get; }
public MainWindow()
{
InitializeComponent();
Sprite = new Path
{
Data = Geometry.Parse("M0,0 L-12,-30 63,0 -12,30Z"),
// or Data = Geometry.Parse("M-13,0 L-25,-30 50,0 -25,30Z"),
// or whatever corresponds to the original RenderTransformOrigin
RenderTransform = new RotateTransform(),
Stroke = new SolidColorBrush(Color.FromArgb(120, 0, 0, 0)),
Fill = new SolidColorBrush(Color.FromArgb(120, 30, 200, 7))
};
canvas.Children.Add(Sprite);
UpdatePosition(100, 100, 45); // for example
}
private void UpdatePosition(double x, double y, double direction)
{
((RotateTransform)Sprite.RenderTransform).Angle = direction;
Canvas.SetLeft(Sprite, x);
Canvas.SetTop(Sprite, y);
}
I know there are several posts on stack and others websites, but it seems I still make something wrong. When I zoom with MouseWheel event, the zoom is always not centered, but the left side of my canvas always stays on the left on my ViewBox, so when I zoom in, I only can see the left of my canvas.
XAML code :
<Grid x:Name="MainGrid">
<Viewbox x:Name="ViewBoxDessin" Stretch="None" HorizontalAlignment="Center" VerticalAlignment="Center">
<Canvas x:Name="monDessin" Background="WhiteSmoke" MouseWheel="monDessin_MouseWheel">
<Canvas.LayoutTransform>
<ScaleTransform x:Name="st" ScaleX="1" ScaleY="-1" CenterX=".5" CenterY=".5" />
</Canvas.LayoutTransform>
</Canvas>
</Viewbox>
<Viewbox x:Name="ViewBoxDessin2" Stretch="None">
<Canvas x:Name="monDessin2">
<Canvas.LayoutTransform>
<ScaleTransform x:Name="st2" ScaleX="1" ScaleY="1" CenterX=".5" CenterY=".5" />
</Canvas.LayoutTransform>
</Canvas>
</Viewbox>
</Grid>
Code behind
public AfficheGraphiquePiece()
{
InitializeComponent();
MakeMyDrawing();
ViewBoxDessin.Width = System.Windows.SystemParameters.PrimaryScreenWidth;
ViewBoxDessin.Height = System.Windows.SystemParameters.PrimaryScreenHeight;
double ech_x = monDessin.Width / System.Windows.SystemParameters.PrimaryScreenWidth;
double ech_y = monDessin.Height / System.Windows.SystemParameters.PrimaryScreenHeight;
double ech = Math.Min(ech_x, ech_y);
this.ech_full = ech;
st.ScaleX = ech;
st.ScaleY = -ech;
st2.ScaleX = ech;
st2.ScaleY = ech;
}
private void monDessin_MouseWheel(object sender, MouseWheelEventArgs e)
{
double zoom = e.Delta > 0 ? 1.1 : 0.9;
if(st.ScaleX<this.ech_full*1.1 && zoom<1)
{
st.ScaleX = this.ech_full;
st.ScaleY = -this.ech_full;
}
else
{
st.ScaleX *= zoom;
st.ScaleY *= zoom;
double coor_x = Mouse.GetPosition(monDessin).X;
double coor_y = Mouse.GetPosition(monDessin).Y;
st.CenterX = coor_x;
st.CenterY = coor_y;
}
}
Excuse me, didn't remove some code, and it could make confusion, just replaced it by a function MakeMyDrawing()
Well, after Clemens advise, and help of that link for matrix use, I could do the following :
XAML :
<Grid x:Name="MainGrid">
<Canvas x:Name="monDessin" Background="WhiteSmoke" MouseWheel="monDessin_MouseWheel" MouseLeftButtonDown="image_MouseLeftButtonDown" MouseMove="image_MouseMove" MouseLeftButtonUp="image_MouseLeftButtonUp" MouseLeave="image_MouseLeave" >
<Canvas.RenderTransform >
<MatrixTransform/>
</Canvas.RenderTransform>
</Canvas>
<Canvas x:Name="monDessin2">
<Canvas.RenderTransform >
<MatrixTransform/>
</Canvas.RenderTransform>
</Canvas>
</Grid>
Code behind
public AfficheGraphiquePiece(Repere rep)
{
InitializeComponent();
ClassGraphique monGraphe = new ClassGraphique(monDessin);
ClassGraphique monGraphe2 = new ClassGraphique(monDessin2);
MakeMyDrawing();
double screenWidth = System.Windows.SystemParameters.PrimaryScreenWidth;
double screenHeight = System.Windows.SystemParameters.PrimaryScreenHeight;
double ech_x = screenWidth/ monDessin.Width ;
double ech_y = screenHeight/ monDessin.Height;
double ech = Math.Min(ech_x, ech_y)*0.9;
this.ech_full = ech;
this.echelleNow = this.ech_full;
MatrixTransform maTrans =(MatrixTransform)monDessin.RenderTransform;
var mat = maTrans.Matrix;
mat.ScaleAt(ech, -ech, 0.1* screenWidth, (screenHeight-monDessin.Height*ech)/2-0.1*screenHeight);
MatrixTransform maTrans2 = (MatrixTransform)monDessin2.RenderTransform;
var mat2 = maTrans2.Matrix;
mat2.ScaleAt(ech, ech, 0.1 * screenWidth, screenHeight*ech-((screenHeight - monDessin.Height * ech) / 2 - 0.1 * screenHeight));
maTrans.Matrix = mat;
maTrans2.Matrix = mat2;
}
private void monDessin_MouseWheel(object sender, MouseWheelEventArgs e)
{
try
{
var position = e.GetPosition(monDessin);
MatrixTransform transform = (MatrixTransform)monDessin.RenderTransform;
MatrixTransform transform2 = (MatrixTransform)monDessin2.RenderTransform;
var matrix = transform.Matrix;
var matrix2 = transform2.Matrix;
var scale = e.Delta >= 0 ? 1.1 : (1.0 / 1.1);
this.echelleNow *= scale;
matrix.ScaleAtPrepend(scale, scale, position.X, position.Y);
matrix2.ScaleAtPrepend(scale, scale, position.X,monDessin.Height-position.Y);
monDessin.RenderTransform = new MatrixTransform(matrix);
monDessin2.RenderTransform = new MatrixTransform(matrix2);
}
catch { }
}
Here is a very basic example for zooming and panning a Canvas with fixed initial size. The MatrixTransform in the RenderTransform of the inner Canvas provides the necessary transformations, while the outer Canvas handles mouse input and sets an initial scaling.
<Canvas Background="Transparent"
SizeChanged="ViewportSizeChanged"
MouseLeftButtonDown="ViewportMouseLeftButtonDown"
MouseLeftButtonUp="ViewportMouseLeftButtonUp"
MouseMove="ViewportMouseMove"
MouseWheel="ViewportMouseWheel">
<Canvas x:Name="canvas" Width="1000" Height="600">
<Canvas.RenderTransform>
<MatrixTransform x:Name="transform"/>
</Canvas.RenderTransform>
<Ellipse Fill="Red" Width="100" Height="100" Canvas.Left="100" Canvas.Top="100"/>
<Ellipse Fill="Green" Width="100" Height="100" Canvas.Right="100" Canvas.Bottom="100"/>
</Canvas>
</Canvas>
Code behind:
private Point? mousePos;
private void ViewportSizeChanged(object sender, SizeChangedEventArgs e)
{
((MatrixTransform)canvas.RenderTransform).Matrix = new Matrix(
e.NewSize.Width / canvas.ActualWidth,
0, 0,
e.NewSize.Height / canvas.ActualHeight,
0, 0);
}
private void ViewportMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var viewport = (UIElement)sender;
viewport.CaptureMouse();
mousePos = e.GetPosition(viewport);
}
private void ViewportMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
((UIElement)sender).ReleaseMouseCapture();
mousePos = null;
}
private void ViewportMouseMove(object sender, MouseEventArgs e)
{
if (mousePos.HasValue)
{
var pos = e.GetPosition((UIElement)sender);
var matrix = transform.Matrix;
matrix.Translate(pos.X - mousePos.Value.X, pos.Y - mousePos.Value.Y);
transform.Matrix = matrix;
mousePos = pos;
}
}
private void ViewportMouseWheel(object sender, MouseWheelEventArgs e)
{
var pos = e.GetPosition((UIElement)sender);
var matrix = transform.Matrix;
var scale = e.Delta > 0 ? 1.1 : 1 / 1.1;
matrix.ScaleAt(scale, scale, pos.X, pos.Y);
transform.Matrix = matrix;
}
I have been struggling for two weeks for this problem . I am applying dragging and scaling to an image inside canvas.Dragging works fine and is limiting inside canvas IsBoundary functions but when I am applying scaling its drag area changes . If increases scaling with mouse drag area increases also and whem I make it shrink in size drag area also shrinks.Help me to solve this problem of limiting scaling
Thanks.
Here is my code link
sample
I think I understand your question. When you scale an item in a canvas the translation needs to account for the change in scale. Is that right?
Assuming this XAML:
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Border Width="500"
Height="500"
BorderBrush="White"
BorderThickness="1">
<Canvas x:Name="MyCanvas">
<Rectangle x:Name="MyRectangle"
Width="50"
Height="50"
Fill="CornflowerBlue">
<Rectangle.RenderTransform>
<CompositeTransform TranslateX="225" TranslateY="225" />
</Rectangle.RenderTransform>
</Rectangle>
</Canvas>
</Border>
</Grid>
Try this code-behind:
void MainPage_Loaded(object sender, RoutedEventArgs args)
{
MyRectangle.ManipulationMode =
ManipulationModes.TranslateX
| ManipulationModes.TranslateY;
var transform = MyRectangle.RenderTransform as CompositeTransform;
var reposition = new Action<double, double>((x, y) =>
{
var size = new Size(MyRectangle.ActualWidth * transform.ScaleX, MyRectangle.ActualHeight * transform.ScaleY);
var location = MyRectangle.TransformToVisual(MyRectangle).TransformPoint(new Point(0, 0));
var minX = -location.X;
var maxX = MyCanvas.ActualWidth - size.Width;
var newX = Within(x, minX, maxX);
transform.TranslateX = Within(newX, minX, maxX);
var minY = -location.Y;
var maxY = MyCanvas.ActualHeight - size.Height;
var newY = Within(y, minY, maxX);
transform.TranslateY = Within(newY, minY, maxY);
});
MyRectangle.ManipulationDelta += (s, e) =>
{
var newX = transform.TranslateX + e.Delta.Translation.X;
var newY = transform.TranslateY + e.Delta.Translation.Y;
reposition(newX, newY);
};
MyRectangle.PointerWheelChanged += (s, e) =>
{
// require control
if (Window.Current.CoreWindow.GetKeyState(VirtualKey.Control)
== Windows.UI.Core.CoreVirtualKeyStates.None)
return;
// ignore horizontal
var props = e.GetCurrentPoint(MyRectangle).Properties;
if (props.IsHorizontalMouseWheel)
return;
// apply scale
var newScale = transform.ScaleX + (double)props.MouseWheelDelta * .001;
transform.ScaleX = transform.ScaleY = newScale;
// reposition
reposition(transform.TranslateX, transform.TranslateY);
};
}
public double Within(double value, double min, double max)
{
if (value <= min)
return min;
else if (value >= max)
return max;
else
return value;
}
I hope this helps.
Note: Since I am not on a touch machine right now, I implemented the mouse wheel to scale. But you can modify the code as you want. The logic would be identical.
Best of luck!
NOTE: I'm not looking for a XAML Solution.
I'm having trouble figuring out how to attach a line to two shapes. The best visible representation of what I'm looking for would be two balls attached to both ends of a straight stick. The problem I'm having is on how to display the line which is dependent on both the positions of ball01's and ball02's center position. As of now, both balls display as I want it, but when ball02 moves away from ball01 (ball02 starts off centered on ball01), the line is not visible.
ball01 = new Ellipse() { Height = BIG_SIZE, Width = BIG_SIZE };
ball01.Fill = baseBrush;
ball01.SetValue(RenderOptions.EdgeModeProperty, EdgeMode.Aliased);
setBall01X(e.GetPosition(canvas).X - (BIG_SIZE / 2));
setBall01Y(e.GetPosition(canvas).Y - (BIG_SIZE / 2));
Canvas.SetLeft(ball01, getBall01X());
Canvas.SetTop(ball01, getBall01Y());
canvas.Children.Add(ball01);
ball02 = new Ellipse() { Height = SMALL_SIZE, Width = SMALL_SIZE };
ball02.Fill = childBrush;
ball02.SetValue(RenderOptions.EdgeModeProperty, EdgeMode.Aliased);
setBall02X(e.GetPosition(canvas).X - (SMALL_SIZE / 2));
setBall02Y(e.GetPosition(canvas).Y - (SMALL_SIZE / 2));
Canvas.SetLeft(ball02, getBall02X());
Canvas.SetTop(ball02, getBall02Y());
canvas.Children.Add(ball02);
// line's X's and Y's are to point to the center of both balls
// Regardless of where the balls move.
line01 = new Line()
{
X1 = getBall01X() + (BIG_SIZE / 2),
Y1 = getBall01Y() + (BIG_SIZE / 2),
X2 = getBall02X() + (SMALL_SIZE / 2),
Y2 = getBall02Y() + (SMALL_SIZE / 2)
};
line01.Fill = baseBrush;
line01.SnapsToDevicePixels = true;
line01.SetValue(RenderOptions.EdgeModeProperty, EdgeMode.Aliased);
line01.StrokeThickness = 2;
// Canvas.Set???
canvas.Children.Add(line01);
Instead of using Ellipse and Line controls and positioning them by Canvas.Left and Canvas.Top you may prefer to use three Path controls with appropriate geometries. Especially the EllipseGeometry provides far easier handling of its center point, compared to an Ellipse control.
private EllipseGeometry ball1Geometry = new EllipseGeometry();
private EllipseGeometry ball2Geometry = new EllipseGeometry();
private LineGeometry lineGeometry = new LineGeometry();
public MainWindow()
{
InitializeComponent();
canvas.Children.Add(new Path
{
Stroke = Brushes.Black,
Data = ball1Geometry
});
canvas.Children.Add(new Path
{
Stroke = Brushes.Black,
Data = ball2Geometry
});
canvas.Children.Add(new Path
{
Stroke = Brushes.Black,
Data = lineGeometry
});
}
...
private void UpdateDrawing(
Point ball1Position, double ball1Radius,
Point ball2Position, double ball2Radius)
{
ball1Geometry.RadiusX = ball1Radius;
ball1Geometry.RadiusY = ball1Radius;
ball1Geometry.Center = ball1Position;
ball2Geometry.RadiusX = ball2Radius;
ball2Geometry.RadiusY = ball2Radius;
ball2Geometry.Center = ball2Position;
lineGeometry.StartPoint = ball1Position;
lineGeometry.EndPoint = ball2Position;
}
Then you may also prefer to do it the WPF way and create the Paths in XAML:
<Canvas>
<Path Stroke="Black">
<Path.Data>
<EllipseGeometry x:Name="ball1Geometry"/>
</Path.Data>
</Path>
<Path Stroke="Black">
<Path.Data>
<EllipseGeometry x:Name="ball2Geometry"/>
</Path.Data>
</Path>
<Path Stroke="Black">
<Path.Data>
<LineGeometry x:Name="lineGeometry"/>
</Path.Data>
</Path>
</Canvas>
I think you'd better draw in two steps :
1) add the 3 figures and store them (when building your window).
2) update the coordinates in an animating loop.
It will be faster / handier than clearing/filling the canvas on each frame.
For your line issue : hook it on circle 1's center, and have it go to circle 2's center :
// new line coordinates :
X1 = Y1 = 0
X2 = Balle02X - Balle01X + ( SMALL_SIZE / 2 )
Y2 = Balle02Y - Balle01Y + ( SMALL_SIZE / 2 )
Canvas.SetTop ( line01, Balle01X + (BIG_SIZE / 2) )
Canvas.SetLeft( line01, Balle01Y + (BIG_SIZE / 2) )
I'm using the MS asp.net charting controls. And I'm using the radar chart to draw some values, but for some reason, the lines of the X-axis doesn't really meet in the middle.
I have set the LineWidth = 1, but the line still takes like 2 pixels and some of the markers are totally off, or maybe it's the line that's totally off.
Maybe my text is a little bit off as well, so please see picture and hopefully you'll understand my problem. =)
Code that generates the chart:
// Populate series data
Chart chart1 = new Chart();
chart1.ChartAreas.Add(new ChartArea("ChartArea1"));
chart1.Height = new Unit(380);
chart1.Width = new Unit(880);
//chart1.AntiAliasing = AntiAliasingStyles.Graphics;
//chart1.BackColor = Color.Transparent;
chart1.Customize += new EventHandler(Chart_Customize);
// Show as 3D
chart1.ChartAreas["ChartArea1"].Area3DStyle.Enable3D = false;
chart1.ChartAreas["ChartArea1"].AxisY.IntervalAutoMode
= IntervalAutoMode.FixedCount;
chart1.ChartAreas["ChartArea1"].AxisY.Interval = 10;
chart1.ChartAreas["ChartArea1"].AxisY.Maximum = 100;
chart1.ChartAreas["ChartArea1"].AxisY.IsReversed = true;
chart1.ChartAreas[0].AxisY.LineWidth = 1;
chart1.ChartAreas[0].AxisY.MajorGrid.LineColor = Color.Gray;
chart1.ChartAreas[0].AxisY.LineColor = Color.Gray;
chart1.ChartAreas[0].AxisY.MajorTickMark.Enabled = false;
List<string> names = new List<string>();
int namecounter = 1;
foreach (var p in Model.Participants)
{
if (SessionHandle.ShowNamesInDiagrams)
names.Add(p.Person.Name);
else
names.Add(namecounter.ToString());
namecounter++;
}
#region firstresult
if (SessionHandle.ShowFirstResult)
{
chart1.Series.Add(new Series("FirstResult"));
List<double> firstresult = new List<double>();
foreach (var p in Model.Participants)
{
var resultSummary = from r in Model.ResultSummary
where r.userID == p.ParentID && Model
.Modules
.Where(x => x.hasResult)
.ToList()
.Exists(x => x.ID == r.moduleID)
select r;
firstresult.Add(resultSummary.Sum(x => x.scorePercent)
/ Model.Modules.Where(x => x.hasResult).Count());
}
chart1.Series["FirstResult"].Points.DataBindXY(names, firstresult);
// Set radar chart type
chart1.Series["FirstResult"].ChartType = SeriesChartType.Radar;
// Set radar chart style (Area, Line or Marker)
chart1.Series["FirstResult"]["RadarDrawingStyle"] = "Marker";
chart1.Series["FirstResult"].Color = Color.DarkBlue;
chart1.Series["FirstResult"].MarkerImage
= Url.Content("~/Content/Images/firstresult.png");
// Set circular area drawing style (Circle or Polygon)
chart1.Series["FirstResult"]["AreaDrawingStyle"] = "Circle";
// Set labels style (Auto, Horizontal, Circular or Radial)
chart1.Series["FirstResult"]["CircularLabelsStyle"] = "Horizontal";
}
#endregion
WPF coordinates refer to the center of the pixel, not the corners, so try adding 0.5 to all your coordinates. To show this is the case consider the following xaml:
<Canvas>
<Line X1="50" Y1="50" X2="100" Y2="50" Stroke="Black" StrokeThickness="1" />
<Line X1="50" Y1="50" X2="50" Y2="100" Stroke="Black" StrokeThickness="1" />
<Line X1="50" Y1="50" X2="100" Y2="100" Stroke="Black" StrokeThickness="1" />
</Canvas>
Here it is rendered normally and then with a 0.5 pixel offset applied to each coordinate: