Various things. Added a tangent line system, allows for different graph parts, and more caching stuff.
This commit is contained in:
parent
21c498f445
commit
e31e6bfdb6
@ -1,5 +1,7 @@
|
|||||||
using Graphing.Extensions;
|
using Graphing.Extensions;
|
||||||
using Graphing.Graphables;
|
using Graphing.Graphables;
|
||||||
|
using Graphing.Parts;
|
||||||
|
using System.Drawing.Drawing2D;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
namespace Graphing.Forms;
|
namespace Graphing.Forms;
|
||||||
@ -145,6 +147,7 @@ public partial class GraphForm : Form
|
|||||||
protected override void OnPaint(PaintEventArgs e)
|
protected override void OnPaint(PaintEventArgs e)
|
||||||
{
|
{
|
||||||
Graphics g = e.Graphics;
|
Graphics g = e.Graphics;
|
||||||
|
g.SmoothingMode = SmoothingMode.HighQuality;
|
||||||
|
|
||||||
Brush background = new SolidBrush(Color.White);
|
Brush background = new SolidBrush(Color.White);
|
||||||
g.FillRectangle(background, e.ClipRectangle);
|
g.FillRectangle(background, e.ClipRectangle);
|
||||||
@ -154,19 +157,9 @@ public partial class GraphForm : Form
|
|||||||
// Draw the actual graphs.
|
// Draw the actual graphs.
|
||||||
for (int i = 0; i < ables.Count; i++)
|
for (int i = 0; i < ables.Count; i++)
|
||||||
{
|
{
|
||||||
IEnumerable<Line2d> lines = ables[i].GetItemsToRender(this);
|
IEnumerable<IGraphPart> lines = ables[i].GetItemsToRender(this);
|
||||||
Brush graphBrush = new SolidBrush(ables[i].Color);
|
Brush graphBrush = new SolidBrush(ables[i].Color);
|
||||||
Pen penBrush = new(graphBrush, 3);
|
foreach (IGraphPart gp in lines) gp.Render(this, g, graphBrush);
|
||||||
|
|
||||||
foreach (Line2d l in lines)
|
|
||||||
{
|
|
||||||
if (!double.IsNormal(l.a.x) || !double.IsNormal(l.a.y) ||
|
|
||||||
!double.IsNormal(l.b.x) || !double.IsNormal(l.b.y)) continue;
|
|
||||||
|
|
||||||
Int2 start = GraphSpaceToScreenSpace(l.a),
|
|
||||||
end = GraphSpaceToScreenSpace(l.b);
|
|
||||||
g.DrawLine(penBrush, start, end);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
base.OnPaint(e);
|
base.OnPaint(e);
|
||||||
@ -375,24 +368,17 @@ public partial class GraphForm : Form
|
|||||||
long total = 0;
|
long total = 0;
|
||||||
foreach (Graphable able in ables)
|
foreach (Graphable able in ables)
|
||||||
{
|
{
|
||||||
if (able is Equation equ)
|
long size = able.GetCacheBytes();
|
||||||
{
|
message.AppendLine($"{able.Name}: {size.FormatAsBytes()}");
|
||||||
long size = equ.GetCacheBytes();
|
total += size;
|
||||||
message.AppendLine($"{able.Name}: {size.FormatAsBytes()}");
|
|
||||||
|
|
||||||
total += size;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message.AppendLine($"\nTotal: {total.FormatAsBytes()}\n\nClick \"No\" to erase caches.");
|
message.AppendLine($"\nTotal: {total.FormatAsBytes()}\n\nErase cache?");
|
||||||
|
|
||||||
DialogResult result = MessageBox.Show(message.ToString(), "Graph Caches", MessageBoxButtons.YesNo, MessageBoxIcon.Information);
|
DialogResult result = MessageBox.Show(message.ToString(), "Graph Caches", MessageBoxButtons.YesNo, MessageBoxIcon.Information);
|
||||||
if (result == DialogResult.No)
|
if (result == DialogResult.Yes)
|
||||||
{
|
{
|
||||||
foreach (Graphable able in ables)
|
foreach (Graphable able in ables) able.EraseCache();
|
||||||
{
|
|
||||||
if (able is Equation equ) equ.EraseCache();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,131 +1,119 @@
|
|||||||
using System;
|
namespace Graphing.Forms;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Data;
|
|
||||||
using System.Drawing;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Numerics;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Windows.Forms;
|
|
||||||
|
|
||||||
namespace Graphing.Forms
|
public partial class SetZoomForm : Form
|
||||||
{
|
{
|
||||||
public partial class SetZoomForm : Form
|
private double minZoomRange;
|
||||||
|
private double maxZoomRange;
|
||||||
|
|
||||||
|
private double zoomLevel;
|
||||||
|
|
||||||
|
private readonly GraphForm form;
|
||||||
|
|
||||||
|
public SetZoomForm(GraphForm form)
|
||||||
{
|
{
|
||||||
private double minZoomRange;
|
InitializeComponent();
|
||||||
private double maxZoomRange;
|
|
||||||
|
|
||||||
private double zoomLevel;
|
minZoomRange = 1 / (form.ZoomLevel * 2);
|
||||||
|
maxZoomRange = 2 / form.ZoomLevel;
|
||||||
|
zoomLevel = 1 / form.ZoomLevel;
|
||||||
|
|
||||||
private readonly GraphForm form;
|
ZoomTrackBar.Value = (int)(ZoomToFactor(zoomLevel) * (ZoomTrackBar.Maximum - ZoomTrackBar.Minimum) + ZoomTrackBar.Minimum);
|
||||||
|
|
||||||
public SetZoomForm(GraphForm form)
|
this.form = form;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnPaint(PaintEventArgs e)
|
||||||
|
{
|
||||||
|
ZoomMaxValue.Text = maxZoomRange.ToString("0.00");
|
||||||
|
ZoomMinValue.Text = minZoomRange.ToString("0.00");
|
||||||
|
|
||||||
|
ValueLabel.Text = $"{zoomLevel:0.00}x";
|
||||||
|
|
||||||
|
base.OnPaint(e);
|
||||||
|
|
||||||
|
form.ZoomLevel = 1 / zoomLevel;
|
||||||
|
form.Invalidate(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private double FactorToZoom(double factor)
|
||||||
|
{
|
||||||
|
return minZoomRange + (factor * factor) * (maxZoomRange - minZoomRange);
|
||||||
|
}
|
||||||
|
private double ZoomToFactor(double zoom)
|
||||||
|
{
|
||||||
|
double sqrValue = (zoom - minZoomRange) / (maxZoomRange - minZoomRange);
|
||||||
|
return Math.Sign(sqrValue) * Math.Sqrt(Math.Abs(sqrValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ZoomTrackBar_Scroll(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
double factor = (ZoomTrackBar.Value - ZoomTrackBar.Minimum) / (double)(ZoomTrackBar.Maximum - ZoomTrackBar.Minimum);
|
||||||
|
zoomLevel = FactorToZoom(factor);
|
||||||
|
|
||||||
|
Invalidate(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ZoomMinValue_TextChanged(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
double original = minZoomRange;
|
||||||
|
try
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
double value;
|
||||||
|
if (string.IsNullOrWhiteSpace(ZoomMinValue.Text) ||
|
||||||
|
ZoomMinValue.Text.EndsWith('.'))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
value = double.Parse(ZoomMinValue.Text);
|
||||||
|
if (value < 1e-2 || value > 1e3 || value > maxZoomRange) throw new();
|
||||||
|
}
|
||||||
|
|
||||||
minZoomRange = 1 / (form.ZoomLevel * 2);
|
minZoomRange = value;
|
||||||
maxZoomRange = 2 / form.ZoomLevel;
|
ZoomTrackBar.Value = (int)Math.Clamp(ZoomToFactor(zoomLevel) * (ZoomTrackBar.Maximum - ZoomTrackBar.Minimum) + ZoomTrackBar.Minimum, ZoomTrackBar.Minimum, ZoomTrackBar.Maximum);
|
||||||
zoomLevel = 1 / form.ZoomLevel;
|
|
||||||
|
|
||||||
ZoomTrackBar.Value = (int)(ZoomToFactor(zoomLevel) * (ZoomTrackBar.Maximum - ZoomTrackBar.Minimum) + ZoomTrackBar.Minimum);
|
|
||||||
|
|
||||||
this.form = form;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnPaint(PaintEventArgs e)
|
|
||||||
{
|
|
||||||
ZoomMaxValue.Text = maxZoomRange.ToString("0.00");
|
|
||||||
ZoomMinValue.Text = minZoomRange.ToString("0.00");
|
|
||||||
|
|
||||||
ValueLabel.Text = $"{zoomLevel:0.00}x";
|
|
||||||
|
|
||||||
base.OnPaint(e);
|
|
||||||
|
|
||||||
form.ZoomLevel = 1 / zoomLevel;
|
|
||||||
form.Invalidate(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private double FactorToZoom(double factor)
|
|
||||||
{
|
|
||||||
return minZoomRange + (factor * factor) * (maxZoomRange - minZoomRange);
|
|
||||||
}
|
|
||||||
private double ZoomToFactor(double zoom)
|
|
||||||
{
|
|
||||||
double sqrValue = (zoom - minZoomRange) / (maxZoomRange - minZoomRange);
|
|
||||||
return Math.Sign(sqrValue) * Math.Sqrt(Math.Abs(sqrValue));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ZoomTrackBar_Scroll(object? sender, EventArgs e)
|
|
||||||
{
|
|
||||||
double factor = (ZoomTrackBar.Value - ZoomTrackBar.Minimum) / (double)(ZoomTrackBar.Maximum - ZoomTrackBar.Minimum);
|
double factor = (ZoomTrackBar.Value - ZoomTrackBar.Minimum) / (double)(ZoomTrackBar.Maximum - ZoomTrackBar.Minimum);
|
||||||
zoomLevel = FactorToZoom(factor);
|
double newZoom = FactorToZoom(factor);
|
||||||
|
|
||||||
Invalidate(true);
|
zoomLevel = newZoom;
|
||||||
|
if (newZoom != factor) Invalidate(true);
|
||||||
}
|
}
|
||||||
|
catch
|
||||||
private void ZoomMinValue_TextChanged(object? sender, EventArgs e)
|
|
||||||
{
|
{
|
||||||
double original = minZoomRange;
|
minZoomRange = original;
|
||||||
try
|
ZoomMinValue.Text = minZoomRange.ToString("0.00");
|
||||||
{
|
|
||||||
double value;
|
|
||||||
if (string.IsNullOrWhiteSpace(ZoomMinValue.Text) ||
|
|
||||||
ZoomMinValue.Text.EndsWith('.'))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
value = double.Parse(ZoomMinValue.Text);
|
|
||||||
if (value < 1e-2 || value > 1e3 || value > maxZoomRange) throw new();
|
|
||||||
}
|
|
||||||
|
|
||||||
minZoomRange = value;
|
|
||||||
ZoomTrackBar.Value = (int)Math.Clamp(ZoomToFactor(zoomLevel) * (ZoomTrackBar.Maximum - ZoomTrackBar.Minimum) + ZoomTrackBar.Minimum, ZoomTrackBar.Minimum, ZoomTrackBar.Maximum);
|
|
||||||
double factor = (ZoomTrackBar.Value - ZoomTrackBar.Minimum) / (double)(ZoomTrackBar.Maximum - ZoomTrackBar.Minimum);
|
|
||||||
double newZoom = FactorToZoom(factor);
|
|
||||||
|
|
||||||
zoomLevel = newZoom;
|
|
||||||
if (newZoom != factor) Invalidate(true);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
minZoomRange = original;
|
|
||||||
ZoomMinValue.Text = minZoomRange.ToString("0.00");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void ZoomMaxValue_TextChanged(object sender, EventArgs e)
|
private void ZoomMaxValue_TextChanged(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
double original = maxZoomRange;
|
||||||
|
try
|
||||||
{
|
{
|
||||||
double original = maxZoomRange;
|
double value;
|
||||||
try
|
if (string.IsNullOrWhiteSpace(ZoomMaxValue.Text) ||
|
||||||
|
ZoomMaxValue.Text.EndsWith('.'))
|
||||||
{
|
{
|
||||||
double value;
|
return;
|
||||||
if (string.IsNullOrWhiteSpace(ZoomMaxValue.Text) ||
|
|
||||||
ZoomMaxValue.Text.EndsWith('.'))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
value = double.Parse(ZoomMaxValue.Text);
|
|
||||||
if (value < 1e-2 || value > 1e3 || value < minZoomRange) throw new();
|
|
||||||
}
|
|
||||||
|
|
||||||
maxZoomRange = value;
|
|
||||||
ZoomTrackBar.Value = (int)Math.Clamp(ZoomToFactor(zoomLevel) * (ZoomTrackBar.Maximum - ZoomTrackBar.Minimum) + ZoomTrackBar.Minimum, ZoomTrackBar.Minimum, ZoomTrackBar.Maximum);
|
|
||||||
double factor = (ZoomTrackBar.Value - ZoomTrackBar.Minimum) / (double)(ZoomTrackBar.Maximum - ZoomTrackBar.Minimum);
|
|
||||||
double newZoom = FactorToZoom(factor);
|
|
||||||
|
|
||||||
zoomLevel = newZoom;
|
|
||||||
if (newZoom != factor) Invalidate(true);
|
|
||||||
}
|
}
|
||||||
catch
|
else
|
||||||
{
|
{
|
||||||
maxZoomRange = original;
|
value = double.Parse(ZoomMaxValue.Text);
|
||||||
ZoomMaxValue.Text = maxZoomRange.ToString("0.00");
|
if (value < 1e-2 || value > 1e3 || value < minZoomRange) throw new();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
maxZoomRange = value;
|
||||||
|
ZoomTrackBar.Value = (int)Math.Clamp(ZoomToFactor(zoomLevel) * (ZoomTrackBar.Maximum - ZoomTrackBar.Minimum) + ZoomTrackBar.Minimum, ZoomTrackBar.Minimum, ZoomTrackBar.Maximum);
|
||||||
|
double factor = (ZoomTrackBar.Value - ZoomTrackBar.Minimum) / (double)(ZoomTrackBar.Maximum - ZoomTrackBar.Minimum);
|
||||||
|
double newZoom = FactorToZoom(factor);
|
||||||
|
|
||||||
|
zoomLevel = newZoom;
|
||||||
|
if (newZoom != factor) Invalidate(true);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
maxZoomRange = original;
|
||||||
|
ZoomMaxValue.Text = maxZoomRange.ToString("0.00");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
using Graphing.Forms;
|
using Graphing.Forms;
|
||||||
|
using Graphing.Parts;
|
||||||
|
|
||||||
namespace Graphing;
|
namespace Graphing;
|
||||||
|
|
||||||
@ -26,5 +27,8 @@ public abstract class Graphable
|
|||||||
Name = "Unnamed Graphable.";
|
Name = "Unnamed Graphable.";
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract IEnumerable<Line2d> GetItemsToRender(in GraphForm graph);
|
public abstract IEnumerable<IGraphPart> GetItemsToRender(in GraphForm graph);
|
||||||
|
|
||||||
|
public abstract void EraseCache();
|
||||||
|
public abstract long GetCacheBytes();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
using Graphing.Forms;
|
using Graphing.Forms;
|
||||||
|
using Graphing.Parts;
|
||||||
|
|
||||||
namespace Graphing.Graphables;
|
namespace Graphing.Graphables;
|
||||||
|
|
||||||
@ -19,73 +20,73 @@ public class Equation : Graphable
|
|||||||
cache = [];
|
cache = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public override IEnumerable<Line2d> GetItemsToRender(in GraphForm graph)
|
public override IEnumerable<IGraphPart> GetItemsToRender(in GraphForm graph)
|
||||||
{
|
{
|
||||||
const int step = 10;
|
const int step = 10;
|
||||||
double epsilon = Math.Abs(graph.ScreenSpaceToGraphSpace(new Int2(0, 0)).x
|
double epsilon = Math.Abs(graph.ScreenSpaceToGraphSpace(new Int2(0, 0)).x
|
||||||
- graph.ScreenSpaceToGraphSpace(new Int2(step / 2, 0)).x) / 5;
|
- graph.ScreenSpaceToGraphSpace(new Int2(step / 2, 0)).x) / 5;
|
||||||
|
|
||||||
List<Line2d> lines = [];
|
List<IGraphPart> lines = [];
|
||||||
|
|
||||||
bool addedToDictionary = false;
|
|
||||||
double previousX = graph.MinVisibleGraph.x;
|
double previousX = graph.MinVisibleGraph.x;
|
||||||
double previousY = GetFromCache(previousX, epsilon, ref addedToDictionary);
|
double previousY = GetFromCache(previousX, epsilon);
|
||||||
|
|
||||||
for (int i = 1; i < graph.ClientRectangle.Width; i += step)
|
for (int i = 1; i < graph.ClientRectangle.Width; i += step)
|
||||||
{
|
{
|
||||||
double currentX = graph.ScreenSpaceToGraphSpace(new Int2(i, 0)).x;
|
double currentX = graph.ScreenSpaceToGraphSpace(new Int2(i, 0)).x;
|
||||||
double currentY = GetFromCache(currentX, epsilon, ref addedToDictionary);
|
double currentY = GetFromCache(currentX, epsilon);
|
||||||
if (Math.Abs(currentY - previousY) <= 10)
|
if (Math.Abs(currentY - previousY) <= 10)
|
||||||
{
|
{
|
||||||
lines.Add(new Line2d(new Float2(previousX, previousY), new Float2(currentX, currentY)));
|
lines.Add(new GraphLine(new Float2(previousX, previousY), new Float2(currentX, currentY)));
|
||||||
}
|
}
|
||||||
previousX = currentX;
|
previousX = currentX;
|
||||||
previousY = currentY;
|
previousY = currentY;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (addedToDictionary) cache.Sort((a, b) => a.y.CompareTo(b.y));
|
// if (addedToDictionary) cache.Sort((a, b) => a.y.CompareTo(b.y)); todo: not required until binary search
|
||||||
|
|
||||||
return lines;
|
return lines;
|
||||||
}
|
}
|
||||||
|
|
||||||
public EquationDelegate GetDelegate() => equ;
|
public EquationDelegate GetDelegate() => equ;
|
||||||
|
|
||||||
public void EraseCache() => cache.Clear();
|
public override void EraseCache() => cache.Clear();
|
||||||
private double GetFromCache(double x, double epsilon, ref bool addedToDictionary)
|
private double GetFromCache(double x, double epsilon)
|
||||||
{
|
{
|
||||||
(double dist, double nearest) = NearestCachedPoint(x);
|
(double dist, double nearest, int index) = NearestCachedPoint(x);
|
||||||
if (dist < epsilon) return nearest;
|
if (dist < epsilon) return nearest;
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
addedToDictionary = true;
|
|
||||||
double result = equ(x);
|
double result = equ(x);
|
||||||
cache.Add(new(x, result));
|
cache.Add(new(x, result));
|
||||||
// TODO: Rather than sorting the whole list when we add a single number,
|
// TODO: Rather than sorting the whole list when we add a single number,
|
||||||
// we could just insert it.
|
// we could just insert it in its proper place.
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private (double dist, double y) NearestCachedPoint(double x)
|
private (double dist, double y, int index) NearestCachedPoint(double x)
|
||||||
{
|
{
|
||||||
// TODO: Replace with a binary search system.
|
// TODO: Replace with a binary search system.
|
||||||
double closestDist = double.PositiveInfinity;
|
double closestDist = double.PositiveInfinity;
|
||||||
double closest = 0;
|
double closest = 0;
|
||||||
|
int closestIndex = -1;
|
||||||
|
|
||||||
foreach (Float2 p in cache)
|
for (int i = 0; i < cache.Count; i++)
|
||||||
{
|
{
|
||||||
double dist = Math.Abs(x - p.x);
|
double dist = Math.Abs(x - cache[i].x);
|
||||||
if (dist < closestDist)
|
if (dist < closestDist)
|
||||||
{
|
{
|
||||||
closestDist = dist;
|
closestDist = dist;
|
||||||
closest = p.y;
|
closest = cache[i].y;
|
||||||
|
closestIndex = i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (closestDist, closest);
|
return (closestDist, closest, closestIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
public long GetCacheBytes() => cache.Count * 16;
|
public override long GetCacheBytes() => cache.Count * 16;
|
||||||
}
|
}
|
||||||
|
|
||||||
public delegate double EquationDelegate(double x);
|
public delegate double EquationDelegate(double x);
|
||||||
|
|||||||
@ -1,4 +1,6 @@
|
|||||||
using Graphing.Forms;
|
using Graphing.Forms;
|
||||||
|
using Graphing.Parts;
|
||||||
|
using static System.Windows.Forms.LinkLabel;
|
||||||
|
|
||||||
namespace Graphing.Graphables;
|
namespace Graphing.Graphables;
|
||||||
|
|
||||||
@ -9,6 +11,8 @@ public class SlopeField : Graphable
|
|||||||
private readonly SlopeFieldsDelegate equ;
|
private readonly SlopeFieldsDelegate equ;
|
||||||
private readonly double detail;
|
private readonly double detail;
|
||||||
|
|
||||||
|
private readonly List<(Float2, GraphLine)> cache;
|
||||||
|
|
||||||
public SlopeField(int detail, SlopeFieldsDelegate equ)
|
public SlopeField(int detail, SlopeFieldsDelegate equ)
|
||||||
{
|
{
|
||||||
slopeFieldNum++;
|
slopeFieldNum++;
|
||||||
@ -16,25 +20,26 @@ public class SlopeField : Graphable
|
|||||||
|
|
||||||
this.equ = equ;
|
this.equ = equ;
|
||||||
this.detail = detail;
|
this.detail = detail;
|
||||||
|
cache = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public override IEnumerable<Line2d> GetItemsToRender(in GraphForm graph)
|
public override IEnumerable<IGraphPart> GetItemsToRender(in GraphForm graph)
|
||||||
{
|
{
|
||||||
List<Line2d> lines = [];
|
double epsilon = 1 / (detail * 2);
|
||||||
|
List<IGraphPart> lines = [];
|
||||||
|
|
||||||
for (double x = Math.Ceiling(graph.MinVisibleGraph.x - 1); x < graph.MaxVisibleGraph.x + 1; x += 1 / detail)
|
for (double x = Math.Ceiling(graph.MinVisibleGraph.x - 1); x < graph.MaxVisibleGraph.x + 1; x += 1 / detail)
|
||||||
{
|
{
|
||||||
for (double y = Math.Ceiling(graph.MinVisibleGraph.y - 1); y < graph.MaxVisibleGraph.y + 1; y += 1 / detail)
|
for (double y = Math.Ceiling(graph.MinVisibleGraph.y - 1); y < graph.MaxVisibleGraph.y + 1; y += 1 / detail)
|
||||||
{
|
{
|
||||||
double slope = equ(x, y);
|
lines.Add(GetFromCache(epsilon, x, y));
|
||||||
lines.Add(MakeSlopeLine(new Float2(x, y), slope));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return lines;
|
return lines;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Line2d MakeSlopeLine(Float2 position, double slope)
|
private GraphLine MakeSlopeLine(Float2 position, double slope)
|
||||||
{
|
{
|
||||||
double size = detail;
|
double size = detail;
|
||||||
|
|
||||||
@ -46,6 +51,28 @@ public class SlopeField : Graphable
|
|||||||
|
|
||||||
return new(new(position.x + dirX, position.y + dirY), new(position.x - dirX, position.y - dirY));
|
return new(new(position.x + dirX, position.y + dirY), new(position.x - dirX, position.y - dirY));
|
||||||
}
|
}
|
||||||
|
private GraphLine GetFromCache(double epsilon, double x, double y)
|
||||||
|
{
|
||||||
|
// Probably no binary search here, though maybe it could be done
|
||||||
|
// in terms of just one axis.
|
||||||
|
|
||||||
|
foreach ((Float2 p, GraphLine l) in cache)
|
||||||
|
{
|
||||||
|
double diffX = Math.Abs(p.x - x),
|
||||||
|
diffY = Math.Abs(p.y - y);
|
||||||
|
|
||||||
|
if (diffX < epsilon && diffY < epsilon) return l;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new value.
|
||||||
|
double slope = equ(x, y);
|
||||||
|
GraphLine result = MakeSlopeLine(new Float2(x, y), slope);
|
||||||
|
cache.Add((new Float2(x, y), result));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void EraseCache() => cache.Clear();
|
||||||
|
public override long GetCacheBytes() => cache.Count * 48;
|
||||||
}
|
}
|
||||||
|
|
||||||
public delegate double SlopeFieldsDelegate(double x, double y);
|
public delegate double SlopeFieldsDelegate(double x, double y);
|
||||||
|
|||||||
47
Base/Graphables/TangentLine.cs
Normal file
47
Base/Graphables/TangentLine.cs
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
using Graphing.Forms;
|
||||||
|
using Graphing.Parts;
|
||||||
|
|
||||||
|
namespace Graphing.Graphables;
|
||||||
|
|
||||||
|
public class TangentLine : Graphable
|
||||||
|
{
|
||||||
|
public double Position { get; set; }
|
||||||
|
|
||||||
|
private readonly EquationDelegate parentEqu;
|
||||||
|
|
||||||
|
private readonly double length;
|
||||||
|
|
||||||
|
public TangentLine(double length, double position, Equation parent)
|
||||||
|
{
|
||||||
|
Name = $"Tangent Line of {parent.Name}";
|
||||||
|
|
||||||
|
parentEqu = parent.GetDelegate();
|
||||||
|
Position = position;
|
||||||
|
this.length = length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IEnumerable<IGraphPart> GetItemsToRender(in GraphForm graph)
|
||||||
|
{
|
||||||
|
Float2 point = new(Position, parentEqu(Position));
|
||||||
|
return [MakeSlopeLine(point, DerivativeAtPoint(Position)),
|
||||||
|
new GraphCircle(point, 8)];
|
||||||
|
}
|
||||||
|
private GraphLine MakeSlopeLine(Float2 position, double slope)
|
||||||
|
{
|
||||||
|
double dirX = length, dirY = slope * length;
|
||||||
|
double magnitude = Math.Sqrt(dirX * dirX + dirY * dirY);
|
||||||
|
|
||||||
|
dirX /= magnitude * 2 / length;
|
||||||
|
dirY /= magnitude * 2 / length;
|
||||||
|
|
||||||
|
return new(new(position.x + dirX, position.y + dirY), new(position.x - dirX, position.y - dirY));
|
||||||
|
}
|
||||||
|
private double DerivativeAtPoint(double x)
|
||||||
|
{
|
||||||
|
const double step = 1e-3;
|
||||||
|
return (parentEqu(x + step) - parentEqu(x)) / step;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void EraseCache() { }
|
||||||
|
public override long GetCacheBytes() => 0;
|
||||||
|
}
|
||||||
8
Base/IGraphPart.cs
Normal file
8
Base/IGraphPart.cs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
using Graphing.Forms;
|
||||||
|
|
||||||
|
namespace Graphing;
|
||||||
|
|
||||||
|
public interface IGraphPart
|
||||||
|
{
|
||||||
|
public void Render(in GraphForm form, in Graphics g, in Brush brush);
|
||||||
|
}
|
||||||
@ -1,18 +0,0 @@
|
|||||||
namespace Graphing;
|
|
||||||
|
|
||||||
public record struct Line2d
|
|
||||||
{
|
|
||||||
public Float2 a;
|
|
||||||
public Float2 b;
|
|
||||||
|
|
||||||
public Line2d()
|
|
||||||
{
|
|
||||||
a = new();
|
|
||||||
b = new();
|
|
||||||
}
|
|
||||||
public Line2d(Float2 a, Float2 b)
|
|
||||||
{
|
|
||||||
this.a = a;
|
|
||||||
this.b = b;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
31
Base/Parts/GraphCircle.cs
Normal file
31
Base/Parts/GraphCircle.cs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
using Graphing.Forms;
|
||||||
|
|
||||||
|
namespace Graphing.Parts;
|
||||||
|
|
||||||
|
public record struct GraphCircle : IGraphPart
|
||||||
|
{
|
||||||
|
public Float2 center;
|
||||||
|
public int radius;
|
||||||
|
|
||||||
|
public GraphCircle()
|
||||||
|
{
|
||||||
|
center = new();
|
||||||
|
radius = 1;
|
||||||
|
}
|
||||||
|
public GraphCircle(Float2 center, int radius)
|
||||||
|
{
|
||||||
|
this.center = center;
|
||||||
|
this.radius = radius;
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly void Render(in GraphForm form, in Graphics g, in Brush brush)
|
||||||
|
{
|
||||||
|
if (!double.IsNormal(center.x) || !double.IsNormal(center.y) ||
|
||||||
|
!double.IsNormal(radius)) return;
|
||||||
|
|
||||||
|
Int2 centerPix = form.GraphSpaceToScreenSpace(center);
|
||||||
|
g.FillEllipse(brush, new Rectangle(new Point(centerPix.x - radius,
|
||||||
|
centerPix.y - radius),
|
||||||
|
new Size(radius * 2, radius * 2)));
|
||||||
|
}
|
||||||
|
}
|
||||||
32
Base/Parts/GraphLine.cs
Normal file
32
Base/Parts/GraphLine.cs
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
using Graphing.Forms;
|
||||||
|
|
||||||
|
namespace Graphing.Parts;
|
||||||
|
|
||||||
|
public record struct GraphLine : IGraphPart
|
||||||
|
{
|
||||||
|
public Float2 a;
|
||||||
|
public Float2 b;
|
||||||
|
|
||||||
|
public GraphLine()
|
||||||
|
{
|
||||||
|
a = new();
|
||||||
|
b = new();
|
||||||
|
}
|
||||||
|
public GraphLine(Float2 a, Float2 b)
|
||||||
|
{
|
||||||
|
this.a = a;
|
||||||
|
this.b = b;
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly void Render(in GraphForm form, in Graphics g, in Brush brush)
|
||||||
|
{
|
||||||
|
if (!double.IsNormal(a.x) || !double.IsNormal(a.y) ||
|
||||||
|
!double.IsNormal(b.x) || !double.IsNormal(b.y)) return;
|
||||||
|
|
||||||
|
Int2 start = form.GraphSpaceToScreenSpace(a),
|
||||||
|
end = form.GraphSpaceToScreenSpace(b);
|
||||||
|
|
||||||
|
Pen pen = new(brush, 3);
|
||||||
|
g.DrawLine(pen, start, end);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -10,12 +10,21 @@ internal static class Program
|
|||||||
{
|
{
|
||||||
Application.EnableVisualStyles();
|
Application.EnableVisualStyles();
|
||||||
Application.SetCompatibleTextRenderingDefault(false);
|
Application.SetCompatibleTextRenderingDefault(false);
|
||||||
Application.SetHighDpiMode(HighDpiMode.PerMonitorV2);
|
Application.SetHighDpiMode(HighDpiMode.SystemAware);
|
||||||
|
|
||||||
GraphForm graph = new("One Of The Graphing Calculators Of All Time");
|
GraphForm graph = new("One Of The Graphing Calculators Of All Time");
|
||||||
graph.Graph(new Equation(x => Math.Pow(2, x)));
|
|
||||||
graph.Graph(new Equation(Math.Log2));
|
Equation equ = new(x => x * x);
|
||||||
|
TangentLine tangent = new(5, 2, equ);
|
||||||
|
|
||||||
|
graph.Graph(equ);
|
||||||
|
graph.Graph(tangent);
|
||||||
|
|
||||||
Application.Run(graph);
|
Application.Run(graph);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static double PopulationGraph(double max, double k, double A, double t)
|
||||||
|
{
|
||||||
|
return max / (1 + A * Math.Exp(-k * t));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user