Zoom out lags on Xamarin.Form implementation - c#

I need to zoom in/out an image in a Xamarin.Form App.
My problem is that when I made the pinch gesture to zooming out the image it starts to lag and flashing all over the display. Instead the zoom in works perfectly.
I had already follow the official guide (https://learn.microsoft.com/it-it/xamarin/xamarin-forms/app-fundamentals/gestures/pinch) and any sort of forum/comunity but I wasn't able to reach a working answer.
Could anyone help me?
I copy/paste here the section of the code. I localize the bug where I try to set the currentScale variable.
NOTE: I have other methods into the class but they don't manage images or related properties so I don't copy/paste them.
public partial class ResizeFoto : ContentPage
{
double currentScale = 1;
double startScale = 1;
double minScale = 0;
double maxScale = 2.5;
private void PinchGestureRecognizer_PinchUpdated(object sender,PinchGestureUpdatedEventArgs e){
switch (e.Status)
{
case GestureStatus.Started:
startScale = imgUserFoto.Scale;
break;
case GestureStatus.Running:
// LAS test 4
//Input gesture:
//Definition: "The distance between the user's digits, divided by the
//last reported distance between the user's digits in the pinch gesture"
// --> ZOOM IN = e.Scale > 1
// --> ZOOM OUT = e.Scale < 1
//ZOOM IN --> works good
if (e.Scale > 1)
{
currentScale += (e.Scale - 1) * startScale;
currentScale = Math.Min(currentScale, maxScale);
imgUserFoto.Scale = currentScale;
}
//ZOOM OUT --> not working, bug
else if (e.Scale < 1)
{
//HERE MAYBE THE BUG
currentScale = minScale + (e.Scale - 1) * startScale;
//also tried: currentScale = (e.Scale - 1) * startScale;
currentScale = Math.Max(minScale, currentScale);
imgUserFoto.Scale = currentScale;
}
}
break;
case GestureStatus.Completed:
break;
}
}
}

Recently I had to do something similar and I almost searched the whole earth and found nothing then I came up with this:
using System;
using Xamarin.Forms;
using FFImageLoading.Forms;
public class ZoomImage : CachedImage
{
private const double MIN_SCALE = 1;
private const double MAX_SCALE = 4;
private const double OVERSHOOT = 0.15;
private double StartScale, LastScale;
private double StartX, StartY;
public ZoomImage()
{
var pinch = new PinchGestureRecognizer();
pinch.PinchUpdated += OnPinchUpdated;
GestureRecognizers.Add(pinch);
var pan = new PanGestureRecognizer();
pan.PanUpdated += OnPanUpdated;
GestureRecognizers.Add(pan);
var tap = new TapGestureRecognizer { NumberOfTapsRequired = 2 };
tap.Tapped += OnTapped;
GestureRecognizers.Add(tap);
Scale = MIN_SCALE;
TranslationX = TranslationY = 0;
AnchorX = AnchorY = 0;
}
protected override SizeRequest OnMeasure(double widthConstraint, double heightConstraint)
{
Scale = MIN_SCALE;
TranslationX = TranslationY = 0;
AnchorX = AnchorY = 0;
return base.OnMeasure(widthConstraint, heightConstraint);
}
private void OnTapped(object sender, EventArgs e)
{
if (Scale > MIN_SCALE)
{
this.ScaleTo(MIN_SCALE, 250, Easing.CubicInOut);
this.TranslateTo(0, 0, 250, Easing.CubicInOut);
}
else
{
AnchorX = AnchorY = 0.5; //TODO tapped position
this.ScaleTo(MAX_SCALE, 250, Easing.CubicInOut);
}
}
private void OnPanUpdated(object sender, PanUpdatedEventArgs e)
{
switch (e.StatusType)
{
case GestureStatus.Started:
StartX = (1 - AnchorX) * Width;
StartY = (1 - AnchorY) * Height;
break;
case GestureStatus.Running:
AnchorX = Clamp(1 - (StartX + e.TotalX) / Width, 0, 1);
AnchorY = Clamp(1 - (StartY + e.TotalY) / Height, 0, 1);
break;
}
}
private void OnPinchUpdated(object sender, PinchGestureUpdatedEventArgs e)
{
switch (e.Status)
{
case GestureStatus.Started:
LastScale = e.Scale;
StartScale = Scale;
AnchorX = e.ScaleOrigin.X;
AnchorY = e.ScaleOrigin.Y;
break;
case GestureStatus.Running:
if (e.Scale < 0 || Math.Abs(LastScale - e.Scale) > (LastScale * 1.3) - LastScale)
{ return; }
LastScale = e.Scale;
var current = Scale + (e.Scale - 1) * StartScale;
Scale = Clamp(current, MIN_SCALE * (1 - OVERSHOOT), MAX_SCALE * (1 + OVERSHOOT));
break;
case GestureStatus.Completed:
if (Scale > MAX_SCALE)
this.ScaleTo(MAX_SCALE, 250, Easing.SpringOut);
else if (Scale < MIN_SCALE)
this.ScaleTo(MIN_SCALE, 250, Easing.SpringOut);
break;
}
}
private T Clamp<T>(T value, T minimum, T maximum) where T: IComparable
{
if (value.CompareTo(minimum) < 0)
return minimum;
else if (value.CompareTo(maximum) > 0)
return maximum;
else
return value;
}
}
What this does:
Pinch zoom, Pan and Swipe movements together with double tap centre zoom and un-zoom
Note: I have used FFimageLoading's CachedImage because I needed to cache the data in case you do not intend this replace CachedImage with Xamarin.Forms.Image

Related

Xamarin add Scale/Rotate to ContentView

I have a question.
I created a CustomView where I can drag/drop the view inside, but now I also want to add a scale/rotate function to it. Now here is the OnTouchEvent that created to move the view inside:
public override bool OnTouchEvent(MotionEvent e)
{
float x = e.RawX;
float y = e.RawY;
var dragView = Element as DraggableView.DraggableView;
switch (e.Action)
{
case MotionEventActions.Down:
if (dragView.DragMode == DragMode.Touch)
{
if (!touchedDown)
{
if (firstTime)
{
originalX = GetX();
originalY = GetY();
firstTime = false;
}
dragView.DragStarted();
}
TextMoved = false;
touchedDown = true;
stopwatch.Start();
}
dX = x - this.GetX();
dY = y - this.GetY();
break;
case MotionEventActions.Move:
if (touchedDown)
{
if (dragView.DragDirection == DragDirectionType.All || dragView.DragDirection == DragDirectionType.Horizontal)
{
SetX(x - dX);
}
if (dragView.DragDirection == DragDirectionType.All || dragView.DragDirection == DragDirectionType.Vertical)
{
SetY(y - dY);
}
TextMoved = true;
}
break;
case MotionEventActions.Up:
touchedDown = false;
if(TextMoved == true)
{
dragView.DragEnded();
}
else
{
MessagingCenter.Send<object, DraggableView.DraggableView>(this, "EditSelectedText", dragView);
}
break;
case MotionEventActions.Cancel:
touchedDown = false;
break;
}
return base.OnTouchEvent(e);
}
But now I also need the Scale/Rotate function.
The problem is that I already created it for my skiasharp bitmaps, but this isn't skiasharp, so I can't use that.
How can I implement this function in a OnTouchEvent without skiasharp?
There is a xamarin-forms-samples about ScaleAndRotate , however it is not related to TouchEvent .I found a way by using PanGestureRecognizer and PinchGestureRecognizer to implement it .
Create a ScaleAndRotateContainer ContentView which contains PanGestureRecognizer and PinchGestureRecognizer :
public class ScaleAndRotateContainer : ContentView
{
double currentScale = 1;
double startScale = 1;
double xOffset = 0;
double yOffset = 0;
double rotateNum = 1;
public ScaleAndRotateContainer()
{
var pinchGesture = new PinchGestureRecognizer ();
pinchGesture.PinchUpdated += OnPinchUpdated;
var panGesture = new PanGestureRecognizer();
panGesture.PanUpdated += PanGesture_PanUpdated;
GestureRecognizers.Add (pinchGesture);
GestureRecognizers.Add(panGesture);
}
private void PanGesture_PanUpdated(object sender, PanUpdatedEventArgs e)
{
rotateNum++;
this.RotateTo(rotateNum);
this.AnchorX = 0.5;
this.AnchorY = 0.5;
}
void OnPinchUpdated (object sender, PinchGestureUpdatedEventArgs e)
{
if (e.Status == GestureStatus.Started) {
// Store the current scale factor applied to the wrapped user interface element,
// and zero the components for the center point of the translate transform.
startScale = Content.Scale;
Content.AnchorX = 0;
Content.AnchorY = 0;
}
if (e.Status == GestureStatus.Running) {
// Calculate the scale factor to be applied.
currentScale += (e.Scale - 1) * startScale;
currentScale = Math.Max (1, currentScale);
// The ScaleOrigin is in relative coordinates to the wrapped user interface element,
// so get the X pixel coordinate.
double renderedX = Content.X + xOffset;
double deltaX = renderedX / Width;
double deltaWidth = Width / (Content.Width * startScale);
double originX = (e.ScaleOrigin.X - deltaX) * deltaWidth;
// The ScaleOrigin is in relative coordinates to the wrapped user interface element,
// so get the Y pixel coordinate.
double renderedY = Content.Y + yOffset;
double deltaY = renderedY / Height;
double deltaHeight = Height / (Content.Height * startScale);
double originY = (e.ScaleOrigin.Y - deltaY) * deltaHeight;
// Calculate the transformed element pixel coordinates.
double targetX = xOffset - (originX * Content.Width) * (currentScale - startScale);
double targetY = yOffset - (originY * Content.Height) * (currentScale - startScale);
// Apply translation based on the change in origin.
Content.TranslationX = targetX.Clamp (-Content.Width * (currentScale - 1), 0);
Content.TranslationY = targetY.Clamp (-Content.Height * (currentScale - 1), 0);
// Apply scale factor
Content.Scale = currentScale;
}
if (e.Status == GestureStatus.Completed) {
// Store the translation delta's of the wrapped user interface element.
xOffset = Content.TranslationX;
yOffset = Content.TranslationY;
}
}
}
Then use it in Xaml for ContentPage :
xmlns:local="clr-namespace:PinchGesture;assembly=YourNameSpace"
<local:ScaleAndRotateContainer>
<local:ScaleAndRotateContainer.Content>
<StackLayout HorizontalOptions="Center" VerticalOptions="Center" >
<Label Text="Hello Xamarin.Forms!" />
<Image Source="waterfront.jpg" />
</StackLayout>
</local:ScaleAndRotateContainer.Content>
</local:ScaleAndRotateContainer>
The effect :

how to enable scrolling when zoom in and zoom out on a content page in xamarin forms

i am trying to zoom in and zoom out on a content page using xamarin.forms.
I am able zoom in and zoom out but the problem is scrolling is not working.
i want zoom an image. with this code zooming is working perfectly. But while zooming i am not able to see full image. i must scroll to view the rest of the image. for that i need to scroll. but scrolling is not working.
XAML
xmlns:helper="clr-namespace:KPGTC.Deals.Mobile.Helpers"
<helper:PinchToZoomContainer>
<helper:PinchToZoomContainer.Content>
<Image x:Name="img_Popup"/>
</helper:PinchToZoomContainer.Content>
</helper:PinchToZoomContainer>
Code:
public class PinchToZoomContainer : ContentView
{
double MIN_SCALE = 1;
double MAX_SCALE = 4;
double startScale = 1;
double currentScale = 1;
double xOffset = 0;
double yOffset = 0;
bool _isActive = false;
public PinchToZoomContainer()
{
DependencyService.Get<IHelpers>().ShowAlert("Double-tap to zoom");
//var _pinchGesture = new PinchGestureRecognizer();
//_pinchGesture.PinchUpdated += OnPinchUpdated;
//GestureRecognizers.Add(_pinchGesture);
var _tapGesture = new TapGestureRecognizer { NumberOfTapsRequired = 2 };
_tapGesture.Tapped += On_Tapped;
GestureRecognizers.Add(_tapGesture);
var _panGesture = new PanGestureRecognizer();
_panGesture.PanUpdated += OnPanUpdated;
GestureRecognizers.Add(_panGesture);
TranslationX = 0;
TranslationY = 0;
_isActive = false;
}
private void OnPanUpdated(object sender, PanUpdatedEventArgs e)
{
if (_isActive)
{
if (e.TotalX > 0)
{
if (e.TotalX > 2)
{
TranslationX += 15;
}
}
else
{
if (e.TotalX < -2)
{
TranslationX -= 15;
}
}
}
}
private void On_Tapped(object sender, EventArgs e)
{
if (Scale > MIN_SCALE)
{
_isActive = false;
this.ScaleTo(MIN_SCALE, 250, Easing.CubicInOut);
this.TranslateTo(0, 0, 250, Easing.CubicInOut);
}
else
{
_isActive = true;
AnchorX = AnchorY = 0.5;
this.ScaleTo(MAX_SCALE, 250, Easing.CubicInOut);
}
}
void OnPinchUpdated(object sender, PinchGestureUpdatedEventArgs e)
{
if (e.Status == GestureStatus.Started)
{
startScale = Content.Scale;
Content.AnchorX = 0;
Content.AnchorY = 0;
}
if (e.Status == GestureStatus.Running)
{
currentScale += (e.Scale - 1) * startScale;
currentScale = Math.Max(1, currentScale);
double renderedX = Content.X + xOffset;
double deltaX = renderedX / Width;
double deltaWidth = Width / (Content.Width * startScale);
double originX = (e.ScaleOrigin.X - deltaX) * deltaWidth;
double renderedY = Content.Y + yOffset;
double deltaY = renderedY / Height;
double deltaHeight = Height / (Content.Height * startScale);
double originY = (e.ScaleOrigin.Y - deltaY) * deltaHeight;
double targetX = xOffset - (originX * Content.Width) * (currentScale - startScale);
double targetY = yOffset - (originY * Content.Height) * (currentScale - startScale);
Content.TranslationX = targetX.Clamp(-Content.Width * (currentScale - 1), 0);
Content.TranslationY = targetY.Clamp(-Content.Height * (currentScale - 1), 0);
Content.Scale = currentScale;
}
if (e.Status == GestureStatus.Completed)
{
xOffset = Content.TranslationX;
yOffset = Content.TranslationY;
}
}
}
Alright, this was quite a tough one and admittedly I don't understand fully how I made it, but I made it.
Some thoughts:
You mixed the translation of the container and the content, which is quite tricky to handle - if this is possible at all
When panning, you added 15 every time the pan event was raised, but there is a better way: Just store the initial offset of the content and then add the TotalX and TotalY respectively to the TranslationX and the TranslationY of the content (this was the easy part)
Panning while zooming was quite hard to get right and I had to find it out by trial and error
Basically you have to store the origin of the pinch gesture when the gesture starts and calculate the diff between the original origin and the current origin
Then you have to add the diff (multiplied by the with and height respectively of the control) to the target translation
Here is the code for the panning:
private void OnPanUpdated(object sender, PanUpdatedEventArgs e)
{
if (e.StatusType == GestureStatus.Started)
{
this.xOffset = this.Content.TranslationX;
this.yOffset = this.Content.TranslationY;
}
if (e.StatusType != GestureStatus.Completed
&& e.StatusType != GestureStatus.Canceled)
{
this.Content.TranslationX = this.xOffset + e.TotalX;
this.Content.TranslationY = this.yOffset + e.TotalY;
}
if (e.StatusType == GestureStatus.Completed)
{
this.xOffset = this.Content.TranslationX;
this.yOffset = this.Content.TranslationY;
}
}
And here for the pinching
void OnPinchUpdated(object sender, PinchGestureUpdatedEventArgs e)
{
if (e.Status == GestureStatus.Started)
{
this.startScale = this.Content.Scale;
this.Content.AnchorX = 0;
this.Content.AnchorY = 0;
this.startScaleOrigin = e.ScaleOrigin;
}
if (e.Status == GestureStatus.Running)
{
var originDiff = PinchToZoomContainer.CalculateDiff(e.ScaleOrigin, this.startScaleOrigin);
this.currentScale += (e.Scale - 1) * this.startScale;
this.currentScale = Math.Max(1, this.currentScale);
double renderedX = this.Content.X + this.xOffset;
double deltaX = renderedX / this.Width;
double deltaWidth = this.Width / (this.Content.Width * this.startScale);
double originX = (this.startScaleOrigin.X - deltaX) * deltaWidth;
double renderedY = this.Content.Y + this.yOffset;
double deltaY = renderedY / this.Height;
double deltaHeight = this.Height / (this.Content.Height * this.startScale);
double originY = (startScaleOrigin.Y - deltaY) * deltaHeight;
double targetX = this.xOffset - ((originX) * this.Content.Width) * (this.currentScale - this.startScale) - originDiff.X * this.Content.Width;
double targetY = this.yOffset - ((originY) * this.Content.Height) * (this.currentScale - this.startScale) - originDiff.Y * this.Content.Height;
this.Content.TranslationX = targetX.Clamp(-this.Content.Width * (this.currentScale - 1), 0);
this.Content.TranslationY = targetY.Clamp(-this.Content.Height * (this.currentScale - 1), 0);
this.Content.Scale = this.currentScale;
}
if (e.Status == GestureStatus.Completed)
{
this.xOffset = this.Content.TranslationX;
this.yOffset = this.Content.TranslationY;
}
}
(Of course you have to add Point startScaleOrigin to your class).
Finally you need the method to calculate the distance between two points
private static Point CalculateDiff(Point first, Point second)
{
return second.Offset(-first.X, -first.Y);
}
Unfortunately I did not manage to get the tapping right, but I think you should be able to figure it out from here.

How to Reset Time of an analog clock by dragging it's handles C#

I'm very new to C#, the aim here is to edit the Time of an analog Clock by dragging it's handles. https://code.msdn.microsoft.com/windowsapps/Analog-Clock-Control-0e8ffcab#content this code has inpired me. I have three simple functions MouseDown, MouseMove and MouseUp but still I can not get Drag to work. Any suggestions please ?
public partial class Form1 : Form
{
#region Construct the clock
public Point Start { get; set; }
public Point End { get; set; }
public Form1()
{
InitializeComponent();
DoubleBuffered = true;
//Create the timer and start it
ClockTimer.Tick += ClockTimer_Tick;
ClockTimer.Enabled = true;
ClockTimer.Interval = 1;
ClockTimer.Start();
Start = p1;
End = p2;
}
#endregion
#region Update the clock
private void ClockTimer_Tick(object sender, EventArgs e)
{
Refresh();
}
private Timer ClockTimer = new Timer();
private Pen circle = new Pen(Color.Black, 2);
private Pen secondHandle = new Pen(Color.Red, 1);
private Pen minHandle = new Pen(Color.Black, 5);
private Pen hrHandle = new Pen(Color.Black, 5);
private Point p1;
private Point p2;
#endregion
#region On paint
protected override void OnPaint(PaintEventArgs pe)
{
base.OnPaint(pe);
//Clear the graphics to the back color of the control
pe.Graphics.Clear(BackColor);
//Draw the border of the clock
pe.Graphics.DrawEllipse(circle, 0, 0, 300, 300);
//Find the radius of the control by dividing the width by 2
float radius = (300 / 2);
//Find the origin of the circle by dividing the width and height of the control
PointF origin = new PointF(300 / 2, 300 / 2);
//Draw only if ShowMajorSegments is true;
if (ShowMajorSegments)
{
//Draw the Major segments for the clock
for (float i = 0f; i != 390f; i += 30f)
{
pe.Graphics.DrawLine(Pens.White, PointOnCircle(radius - 1, i, origin), PointOnCircle(radius - 21, i, origin));
}
}
//Draw only if ShowMinorSegments is true
if (ShowMinorSegments)
{
//Draw the minor segments for the control
for (float i = 0f; i != 366f; i += 6f)
{
pe.Graphics.DrawLine(Pens.Black, PointOnCircle(radius, i, origin), PointOnCircle(radius - 10, i, origin));
}
}
//Draw only if ShowSecondHand is true
if (ShowSecondhand)
//Draw the second hand
pe.Graphics.DrawLine(secondHandle, origin, PointOnCircle(radius, DateTime.Now.Second * 6f, origin));
//Draw only if ShowMinuteHand is true
if (ShowMinuteHand)
//Draw the minute hand
pe.Graphics.DrawLine(minHandle, origin, PointOnCircle(radius * 0.75f, DateTime.Now.Minute * 6f, origin));
minHandle.StartCap = LineCap.RoundAnchor;
minHandle.EndCap = LineCap.ArrowAnchor;
pe.Graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.High;
pe.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
//Draw only if ShowHourHand is true
if (ShowHourHand)
//Draw the hour hand
pe.Graphics.DrawLine(hrHandle, origin, PointOnCircle(radius * 0.50f, DateTime.Now.Hour * 30f, origin));
hrHandle.StartCap = LineCap.RoundAnchor;
hrHandle.EndCap = LineCap.ArrowAnchor;
pe.Graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.High;
pe.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
}
#endregion
#region On size changed
protected override void OnSizeChanged(EventArgs e)
{
base.OnSizeChanged(e);
//Make sure the control is square
if (Size.Height != Size.Width)
Size = new Size(Size.Width, Size.Width);
//Redraw the control
Refresh();
}
#endregion
#region Point on circle
private PointF PointOnCircle(float radius, float angleInDegrees, PointF origin)
{
//Find the x and y using the parametric equation for a circle
float x = (float)(radius * Math.Cos((angleInDegrees - 90f) * Math.PI / 180F)) + origin.X;
float y = (float)(radius * Math.Sin((angleInDegrees - 90f) * Math.PI / 180F)) + origin.Y;
return new PointF(x, y);
}
#endregion
#region Show Minor Segments
private bool showMinorSegments = true;
public bool ShowMinorSegments
{
get
{
return showMinorSegments;
}
set
{
showMinorSegments = value;
Refresh();
}
}
#endregion
#region Show Major Segments
private bool showMajorSegments = true;
public bool ShowMajorSegments
{
get
{
return showMajorSegments;
}
set
{
showMajorSegments = value;
Refresh();
}
}
#endregion
#region Show Second Hand
private bool showSecondHand = false;
public bool ShowSecondhand
{
get
{
return showSecondHand;
}
set
{
showSecondHand = value;
Refresh();
}
}
#endregion
#region Show Minute Hand
private bool showMinuteHand = true;
public bool ShowMinuteHand
{
get
{
return showMinuteHand;
}
set
{
showMinuteHand = value;
Refresh();
}
}
#endregion
#region Show Hour Hand
private bool showHourHand = true;
public bool ShowHourHand
{
get
{
return showHourHand;
}
set
{
showHourHand = value;
Refresh();
}
}
#endregion
public float slope
{
get
{
return (((float)p2.Y - (float)p1.Y) / ((float)p2.X - (float)p1.X));
}
}
public float YIntercept
{
get
{
return p1.Y - slope * p1.X;
}
}
public bool IsPointOnLine(Point p, int cushion)
{
float temp = (slope * p.X + YIntercept);
if (temp >= (p.Y - cushion) && temp <= (p.Y + cushion))
{
return true;
}
else
{
return false;
}
}
Point deltaStart;
Point deltaEnd;
bool dragging = false;
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == System.Windows.Forms.MouseButtons.Left && IsPointOnLine(e.Location, 5))
{
dragging = true;
deltaStart = new Point(p1.X - e.Location.X, p1.Y - e.Location.Y);
deltaEnd = new Point(p2.X - e.Location.X, p2.Y - e.Location.Y);
}
}
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
if (dragging && deltaStart != null && deltaEnd != null)
{
p1 = new Point(deltaStart.X + e.Location.X, deltaStart.Y + e.Location.Y);
p2 = new Point(deltaEnd.X + e.Location.X, deltaEnd.Y + e.Location.Y);
this.Refresh();
}
}
private void Form1_MouseUp(object sender, MouseEventArgs e)
{
dragging = false;
}
}
I give a partial answer about translating a X, Y coordinate to an angle (in degree) based on a circle, where the 0° angle is located at the top.
(Scroll down for a compact solution)
Following the directions of typical GUI coordinates, the absolute 0,0 Point is located top left, positive X values stretch to the right and positive Y values stretch to the bottom.
In order to simplify the math, I use a virtual 0,0 point at the center of the circle, so all coordinates need to be translated to locals before calculation and to globals before actual drawing.
Coordinate overview (imagine the circle around 0; 0):
(0;-1)
(-1; 0) (0; 0) (1; 0)
(0; 1)
Now the task is for any coordinate (X; Y) to find the clock-wise angle between the line (0; 0) - (0; -1) and the line (0; 0) - (X; Y)
The circle can be divided into 4 quarter-circles, each covering a combination of signed (X; Y) values.
Quarter 1 contains the angle values 0° to 90° and is represented by positive X values and negative Y values.
Quarter 2 contains the angle values 90° to 180° and is represented by positive X values and positive Y values.
Quarter 3 contains the angle values 180° to 270° and is represented by negative X values and positive Y values.
Quarter 4 contains the angle values 270° to 360° and is represented by negative X values and negative Y values.
Note that for the corner cases 0°, 90°, 180°, 270°, 360° it doesn't really matter which of the two quarters they are assigned to.
The easiest way to understand such problems is to stick to the normal circle -> read: to normalize the X; Y coordinate to a length of 1. Additionally I go with positive values (it would also work without, but a bit differently in the + and - combinations):
var len = Math.Sqrt(X * X + Y * Y);
var xNorm = Math.Abs(X) / len;
var yNorm = Math.Abs(Y) / len;
Now, the reverse sine / cosine can be used to translate the normalized coordinates back into angle values (there's some redundancy in my calculation for the sake of simplicity and completeness):
var angleFromX = Math.Asin(xNorm) * 180.0 / Math.PI;
var angleFromY = Math.Asin(yNorm) * 180.0 / Math.PI;
Now lets apply the appropriate angle for each of the quarter circle areas
var resultAngle = 0.0;
if (quarter_1)
{
resultAngle = 0 + angleFromX;
// same as
resultAngle = 90 - angleFromY;
}
if (quarter_2)
{
resultAngle = 90 + angleFromY;
// same as
resultAngle = 180 - angleFromX;
}
if (quarter_3)
{
resultAngle = 180 + angleFromX;
// same as
resultAngle = 270 - angleFromY;
}
if (quarter_4)
{
resultAngle = 270 + angleFromY;
// same as
resultAngle = 360 - angleFromX;
}
Ofcourse, the quarter_1 - quarter_4 are pseudo-variables that represent the quarter selection as explained.
A more compact solution can be found by analyzing the different properties of the full solution.
var angleFromYAxis = Math.Asin(Y / Math.Sqrt(X * X + Y * Y)) * 180.0 / Math.PI;
var resultAngle = 0.0;
if (X >= 0)
{
resultAngle = 90 + angleFromYAxis;
}
else
{
resultAngle = 270 - angleFromYAxis;
}

Zoom every point in chart by mouse over

I have a chart on my c# windows application.
I want to zoom every point of chart when mouse on them.
like google map
I mean I don't want zoom all part of chart
I want zoom just specefic point like google map
code:
public partial class Form1 : Form
{
int[] myArrayX = new int[5];
double[] myArrayY = new double[5];
int lastX = -1;
double lastY = -0.6;
double xmax;
Graph.Chart chart;
public Form1()
{
InitializeComponent();
this.MouseWheel += new MouseEventHandler(Form1_MouseWheel);
}
void Form1_MouseWheel(object sender, MouseEventArgs e)
{
try
{
if (e.Delta > 0)
{
double xMin = chart.ChartAreas["draw"].AxisX.ScaleView.ViewMinimum;
double xMax = chart.ChartAreas["draw"].AxisX.ScaleView.ViewMaximum;
double yMin = chart.ChartAreas["draw"].AxisY.ScaleView.ViewMinimum;
double yMax = chart.ChartAreas["draw"].AxisY.ScaleView.ViewMaximum;
double posXStart = chart.ChartAreas["draw"].AxisX.PixelPositionToValue(e.Location.X) - (xMax - xMin) / 2;
double posXFinish = chart.ChartAreas["draw"].AxisX.PixelPositionToValue(e.Location.X) + (xMax - xMin) / 2;
double posYStart = chart.ChartAreas["draw"].AxisY.PixelPositionToValue(e.Location.Y) - (yMax - yMin) / 2;
double posYFinish = chart.ChartAreas["draw"].AxisY.PixelPositionToValue(e.Location.Y) + (yMax - yMin) / 2;
chart.ChartAreas["draw"].AxisX.ScaleView.Zoom(posXStart, posXFinish);
chart.ChartAreas["draw"].AxisY.ScaleView.Zoom(posYStart, posYFinish);
}
else if (e.Delta < 0)
{
ZoomOut();
}
}
catch { }
}
private void ZoomOut()
{
chart.ChartAreas["draw"].AxisX.ScaleView.ZoomReset();
chart.ChartAreas["draw"].AxisY.ScaleView.ZoomReset();
}
void CreateNewGraph()
{
// Create new Graph
chart = new Graph.Chart();
chart.Location = new System.Drawing.Point(13, 185);
chart.Size = new System.Drawing.Size(900, 500);
chart.ChartAreas.Add("draw");
chart.ChartAreas["draw"].AxisX.Minimum = 0;
chart.ChartAreas["draw"].AxisX.Maximum = 20;
chart.ChartAreas["draw"].AxisX.Interval = 1;
chart.ChartAreas["draw"].AxisX.MajorGrid.LineColor = Color.White;
chart.ChartAreas["draw"].AxisX.MajorGrid.LineDashStyle = Graph.ChartDashStyle.Dash;
chart.ChartAreas["draw"].AxisY.Minimum = -0.4;
chart.ChartAreas["draw"].AxisY.Maximum = 1;
chart.ChartAreas["draw"].AxisY.Interval = 0.2;
chart.ChartAreas["draw"].AxisY.MajorGrid.LineColor = Color.White;
chart.ChartAreas["draw"].AxisY.MajorGrid.LineDashStyle = Graph.ChartDashStyle.Dash;
chart.ChartAreas["draw"].BackColor = Color.Black;
var series = chart.Series.Add("Test");
chart.Series["Test"].ChartType = Graph.SeriesChartType.Line;
chart.Series["Test"].Color = Color.Yellow;
chart.Series["Test"].BorderWidth = 3;
chart.Legends.Add("MyLegend");
chart.Legends["MyLegend"].BorderColor = Color.YellowGreen;
// Set automatic zooming
chart.ChartAreas["draw"].AxisX.ScaleView.Zoomable = true;
chart.ChartAreas["draw"].AxisY.ScaleView.Zoomable = true;
// Set automatic scrolling
chart.ChartAreas["draw"].CursorX.AutoScroll = true;
chart.ChartAreas["draw"].CursorY.AutoScroll = true;
// Allow user selection for Zoom
chart.ChartAreas["draw"].CursorX.IsUserSelectionEnabled = true;
chart.ChartAreas["draw"].CursorY.IsUserSelectionEnabled = true;
chart.ChartAreas["draw"].AxisX.ScaleView.Zoomable = true;
chart.ChartAreas["draw"].AxisY.ScaleView.Zoomable = true;
//chart.MouseWheel += new MouseEventHandler(chart_MouseWheel);
}
private void Form1_Load(object sender, EventArgs e)
{
CreateNewGraph();
}
private void timer1_Tick(object sender, EventArgs e)
{
fillarray();
for (int i = 1; i <= 5; i += 1)
{
chart.Series["Test"].Points.AddXY(myArrayX[i - 1], myArrayY[i - 1]);
xmax = myArrayX[i - 1];
}
if (xmax >= 20)
{
chart.ChartAreas["draw"].AxisX.ScrollBar.Enabled = true;
chart.ChartAreas["draw"].AxisX.ScaleView.Zoomable = true;
chart.ChartAreas["draw"].AxisX.ScaleView.Zoom(0, xmax);
}
Controls.Add(this.chart);
}
public void fillarray()
{
for (int i = 1; i <= 5; i += 1)
{
lastX = lastX + 1;
myArrayX[i - 1] = lastX;
}
for (int i = 1; i < 5; i += 1)
{
lastY = lastY + 0.2;
myArrayY[i - 1] = lastY;
}
}
}
Asuming that you use the "standard" (since .NET 4.0) Charting Lib which is in the namespace System.Windows.Forms.DataVisualization.Charting. You can implement custom interactivity (zoom when mouse does this or that). MSDN is a good start and GIYF.
http://msdn.microsoft.com/en-us/library/dd456772(v=vs.110).aspx
There are also a lot of examples around the web.
good luck!

ZOOM Out function in MS Chart c#

I have the following Winforms code:
void chart1_MouseWheel(object sender, MouseEventArgs e)
{
double xMin = chart1.ChartAreas[0].AxisX.ScaleView.ViewMinimum;
double xMax = chart1.ChartAreas[0].AxisX.ScaleView.ViewMaximum;
if (e.Delta < 0)
{ //chart1.ChartAreas[0].AxisX.ScaleView.ZoomReset();
//chart1.ChartAreas[0].AxisY.ScaleView.ZoomReset();
}
if (e.Delta > 0)
{
double posXStart = chart1.ChartAreas[0].AxisX.PixelPositionToValue(e.Location.X) - (xMax - xMin)/2 ;
double posXFinish = chart1.ChartAreas[0].AxisX.PixelPositionToValue(e.Location.X) + (xMax - xMin)/2;
chart1.ChartAreas[0].AxisX.ScaleView.Zoom(posXStart, posXFinish);
}
}
The Zoom In function is working but when e.Delta < 0, I need the Zoom Out function based on the above code.
try
chart1.ChartAreas[0].AxisX.ScaleView.ZoomReset(1);
chart1.ChartAreas[0].AxisY.ScaleView.ZoomReset(1);
If you set the saveState to true when you are zooming, the ZoomReset(1) will go back to the last zoom state. Or if you set the saveState to false, the ZoomReset(1) will just zoom all the way back out. Here is my code, I do mine with the mouse click, but I'm sure you can get it working with scroll wheel:
private void chart1_SelectionRangeChanged(object sender, CursorEventArgs e)
{
double startX, endX, startY, endY;
if (chart1.ChartAreas[0].CursorX.SelectionStart > chart1.ChartAreas[0].CursorX.SelectionEnd)
{
startX = chart1.ChartAreas[0].CursorX.SelectionEnd;
endX = chart1.ChartAreas[0].CursorX.SelectionStart;
}
else
{
startX = chart1.ChartAreas[0].CursorX.SelectionStart;
endX = chart1.ChartAreas[0].CursorX.SelectionEnd;
}
if (chart1.ChartAreas[0].CursorY.SelectionStart > chart1.ChartAreas[0].CursorY.SelectionEnd)
{
endY = chart1.ChartAreas[0].CursorY.SelectionStart;
startY = chart1.ChartAreas[0].CursorY.SelectionEnd;
}
else
{
startY = chart1.ChartAreas[0].CursorY.SelectionStart;
endY = chart1.ChartAreas[0].CursorY.SelectionEnd;
}
if (startX == endX && startY == endY)
{
return;
}
chart1.ChartAreas[0].AxisX.ScaleView.Zoom(startX, (endX - startX), DateTimeIntervalType.Auto, true);
chart1.ChartAreas[0].AxisY.ScaleView.Zoom(startY, (endY - startY), DateTimeIntervalType.Auto, true);
}
As Baddack points out you can use the ZoomReset(1) method to go back one step in the zoom history. However, if you use ZoomReset(0) you can reset all zoom operations without having to turn the history saving off.

Categories

Resources