diff --git a/Base/Extensions/FormattingExtensions.cs b/Base/Extensions/FormattingExtensions.cs new file mode 100644 index 0000000..baf27c2 --- /dev/null +++ b/Base/Extensions/FormattingExtensions.cs @@ -0,0 +1,32 @@ +namespace Graphing.Extensions; + +public static class FormattingExtensions +{ + private static readonly string[] sizeUnits = + [ + " bytes", + " KB", + " MB", + " GB", + " TB", + " PB", + ]; + + public static string FormatAsBytes(this long bytes) + { + double val = bytes; + int unitIndex = 0; + + while (val > 1024) + { + unitIndex++; + val /= 1024; + } + + string result; + if (unitIndex == 0) result = val.ToString("0"); + else result = val.ToString("0.00"); + + return result + sizeUnits[unitIndex]; + } +} diff --git a/Base/Forms/GraphForm.Designer.cs b/Base/Forms/GraphForm.Designer.cs index 23704fe..15c0cfe 100644 --- a/Base/Forms/GraphForm.Designer.cs +++ b/Base/Forms/GraphForm.Designer.cs @@ -39,6 +39,8 @@ MenuEquations = new ToolStripMenuItem(); MenuEquationsDerivative = new ToolStripMenuItem(); MenuEquationsIntegral = new ToolStripMenuItem(); + MenuMisc = new ToolStripMenuItem(); + MenuMiscCaches = new ToolStripMenuItem(); GraphMenu.SuspendLayout(); SuspendLayout(); // @@ -58,7 +60,7 @@ // GraphMenu // GraphMenu.ImageScalingSize = new Size(32, 32); - GraphMenu.Items.AddRange(new ToolStripItem[] { MenuViewport, MenuColors, MenuEquations }); + GraphMenu.Items.AddRange(new ToolStripItem[] { MenuViewport, MenuColors, MenuEquations, MenuMisc }); GraphMenu.Location = new Point(0, 0); GraphMenu.Name = "GraphMenu"; GraphMenu.Size = new Size(1449, 42); @@ -125,6 +127,20 @@ MenuEquationsIntegral.Size = new Size(360, 44); MenuEquationsIntegral.Text = "Compute Integral"; // + // MenuMisc + // + MenuMisc.DropDownItems.AddRange(new ToolStripItem[] { MenuMiscCaches }); + MenuMisc.Name = "MenuMisc"; + MenuMisc.Size = new Size(83, 38); + MenuMisc.Text = "Misc"; + // + // MenuMiscCaches + // + MenuMiscCaches.Name = "MenuMiscCaches"; + MenuMiscCaches.Size = new Size(359, 44); + MenuMiscCaches.Text = "View Caches"; + MenuMiscCaches.Click += MenuMiscCaches_Click; + // // GraphForm // AutoScaleDimensions = new SizeF(13F, 32F); @@ -154,5 +170,7 @@ private ToolStripMenuItem MenuEquations; private ToolStripMenuItem MenuEquationsDerivative; private ToolStripMenuItem MenuEquationsIntegral; + private ToolStripMenuItem MenuMisc; + private ToolStripMenuItem MenuMiscCaches; } } \ No newline at end of file diff --git a/Base/Forms/GraphForm.cs b/Base/Forms/GraphForm.cs index 66fe8a5..c1e20ea 100644 --- a/Base/Forms/GraphForm.cs +++ b/Base/Forms/GraphForm.cs @@ -1,4 +1,6 @@ -using Graphing.Graphables; +using Graphing.Extensions; +using Graphing.Graphables; +using System.Text; namespace Graphing.Forms; @@ -346,7 +348,7 @@ public partial class GraphForm : Form static double Integrate(EquationDelegate e, double lower, double upper) { // TODO: a better rendering method could make this much faster. - const double step = 1e-1; + const double step = 1e-2; double factor = 1; if (upper < lower) @@ -364,4 +366,33 @@ public partial class GraphForm : Form return sum * factor; } } + + private void MenuMiscCaches_Click(object? sender, EventArgs e) + { + // TODO: Replace with a form with a pie chart of the use by equation + // and the ability to reset them. + StringBuilder message = new(); + long total = 0; + foreach (Graphable able in ables) + { + if (able is Equation equ) + { + long size = equ.GetCacheBytes(); + message.AppendLine($"{able.Name}: {size.FormatAsBytes()}"); + + total += size; + } + } + + message.AppendLine($"\nTotal: {total.FormatAsBytes()}\n\nClick \"No\" to erase caches."); + + DialogResult result = MessageBox.Show(message.ToString(), "Graph Caches", MessageBoxButtons.YesNo, MessageBoxIcon.Information); + if (result == DialogResult.No) + { + foreach (Graphable able in ables) + { + if (able is Equation equ) equ.EraseCache(); + } + } + } } diff --git a/Base/Graphables/Equation.cs b/Base/Graphables/Equation.cs index dd0f2f0..c7eb67d 100644 --- a/Base/Graphables/Equation.cs +++ b/Base/Graphables/Equation.cs @@ -8,23 +8,33 @@ public class Equation : Graphable private readonly EquationDelegate equ; + private readonly List cache; + public Equation(EquationDelegate equ) { equationNum++; Name = $"Equation {equationNum}"; this.equ = equ; + cache = []; } public override IEnumerable GetItemsToRender(in GraphForm graph) { + const int step = 10; + double epsilon = Math.Abs(graph.ScreenSpaceToGraphSpace(new Int2(0, 0)).x + - graph.ScreenSpaceToGraphSpace(new Int2(step / 2, 0)).x) / 5; + List lines = []; + + bool addedToDictionary = false; double previousX = graph.MinVisibleGraph.x; - double previousY = equ(previousX); - for (int i = 1; i < graph.ClientRectangle.Width; i += 10) + double previousY = GetFromCache(previousX, epsilon, ref addedToDictionary); + + for (int i = 1; i < graph.ClientRectangle.Width; i += step) { double currentX = graph.ScreenSpaceToGraphSpace(new Int2(i, 0)).x; - double currentY = equ(currentX); + double currentY = GetFromCache(currentX, epsilon, ref addedToDictionary); if (Math.Abs(currentY - previousY) <= 10) { lines.Add(new Line2d(new Float2(previousX, previousY), new Float2(currentX, currentY))); @@ -32,10 +42,50 @@ public class Equation : Graphable previousX = currentX; previousY = currentY; } + + if (addedToDictionary) cache.Sort((a, b) => a.y.CompareTo(b.y)); + return lines; } public EquationDelegate GetDelegate() => equ; + + public void EraseCache() => cache.Clear(); + private double GetFromCache(double x, double epsilon, ref bool addedToDictionary) + { + (double dist, double nearest) = NearestCachedPoint(x); + if (dist < epsilon) return nearest; + else + { + addedToDictionary = true; + 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. + return result; + } + } + + private (double dist, double y) NearestCachedPoint(double x) + { + // TODO: Replace with a binary search system. + double closestDist = double.PositiveInfinity; + double closest = 0; + + foreach (Float2 p in cache) + { + double dist = Math.Abs(x - p.x); + if (dist < closestDist) + { + closestDist = dist; + closest = p.y; + } + } + + return (closestDist, closest); + } + + public long GetCacheBytes() => cache.Count * 16; } public delegate double EquationDelegate(double x); diff --git a/Testing/Program.cs b/Testing/Program.cs index e622a5e..40a670b 100644 --- a/Testing/Program.cs +++ b/Testing/Program.cs @@ -13,7 +13,8 @@ internal static class Program Application.SetHighDpiMode(HighDpiMode.PerMonitorV2); GraphForm graph = new("One Of The Graphing Calculators Of All Time"); - graph.Graph(new Equation(Math.Cos)); + graph.Graph(new Equation(x => Math.Pow(2, x))); + graph.Graph(new Equation(Math.Log2)); Application.Run(graph); } diff --git a/Testing/Testing.csproj b/Testing/Testing.csproj index 00b21a5..fe3e2f2 100644 --- a/Testing/Testing.csproj +++ b/Testing/Testing.csproj @@ -1,7 +1,6 @@  - WinExe net8.0-windows enable true @@ -9,6 +8,14 @@ Graphing.Testing ThatOneNerd.Graphing.Testing + + + Exe + + + + WinExe +