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 { /// /// Interaction logic for GraphingCanvas.xaml /// 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 expressions = new List(); public List Expressions { get { return expressions; } } /// /// Adds an expression to be plotted /// 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 queuedPoints = new List(); 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 } }