From fc829d6a6bf1d4116899b30a1b859807f615c35e Mon Sep 17 00:00:00 2001 From: That_One_Nerd Date: Sat, 9 Mar 2024 16:10:06 -0500 Subject: [PATCH] Made binary search work with the caching. Much faster, very happy with it. --- Base/Forms/GraphForm.cs | 5 +- Base/Graphable.cs | 2 + Base/Graphables/ColumnTable.cs | 51 +++++++++++++++++++ Base/Graphables/Equation.cs | 51 +++++++++++-------- Base/Graphables/SlopeField.cs | 5 +- Base/Graphables/TangentLine.cs | 6 ++- Base/Parts/GraphLine.cs | 4 +- Base/Parts/GraphRectangle.cs | 45 ++++++++++++++++ .../{GraphCircle.cs => GraphUiCircle.cs} | 10 ++-- Testing/Program.cs | 19 +++---- 10 files changed, 155 insertions(+), 43 deletions(-) create mode 100644 Base/Graphables/ColumnTable.cs create mode 100644 Base/Parts/GraphRectangle.cs rename Base/Parts/{GraphCircle.cs => GraphUiCircle.cs} (71%) diff --git a/Base/Forms/GraphForm.cs b/Base/Forms/GraphForm.cs index 69379d8..3a2b587 100644 --- a/Base/Forms/GraphForm.cs +++ b/Base/Forms/GraphForm.cs @@ -1,6 +1,5 @@ using Graphing.Extensions; using Graphing.Graphables; -using Graphing.Parts; using System.Drawing.Drawing2D; using System.Text; @@ -170,9 +169,9 @@ public partial class GraphForm : Form Invalidate(false); } - public void Graph(Graphable able) + public void Graph(params Graphable[] able) { - ables.Add(able); + ables.AddRange(able); RegenerateMenuItems(); Invalidate(false); } diff --git a/Base/Graphable.cs b/Base/Graphable.cs index 7dc48a5..5f42e15 100644 --- a/Base/Graphable.cs +++ b/Base/Graphable.cs @@ -29,6 +29,8 @@ public abstract class Graphable public abstract IEnumerable GetItemsToRender(in GraphForm graph); + public abstract Graphable DeepCopy(); + public abstract void EraseCache(); public abstract long GetCacheBytes(); } diff --git a/Base/Graphables/ColumnTable.cs b/Base/Graphables/ColumnTable.cs new file mode 100644 index 0000000..fb18e58 --- /dev/null +++ b/Base/Graphables/ColumnTable.cs @@ -0,0 +1,51 @@ +using Graphing.Forms; +using Graphing.Parts; + +namespace Graphing.Graphables; + +public class ColumnTable : Graphable +{ + private static int tableNum; + + private readonly Dictionary tableXY; + private readonly double width; + + public ColumnTable(double width, Dictionary tableXY) + { + tableNum++; + Name = $"Column Table {tableNum}"; + + this.tableXY = tableXY; + this.width = width; + } + public ColumnTable(double step, Equation equation, double min, double max) + { + Name = $"Column Table for {equation.Name}"; + + tableXY = []; + EquationDelegate equ = equation.GetDelegate(); + width = 0.75 * step; + + double minRounded = Math.Round(min / step) * step, + maxRounded = Math.Round(max / step) * step; + for (double x = minRounded; x <= maxRounded; x += step) + tableXY.Add(x, equ(x)); + } + + public override void EraseCache() { } + public override long GetCacheBytes() => 16 * tableXY.Count; + + public override Graphable DeepCopy() => new ColumnTable(width / 0.75, tableXY.ToArray().ToDictionary()); + + public override IEnumerable GetItemsToRender(in GraphForm graph) + { + List items = []; + foreach (KeyValuePair col in tableXY) + { + items.Add(GraphRectangle.FromSize(new Float2(col.Key, col.Value / 2), + new Float2(width, col.Value))); + } + + return items; + } +} diff --git a/Base/Graphables/Equation.cs b/Base/Graphables/Equation.cs index e9aae99..7427b7c 100644 --- a/Base/Graphables/Equation.cs +++ b/Base/Graphables/Equation.cs @@ -8,7 +8,6 @@ public class Equation : Graphable private static int equationNum; private readonly EquationDelegate equ; - private readonly List cache; public Equation(EquationDelegate equ) @@ -42,9 +41,6 @@ public class Equation : Graphable previousX = currentX; previousY = currentY; } - - // if (addedToDictionary) cache.Sort((a, b) => a.y.CompareTo(b.y)); todo: not required until binary search - return lines; } @@ -58,34 +54,47 @@ public class Equation : Graphable else { double result = equ(x); - cache.Add(new(x, result)); - // TODO: Rather than sorting the whole list when we add a single number, - // we could just insert it in its proper place. + cache.Insert(index + 1, new(x, result)); return result; } } + // Pretty sure this works. Certainly works pretty well with "hard-to-compute" + // equations. private (double dist, double y, int index) NearestCachedPoint(double x) { - // TODO: Replace with a binary search system. - double closestDist = double.PositiveInfinity; - double closest = 0; - int closestIndex = -1; - - for (int i = 0; i < cache.Count; i++) + if (cache.Count == 0) return (double.PositiveInfinity, double.NaN, -1); + else if (cache.Count == 1) { - double dist = Math.Abs(x - cache[i].x); - if (dist < closestDist) - { - closestDist = dist; - closest = cache[i].y; - closestIndex = i; - } + Float2 single = cache[0]; + return (Math.Abs(single.x - x), single.y, 0); } + else + { + int boundA = 0, boundB = cache.Count; + do + { + int boundC = (boundA + boundB) / 2; + Float2 pointC = cache[boundC]; - return (closestDist, closest, closestIndex); + if (pointC.x == x) return (0, pointC.y, boundC); + else if (pointC.x > x) + { + boundA = boundC; + } + else // pointC.x < x + { + boundB = boundC; + } + + } while (boundB - boundA > 1); + + return (Math.Abs(cache[boundA].x - x), cache[boundA].y, boundA); + } } + public override Graphable DeepCopy() => new Equation(equ); + public override long GetCacheBytes() => cache.Count * 16; } diff --git a/Base/Graphables/SlopeField.cs b/Base/Graphables/SlopeField.cs index 71074d3..1307610 100644 --- a/Base/Graphables/SlopeField.cs +++ b/Base/Graphables/SlopeField.cs @@ -1,6 +1,5 @@ using Graphing.Forms; using Graphing.Parts; -using static System.Windows.Forms.LinkLabel; namespace Graphing.Graphables; @@ -9,7 +8,7 @@ public class SlopeField : Graphable private static int slopeFieldNum; private readonly SlopeFieldsDelegate equ; - private readonly double detail; + private readonly int detail; private readonly List<(Float2, GraphLine)> cache; @@ -71,6 +70,8 @@ public class SlopeField : Graphable return result; } + public override Graphable DeepCopy() => new SlopeField(detail, equ); + public override void EraseCache() => cache.Clear(); public override long GetCacheBytes() => cache.Count * 48; } diff --git a/Base/Graphables/TangentLine.cs b/Base/Graphables/TangentLine.cs index 43e3739..e179329 100644 --- a/Base/Graphables/TangentLine.cs +++ b/Base/Graphables/TangentLine.cs @@ -7,6 +7,7 @@ public class TangentLine : Graphable { public double Position { get; set; } + private readonly Equation parent; private readonly EquationDelegate parentEqu; private readonly double length; @@ -18,13 +19,14 @@ public class TangentLine : Graphable parentEqu = parent.GetDelegate(); Position = position; this.length = length; + this.parent = parent; } public override IEnumerable GetItemsToRender(in GraphForm graph) { Float2 point = new(Position, parentEqu(Position)); return [MakeSlopeLine(point, DerivativeAtPoint(Position)), - new GraphCircle(point, 8)]; + new GraphUiCircle(point, 8)]; } private GraphLine MakeSlopeLine(Float2 position, double slope) { @@ -42,6 +44,8 @@ public class TangentLine : Graphable return (parentEqu(x + step) - parentEqu(x)) / step; } + public override Graphable DeepCopy() => new TangentLine(length, Position, parent); + public override void EraseCache() { } public override long GetCacheBytes() => 0; } diff --git a/Base/Parts/GraphLine.cs b/Base/Parts/GraphLine.cs index a185f5c..fff70f1 100644 --- a/Base/Parts/GraphLine.cs +++ b/Base/Parts/GraphLine.cs @@ -20,8 +20,8 @@ public record struct GraphLine : IGraphPart 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; + if (!double.IsFinite(a.x) || !double.IsFinite(a.y) || + !double.IsFinite(b.x) || !double.IsFinite(b.y)) return; Int2 start = form.GraphSpaceToScreenSpace(a), end = form.GraphSpaceToScreenSpace(b); diff --git a/Base/Parts/GraphRectangle.cs b/Base/Parts/GraphRectangle.cs new file mode 100644 index 0000000..ce2c37f --- /dev/null +++ b/Base/Parts/GraphRectangle.cs @@ -0,0 +1,45 @@ +using Graphing.Forms; + +namespace Graphing.Parts; + +public struct GraphRectangle : IGraphPart +{ + public Float2 min, max; + + public GraphRectangle() + { + min = new(); + max = new(); + } + + public static GraphRectangle FromSize(Float2 center, Float2 size) => new() + { + min = new(center.x - size.x / 2, + center.y - size.y / 2), + max = new(center.x + size.x / 2, + center.y + size.y / 2) + }; + public static GraphRectangle FromRange(Float2 min, Float2 max) => new() + { + min = min, + max = max + }; + + public void Render(in GraphForm form, in Graphics g, in Brush brush) + { + if (!double.IsFinite(max.x) || !double.IsFinite(max.y) || + !double.IsFinite(min.x) || !double.IsFinite(min.y)) return; + + if (min.x > max.x) (min.x, max.x) = (max.x, min.x); + if (min.y > max.y) (min.y, max.y) = (max.y, min.y); + + Int2 start = form.GraphSpaceToScreenSpace(min), + end = form.GraphSpaceToScreenSpace(max); + + Int2 size = new(end.x - start.x + 1, + start.y - end.y); + + if (size.x == 0 || size.y == 0) return; + g.FillRectangle(brush, new Rectangle(start.x, end.y, size.x, size.y)); + } +} diff --git a/Base/Parts/GraphCircle.cs b/Base/Parts/GraphUiCircle.cs similarity index 71% rename from Base/Parts/GraphCircle.cs rename to Base/Parts/GraphUiCircle.cs index a5154c6..28bb010 100644 --- a/Base/Parts/GraphCircle.cs +++ b/Base/Parts/GraphUiCircle.cs @@ -2,17 +2,17 @@ namespace Graphing.Parts; -public record struct GraphCircle : IGraphPart +public record struct GraphUiCircle : IGraphPart { public Float2 center; public int radius; - public GraphCircle() + public GraphUiCircle() { center = new(); radius = 1; } - public GraphCircle(Float2 center, int radius) + public GraphUiCircle(Float2 center, int radius) { this.center = center; this.radius = radius; @@ -20,8 +20,8 @@ public record struct GraphCircle : IGraphPart 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; + if (!double.IsFinite(center.x) || !double.IsFinite(center.y) || + !double.IsFinite(radius) || radius == 0) return; Int2 centerPix = form.GraphSpaceToScreenSpace(center); g.FillEllipse(brush, new Rectangle(new Point(centerPix.x - radius, diff --git a/Testing/Program.cs b/Testing/Program.cs index 5aec97a..6b53e4a 100644 --- a/Testing/Program.cs +++ b/Testing/Program.cs @@ -14,17 +14,18 @@ internal static class Program GraphForm graph = new("One Of The Graphing Calculators Of All Time"); - Equation equ = new(x => x * x); - TangentLine tangent = new(5, 2, equ); - + Equation equ = new(x => + { + // Demonstrate the caching abilities of the software. + // This extra waiting is done every time the form requires a + // calculation done. At the start, it'll be laggy, but as you + // move around and zoom in, more pieces are cached, and when + // you reset, the viewport will be a lot less laggy. + for (int i = 0; i < 1_000_000; i++) ; + return x * x; + }); graph.Graph(equ); - graph.Graph(tangent); Application.Run(graph); } - - private static double PopulationGraph(double max, double k, double A, double t) - { - return max / (1 + A * Math.Exp(-k * t)); - } }