math-suite/Source/GraphingCalculator/Controls/GraphingCanvas.xaml.cs

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
}
}