467 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			467 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| using System;
 | |
| using System.Collections.Generic;
 | |
| using System.Linq;
 | |
| using System.Text;
 | |
| using System.Windows;
 | |
| using System.Windows.Controls;
 | |
| using System.Windows.Data;
 | |
| using System.Windows.Documents;
 | |
| using System.Windows.Input;
 | |
| using System.Windows.Media;
 | |
| using System.Windows.Media.Imaging;
 | |
| using System.Windows.Navigation;
 | |
| using System.Windows.Shapes;
 | |
| 
 | |
| namespace GraphingCalculator
 | |
| {
 | |
|     /// <summary>
 | |
|     /// Interaction logic for GraphingCanvas.xaml
 | |
|     /// </summary>
 | |
|     public partial class GraphingCanvas : Canvas
 | |
|     {
 | |
|         #region Settings
 | |
|         private double PlotPrecision 
 | |
|         {
 | |
|             get { return GraphingCalculator.Properties.Settings.Default.PlotPrecision; }
 | |
|             set { GraphingCalculator.Properties.Settings.Default.PlotPrecision = value; }
 | |
|         }
 | |
| 
 | |
|         private double GridDensity
 | |
|         {
 | |
|             get { return GraphingCalculator.Properties.Settings.Default.GridDensity; }
 | |
|             set { GraphingCalculator.Properties.Settings.Default.GridDensity = value; }
 | |
|         }
 | |
| 
 | |
|         private int RoundDoublesGraph
 | |
|         {
 | |
|             get { return GraphingCalculator.Properties.Settings.Default.RoundDoublesGraph; }
 | |
|             set { GraphingCalculator.Properties.Settings.Default.RoundDoublesGraph = value; }
 | |
|         }
 | |
| 
 | |
|         private double NavigationSens
 | |
|         {
 | |
|             get { return GraphingCalculator.Properties.Settings.Default.NavigationSensitivity; }
 | |
|             set { GraphingCalculator.Properties.Settings.Default.NavigationSensitivity = value; }
 | |
|         }
 | |
| 
 | |
|         private double ZoomSens
 | |
|         {
 | |
|             get { return GraphingCalculator.Properties.Settings.Default.ZoomSensitivity; }
 | |
|             set { GraphingCalculator.Properties.Settings.Default.ZoomSensitivity = value; }
 | |
|         }
 | |
| 
 | |
|         #endregion
 | |
| 
 | |
|         #region Constants
 | |
|         private Point specialSeparator = new Point(double.MaxValue, double.MaxValue);
 | |
|         private Point specialVisibleTrue = new Point(0, double.MaxValue);
 | |
|         private Point specialVisibleFalse = new Point(0, double.MinValue);
 | |
|         #endregion
 | |
| 
 | |
|         #region Expressions
 | |
|         private List<VisualExpression> expressions = new List<VisualExpression>();
 | |
|         public List<VisualExpression> Expressions { get { return expressions; } }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Adds an expression to be plotted
 | |
|         /// </summary>
 | |
|         public void AddExpression(VisualExpression ex)
 | |
|         {
 | |
|             this.Expressions.Add(ex);
 | |
|             EvaluateExpression(ex);
 | |
|             Redraw();
 | |
| 
 | |
|             #region Log
 | |
|             Log.LogEvent("Plotted expression '{0}'", ex.ExpressionString);
 | |
|             #endregion
 | |
|         }
 | |
| 
 | |
|         public void SetExpressionVisibility(int index, bool visibility)
 | |
|         {
 | |
|             if (Expressions[index].IsVisible == visibility) return;
 | |
| 
 | |
|             if (queuedPoints.Count != 0) {
 | |
|                 int i, ci;
 | |
|                 for (i = 0, ci = -1; i < queuedPoints.Count && ci < index; i++)
 | |
|                     if (queuedPoints[i] == specialSeparator) ci++;
 | |
| 
 | |
|                 queuedPoints[i] = (visibility) ? specialVisibleTrue : specialVisibleFalse;
 | |
|             }
 | |
| 
 | |
|             Expressions[index].IsVisible = visibility;
 | |
|             Redraw();
 | |
|         }
 | |
|         #endregion
 | |
| 
 | |
|         #region Canvas and screen bounds
 | |
|         public Bounds CanvasBounds { get; set; }
 | |
|         public Bounds ScreenBounds { get; set; }
 | |
| 
 | |
|         public double CoordXToCanvas(double x)
 | |
|         {
 | |
|             double scrX = x - ScreenBounds.Left;
 | |
|             return CanvasBounds.Left + (CanvasBounds.Width * (scrX / ScreenBounds.Width));
 | |
|         }
 | |
| 
 | |
|         public double CoordXToScreen(double x)
 | |
|         {
 | |
|             double canX = x - CanvasBounds.Left;
 | |
|             return ScreenBounds.Left + (ScreenBounds.Width * (canX / CanvasBounds.Width));
 | |
|         }
 | |
| 
 | |
|         public double CoordYToCanvas(double y)
 | |
|         {
 | |
|             double scrY = y - ScreenBounds.Bottom;
 | |
|             return CanvasBounds.Bottom + (CanvasBounds.Height * (scrY / ScreenBounds.Height));
 | |
|         }
 | |
| 
 | |
|         public double CoordYToScreen(double y)
 | |
|         {
 | |
|             double canY = y - CanvasBounds.Bottom;
 | |
|             return ScreenBounds.Bottom + (ScreenBounds.Height * (canY / CanvasBounds.Height));
 | |
|         }
 | |
| 
 | |
|         public Point CoordToCanvas(double x, double y)
 | |
|         {
 | |
|             return new Point(CoordXToCanvas(x), CoordYToCanvas(y));
 | |
|         }
 | |
| 
 | |
|         public Point CoordToCanvas(Point p)
 | |
|         {
 | |
|             return new Point(CoordXToCanvas(p.X), CoordYToCanvas(p.Y));
 | |
|         }
 | |
| 
 | |
|         public Point CoordToScreen(double x, double y)
 | |
|         {
 | |
|             return new Point(CoordXToScreen(x), CoordYToScreen(y));
 | |
|         }
 | |
| 
 | |
|         public Point CoordToScreen(Point p)
 | |
|         {
 | |
|             return new Point(CoordXToScreen(p.X), CoordYToScreen(p.Y));
 | |
|         }
 | |
| 
 | |
|         public void SetCanvasBounds(Bounds b)
 | |
|         {
 | |
|             if (b.Width <= 0 || b.Height <= 0) return;
 | |
| 
 | |
|             CanvasBounds.Top = b.Top;
 | |
|             CanvasBounds.Bottom = b.Bottom;
 | |
|             CanvasBounds.Left = b.Left;
 | |
|             CanvasBounds.Right = b.Right;
 | |
| 
 | |
|             EvaluateExpressions();
 | |
|             Redraw();
 | |
|         }
 | |
|         #endregion
 | |
| 
 | |
|         #region Constructor
 | |
|         public GraphingCanvas()
 | |
|         {
 | |
|             InitializeComponent();
 | |
| 
 | |
|             CanvasBounds = new Bounds(-5, 5, 5, -5);
 | |
|             ScreenBounds = new Bounds(0, 0, this.ActualWidth, this.ActualHeight);
 | |
|         }
 | |
|         #endregion
 | |
| 
 | |
|         #region UI Events
 | |
|         private void Canvas_SizeChanged(object sender, SizeChangedEventArgs e)
 | |
|         {
 | |
|             ScreenBounds.Right = e.NewSize.Width;
 | |
|             ScreenBounds.Bottom = e.NewSize.Height;
 | |
| 
 | |
|             Redraw();
 | |
|         }
 | |
| 
 | |
|         private void buttonReset_Click(object sender, RoutedEventArgs e)
 | |
|         {
 | |
|             CanvasBounds.Left = -5;
 | |
|             CanvasBounds.Right = 5;
 | |
|             CanvasBounds.Bottom = -5;
 | |
|             CanvasBounds.Top = 5;
 | |
| 
 | |
|             EvaluateExpressions();
 | |
|             Redraw();
 | |
|         }
 | |
| 
 | |
|         private void buttonUp_Click(object sender, RoutedEventArgs e)
 | |
|         {
 | |
|             double h = CanvasBounds.Height * NavigationSens;
 | |
|             CanvasBounds.Top += h;
 | |
|             CanvasBounds.Bottom += h;
 | |
| 
 | |
|             EvaluateExpressions();
 | |
|             Redraw();
 | |
|         }
 | |
| 
 | |
|         private void buttonLeft_Click(object sender, RoutedEventArgs e)
 | |
|         {
 | |
|             double w = CanvasBounds.Width * NavigationSens;
 | |
|             CanvasBounds.Left -= w;
 | |
|             CanvasBounds.Right -= w;
 | |
| 
 | |
|             EvaluateExpressions();
 | |
|             Redraw();
 | |
|         }
 | |
| 
 | |
|         private void buttonRight_Click(object sender, RoutedEventArgs e)
 | |
|         {
 | |
|             double w = CanvasBounds.Width * NavigationSens;
 | |
|             CanvasBounds.Left += w;
 | |
|             CanvasBounds.Right += w;
 | |
| 
 | |
|             EvaluateExpressions();
 | |
|             Redraw();
 | |
|         }
 | |
| 
 | |
|         private void buttonBottom_Click(object sender, RoutedEventArgs e)
 | |
|         {
 | |
|             double h = CanvasBounds.Height * NavigationSens;
 | |
|             CanvasBounds.Top -= h;
 | |
|             CanvasBounds.Bottom -= h;
 | |
| 
 | |
|             EvaluateExpressions();
 | |
|             Redraw();
 | |
|         }
 | |
| 
 | |
|         private void buttonZoomIn_Click(object sender, RoutedEventArgs e)
 | |
|         {
 | |
|             double new_w = CanvasBounds.Width / ZoomSens;
 | |
|             double new_h = CanvasBounds.Height / ZoomSens;
 | |
|             double diff_w = (CanvasBounds.Width - new_w) / 2 ;
 | |
|             double diff_h = (CanvasBounds.Height - new_h) / 2;
 | |
| 
 | |
|             CanvasBounds.Left += diff_w;
 | |
|             CanvasBounds.Right -= diff_w;
 | |
|             CanvasBounds.Bottom += diff_h;
 | |
|             CanvasBounds.Top -= diff_h;
 | |
| 
 | |
|             EvaluateExpressions();
 | |
|             Redraw();
 | |
|         }
 | |
| 
 | |
|         private void buttonZoomOut_Click(object sender, RoutedEventArgs e)
 | |
|         {
 | |
|             double new_w = CanvasBounds.Width * ZoomSens;
 | |
|             double new_h = CanvasBounds.Height * ZoomSens;
 | |
|             double diff_w = (CanvasBounds.Width - new_w) / 2;
 | |
|             double diff_h = (CanvasBounds.Height - new_h) / 2;
 | |
| 
 | |
|             CanvasBounds.Left += diff_w;
 | |
|             CanvasBounds.Right -= diff_w;
 | |
|             CanvasBounds.Bottom += diff_h;
 | |
|             CanvasBounds.Top -= diff_h;
 | |
| 
 | |
|             EvaluateExpressions();
 | |
|             Redraw();
 | |
|         }
 | |
|         #endregion
 | |
| 
 | |
|         #region Expression evaluation
 | |
|         List<Point> queuedPoints = new List<Point>();
 | |
| 
 | |
|         public void EvaluateExpression(VisualExpression expr)
 | |
|         {
 | |
|             queuedPoints.Add(specialSeparator);
 | |
|             queuedPoints.Add((expr.IsVisible) ? specialVisibleTrue : specialVisibleFalse);
 | |
| 
 | |
|             for (double x = CanvasBounds.Left; x <= CanvasBounds.Right; x += CanvasBounds.Width / PlotPrecision)
 | |
|             {
 | |
|                 expr.Variables["x"] = expr.Variables["X"] = x;
 | |
|                 queuedPoints.Add(new Point(x, expr.Evaluate()));
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         public void EvaluateExpressions()
 | |
|         {
 | |
|             queuedPoints.Clear();
 | |
|             foreach (var expr in Expressions)
 | |
|                 EvaluateExpression(expr);
 | |
|         }
 | |
|         #endregion
 | |
| 
 | |
|         #region Rendering
 | |
|         public void Redraw()
 | |
|         {
 | |
|             this.InvalidateVisual();
 | |
|         }
 | |
| 
 | |
|         private void RenderDouble(DrawingContext dc, double n, Point pos, bool center)
 | |
|         {
 | |
|             FormattedText text = new FormattedText(Math.Round(n, RoundDoublesGraph).ToString(),
 | |
|                 new System.Globalization.CultureInfo("en-US"), FlowDirection.LeftToRight, 
 | |
|                 new Typeface("Arial"), 8.0, Brushes.Black);
 | |
| 
 | |
|             if (center) dc.DrawText(text, new Point(pos.X - (text.Width / 2), pos.Y + 10));
 | |
|             else dc.DrawText(text, new Point(pos.X + 2, pos.Y));
 | |
|         }
 | |
| 
 | |
|         private void RenderGridVertical(DrawingContext dc, Pen pen, double from, double to, double interv)
 | |
|         {
 | |
|             if (from > to) interv *= -1;
 | |
| 
 | |
|             // Draw vertical grid
 | |
|             for (double i = from; (from < to && i < to) || (from > to && i > to); i += interv)
 | |
|             {
 | |
|                 Point text = CoordToScreen(i, 0);
 | |
|                 Point f = CoordToScreen(i, CanvasBounds.Bottom);
 | |
|                 Point t = CoordToScreen(i, CanvasBounds.Top);
 | |
| 
 | |
|                 dc.DrawLine(pen, f, t);
 | |
|                 RenderDouble(dc, i, text, true);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         private void RenderGridHorizontal(DrawingContext dc, Pen pen, double from, double to, double interv)
 | |
|         {
 | |
|             if (from > to) interv *= -1;
 | |
| 
 | |
|             // Draw vertical grid
 | |
|             for (double i = from; (from < to && i < to) || (from > to && i > to); i += interv)
 | |
|             {
 | |
|                 Point text = CoordToScreen(0, i);
 | |
|                 Point f = CoordToScreen(CanvasBounds.Left, i);
 | |
|                 Point t = CoordToScreen(CanvasBounds.Right, i);
 | |
| 
 | |
|                 dc.DrawLine(pen, f, t);
 | |
|                 RenderDouble(dc, i, text, false);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         private void RenderGrid(DrawingContext dc)
 | |
|         {
 | |
|             Pen pen = new Pen(new SolidColorBrush(Color.FromRgb(0xa6, 0xdd, 0xe2)), 1);
 | |
|             pen.DashStyle = DashStyles.Dash;
 | |
| 
 | |
|             double w_div = Math.Truncate(this.ActualWidth / GridDensity);
 | |
|             double h_div = Math.Truncate(this.ActualHeight / GridDensity);
 | |
|             if (Convert.ToInt32(w_div) % 2 == 1) w_div += 1;
 | |
|             if (Convert.ToInt32(h_div) % 2 == 1) h_div += 1;
 | |
| 
 | |
|             double w_int = CanvasBounds.Width / w_div;
 | |
|             double h_int = CanvasBounds.Height / h_div;
 | |
| 
 | |
|             RenderGridVertical(dc, pen, 0, CanvasBounds.Right, w_int);
 | |
|             RenderGridVertical(dc, pen, 0, CanvasBounds.Left, w_int);
 | |
|             RenderGridHorizontal(dc, pen, 0, CanvasBounds.Top, h_int);
 | |
|             RenderGridHorizontal(dc, pen, 0, CanvasBounds.Bottom, h_int);
 | |
|         }
 | |
| 
 | |
|         private void RenderAxis(DrawingContext dc)
 | |
|         {
 | |
|             Point dst;
 | |
|             Pen lines = new Pen(Brushes.Coral, 1.5);
 | |
| 
 | |
|             // Draw X axis
 | |
|             dst = CoordToScreen(CanvasBounds.Right, 0);
 | |
|             dc.DrawLine(lines, CoordToScreen(CanvasBounds.Left, 0), dst);
 | |
|             dc.DrawLine(lines, dst, new Point(dst.X - 5, dst.Y + 2));
 | |
|             dc.DrawLine(lines, dst, new Point(dst.X - 5, dst.Y - 2));
 | |
| 
 | |
|             // Draw Y axis
 | |
|             dst = CoordToScreen(0, CanvasBounds.Top);
 | |
|             dc.DrawLine(lines, CoordToScreen(0, CanvasBounds.Bottom), dst);
 | |
|             dc.DrawLine(lines, dst, new Point(dst.X - 2, dst.Y + 5));
 | |
|             dc.DrawLine(lines, dst, new Point(dst.X + 2, dst.Y + 5));
 | |
|         }
 | |
| 
 | |
|         private void RenderFunctions(DrawingContext dc)
 | |
|         {
 | |
|             if (Expressions.Count == 0) return;
 | |
| 
 | |
|             int exprIndex = -1;
 | |
|             bool visible = true;
 | |
|             Pen pen = new Pen();
 | |
| 
 | |
|             for (int i = 0; i < queuedPoints.Count - 1; i++)
 | |
|             {
 | |
|                 if (queuedPoints[i] == specialSeparator)
 | |
|                 {
 | |
|                     exprIndex++; i++;
 | |
|                     pen = new Pen(new SolidColorBrush(Expressions[exprIndex].Color), Expressions[exprIndex].Thickness);
 | |
|                     visible = (queuedPoints[i] == specialVisibleTrue);
 | |
|                 }
 | |
| 
 | |
|                 else if (visible && queuedPoints[i + 1] != specialSeparator)
 | |
|                 {
 | |
|                     Point src = CoordToScreen(queuedPoints[i]);
 | |
|                     Point dst = CoordToScreen(queuedPoints[i + 1]);
 | |
| 
 | |
|                     if (ScreenBounds.IsInBounds(src) || ScreenBounds.IsInBounds(dst)) 
 | |
|                         dc.DrawLine(pen, src, dst);
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         protected override void OnRender(DrawingContext dc)
 | |
|         {
 | |
|             // Reduce garbage collector intrusions
 | |
|             var latency = System.Runtime.GCSettings.LatencyMode;
 | |
|             System.Runtime.GCSettings.LatencyMode = System.Runtime.GCLatencyMode.LowLatency;
 | |
| 
 | |
|             // Draw grid
 | |
|             RenderGrid(dc);
 | |
|             RenderAxis(dc);
 | |
| 
 | |
|             // Render functions
 | |
|             RenderFunctions(dc);
 | |
| 
 | |
|             // Base render
 | |
|             base.OnRender(dc);
 | |
| 
 | |
|             // Restore previous garbage collector setting
 | |
|             System.Runtime.GCSettings.LatencyMode = latency;
 | |
|         } 
 | |
|         #endregion
 | |
| 
 | |
|         private void Canvas_MouseWheel(object sender, MouseWheelEventArgs e)
 | |
|         {
 | |
|             if (e.Delta > 0) buttonZoomIn_Click(this, new RoutedEventArgs());
 | |
|             else buttonZoomOut_Click(this, new RoutedEventArgs());
 | |
|         }
 | |
| 
 | |
|         #region Navigation control
 | |
|         public void PeformMouseWheelChange(MouseWheelEventArgs e)
 | |
|         {
 | |
|             Canvas_MouseWheel(this, e);
 | |
|         }
 | |
| 
 | |
|         public void PerformMoveLeft() 
 | |
|         {
 | |
|             buttonLeft_Click(this, new RoutedEventArgs());
 | |
|         }
 | |
| 
 | |
|         public void PerformMoveRight()
 | |
|         {
 | |
|             buttonRight_Click(this, new RoutedEventArgs());
 | |
|         }
 | |
| 
 | |
|         public void PerformMoveUp()
 | |
|         {
 | |
|             buttonUp_Click(this, new RoutedEventArgs());
 | |
|         }
 | |
| 
 | |
|         public void PerformMoveDown()
 | |
|         {
 | |
|             buttonBottom_Click(this, new RoutedEventArgs());
 | |
|         }
 | |
| 
 | |
|         public void PerformReset()
 | |
|         {
 | |
|             buttonReset_Click(this, new RoutedEventArgs());
 | |
|         }
 | |
| 
 | |
|         public void PerformZoomIn()
 | |
|         {
 | |
|             buttonZoomIn_Click(this, new RoutedEventArgs());
 | |
|         }
 | |
| 
 | |
|         public void PerformZoomOut()
 | |
|         {
 | |
|             buttonZoomOut_Click(this, new RoutedEventArgs());
 | |
|         }
 | |
|         #endregion
 | |
|     }
 | |
| }
 |