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