From 21c498f445acfe7a0a58d209311def867f930f8b Mon Sep 17 00:00:00 2001 From: That_One_Nerd Date: Tue, 27 Feb 2024 16:49:06 -0500 Subject: [PATCH 1/6] Probably done for today. Added a start to some caching optimizations. --- Base/Extensions/FormattingExtensions.cs | 32 ++++++++++++++ Base/Forms/GraphForm.Designer.cs | 20 ++++++++- Base/Forms/GraphForm.cs | 35 +++++++++++++++- Base/Graphables/Equation.cs | 56 +++++++++++++++++++++++-- Testing/Program.cs | 3 +- Testing/Testing.csproj | 9 +++- 6 files changed, 147 insertions(+), 8 deletions(-) create mode 100644 Base/Extensions/FormattingExtensions.cs 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 + From e31e6bfdb653f61b4f6d5cad34a99f6036d077cb Mon Sep 17 00:00:00 2001 From: That_One_Nerd Date: Thu, 29 Feb 2024 10:44:28 -0500 Subject: [PATCH 2/6] Various things. Added a tangent line system, allows for different graph parts, and more caching stuff. --- Base/Forms/GraphForm.cs | 36 ++---- Base/Forms/SetZoomForm.cs | 206 ++++++++++++++++----------------- Base/Graphable.cs | 6 +- Base/Graphables/Equation.cs | 37 +++--- Base/Graphables/SlopeField.cs | 37 +++++- Base/Graphables/TangentLine.cs | 47 ++++++++ Base/IGraphPart.cs | 8 ++ Base/Line2d.cs | 18 --- Base/Parts/GraphCircle.cs | 31 +++++ Base/Parts/GraphLine.cs | 32 +++++ Testing/Program.cs | 15 ++- 11 files changed, 294 insertions(+), 179 deletions(-) create mode 100644 Base/Graphables/TangentLine.cs create mode 100644 Base/IGraphPart.cs delete mode 100644 Base/Line2d.cs create mode 100644 Base/Parts/GraphCircle.cs create mode 100644 Base/Parts/GraphLine.cs diff --git a/Base/Forms/GraphForm.cs b/Base/Forms/GraphForm.cs index c1e20ea..69379d8 100644 --- a/Base/Forms/GraphForm.cs +++ b/Base/Forms/GraphForm.cs @@ -1,5 +1,7 @@ using Graphing.Extensions; using Graphing.Graphables; +using Graphing.Parts; +using System.Drawing.Drawing2D; using System.Text; namespace Graphing.Forms; @@ -145,6 +147,7 @@ public partial class GraphForm : Form protected override void OnPaint(PaintEventArgs e) { Graphics g = e.Graphics; + g.SmoothingMode = SmoothingMode.HighQuality; Brush background = new SolidBrush(Color.White); g.FillRectangle(background, e.ClipRectangle); @@ -154,19 +157,9 @@ public partial class GraphForm : Form // Draw the actual graphs. for (int i = 0; i < ables.Count; i++) { - IEnumerable lines = ables[i].GetItemsToRender(this); + IEnumerable lines = ables[i].GetItemsToRender(this); Brush graphBrush = new SolidBrush(ables[i].Color); - Pen penBrush = new(graphBrush, 3); - - 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); - } + foreach (IGraphPart gp in lines) gp.Render(this, g, graphBrush); } base.OnPaint(e); @@ -375,24 +368,17 @@ public partial class GraphForm : Form 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; - } + long size = able.GetCacheBytes(); + 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); - if (result == DialogResult.No) + if (result == DialogResult.Yes) { - foreach (Graphable able in ables) - { - if (able is Equation equ) equ.EraseCache(); - } + foreach (Graphable able in ables) able.EraseCache(); } } } diff --git a/Base/Forms/SetZoomForm.cs b/Base/Forms/SetZoomForm.cs index 30e2567..1d30ee4 100644 --- a/Base/Forms/SetZoomForm.cs +++ b/Base/Forms/SetZoomForm.cs @@ -1,131 +1,119 @@ -using System; -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; -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; - private double maxZoomRange; + InitializeComponent(); - 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); - maxZoomRange = 2 / form.ZoomLevel; - 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) - { + 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); - zoomLevel = FactorToZoom(factor); + double newZoom = FactorToZoom(factor); - Invalidate(true); + zoomLevel = newZoom; + if (newZoom != factor) Invalidate(true); } - - private void ZoomMinValue_TextChanged(object? sender, EventArgs e) + catch { - double original = minZoomRange; - try - { - 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"); - } + 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; - try + double value; + if (string.IsNullOrWhiteSpace(ZoomMaxValue.Text) || + ZoomMaxValue.Text.EndsWith('.')) { - double value; - 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); + return; } - catch + else { - maxZoomRange = original; - ZoomMaxValue.Text = maxZoomRange.ToString("0.00"); + 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 + { + maxZoomRange = original; + ZoomMaxValue.Text = maxZoomRange.ToString("0.00"); } } } diff --git a/Base/Graphable.cs b/Base/Graphable.cs index 1ccd839..7dc48a5 100644 --- a/Base/Graphable.cs +++ b/Base/Graphable.cs @@ -1,4 +1,5 @@ using Graphing.Forms; +using Graphing.Parts; namespace Graphing; @@ -26,5 +27,8 @@ public abstract class Graphable Name = "Unnamed Graphable."; } - public abstract IEnumerable GetItemsToRender(in GraphForm graph); + public abstract IEnumerable GetItemsToRender(in GraphForm graph); + + public abstract void EraseCache(); + public abstract long GetCacheBytes(); } diff --git a/Base/Graphables/Equation.cs b/Base/Graphables/Equation.cs index c7eb67d..e9aae99 100644 --- a/Base/Graphables/Equation.cs +++ b/Base/Graphables/Equation.cs @@ -1,4 +1,5 @@ using Graphing.Forms; +using Graphing.Parts; namespace Graphing.Graphables; @@ -19,73 +20,73 @@ public class Equation : Graphable cache = []; } - public override IEnumerable GetItemsToRender(in GraphForm graph) + 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 = []; + List lines = []; - bool addedToDictionary = false; 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) { 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) { - 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; 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; } public EquationDelegate GetDelegate() => equ; - public void EraseCache() => cache.Clear(); - private double GetFromCache(double x, double epsilon, ref bool addedToDictionary) + public override void EraseCache() => cache.Clear(); + 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; 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. + // we could just insert it in its proper place. 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. double closestDist = double.PositiveInfinity; 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) { 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); diff --git a/Base/Graphables/SlopeField.cs b/Base/Graphables/SlopeField.cs index a230240..71074d3 100644 --- a/Base/Graphables/SlopeField.cs +++ b/Base/Graphables/SlopeField.cs @@ -1,4 +1,6 @@ using Graphing.Forms; +using Graphing.Parts; +using static System.Windows.Forms.LinkLabel; namespace Graphing.Graphables; @@ -9,6 +11,8 @@ public class SlopeField : Graphable private readonly SlopeFieldsDelegate equ; private readonly double detail; + private readonly List<(Float2, GraphLine)> cache; + public SlopeField(int detail, SlopeFieldsDelegate equ) { slopeFieldNum++; @@ -16,25 +20,26 @@ public class SlopeField : Graphable this.equ = equ; this.detail = detail; + cache = []; } - public override IEnumerable GetItemsToRender(in GraphForm graph) + public override IEnumerable GetItemsToRender(in GraphForm graph) { - List lines = []; + double epsilon = 1 / (detail * 2); + List lines = []; 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) { - double slope = equ(x, y); - lines.Add(MakeSlopeLine(new Float2(x, y), slope)); + lines.Add(GetFromCache(epsilon, x, y)); } } return lines; } - private Line2d MakeSlopeLine(Float2 position, double slope) + private GraphLine MakeSlopeLine(Float2 position, double slope) { 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)); } + 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); diff --git a/Base/Graphables/TangentLine.cs b/Base/Graphables/TangentLine.cs new file mode 100644 index 0000000..43e3739 --- /dev/null +++ b/Base/Graphables/TangentLine.cs @@ -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 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; +} diff --git a/Base/IGraphPart.cs b/Base/IGraphPart.cs new file mode 100644 index 0000000..0e3c592 --- /dev/null +++ b/Base/IGraphPart.cs @@ -0,0 +1,8 @@ +using Graphing.Forms; + +namespace Graphing; + +public interface IGraphPart +{ + public void Render(in GraphForm form, in Graphics g, in Brush brush); +} diff --git a/Base/Line2d.cs b/Base/Line2d.cs deleted file mode 100644 index 8103f58..0000000 --- a/Base/Line2d.cs +++ /dev/null @@ -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; - } -} diff --git a/Base/Parts/GraphCircle.cs b/Base/Parts/GraphCircle.cs new file mode 100644 index 0000000..a5154c6 --- /dev/null +++ b/Base/Parts/GraphCircle.cs @@ -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))); + } +} diff --git a/Base/Parts/GraphLine.cs b/Base/Parts/GraphLine.cs new file mode 100644 index 0000000..a185f5c --- /dev/null +++ b/Base/Parts/GraphLine.cs @@ -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); + } +} diff --git a/Testing/Program.cs b/Testing/Program.cs index 40a670b..5aec97a 100644 --- a/Testing/Program.cs +++ b/Testing/Program.cs @@ -10,12 +10,21 @@ internal static class Program { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); - Application.SetHighDpiMode(HighDpiMode.PerMonitorV2); + Application.SetHighDpiMode(HighDpiMode.SystemAware); 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); } + + private static double PopulationGraph(double max, double k, double A, double t) + { + return max / (1 + A * Math.Exp(-k * t)); + } } From fc829d6a6bf1d4116899b30a1b859807f615c35e Mon Sep 17 00:00:00 2001 From: That_One_Nerd Date: Sat, 9 Mar 2024 16:10:06 -0500 Subject: [PATCH 3/6] 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)); - } } From 6d8787cac739f07f8d28cbcafa782d1bd8f76ac4 Mon Sep 17 00:00:00 2001 From: That_One_Nerd Date: Wed, 13 Mar 2024 09:42:03 -0400 Subject: [PATCH 4/6] Ready for 1.1. Made a graph cache viewer. --- Base/Base.csproj.user | 6 + Base/Forms/Controls/PieChart.Designer.cs | 44 +++++++ Base/Forms/Controls/PieChart.cs | 59 ++++++++++ Base/Forms/Controls/PieChart.resx | 120 +++++++++++++++++++ Base/Forms/GraphForm.cs | 38 +++---- Base/Forms/ViewCacheForm.Designer.cs | 97 ++++++++++++++++ Base/Forms/ViewCacheForm.cs | 89 +++++++++++++++ Base/Forms/ViewCacheForm.resx | 139 +++++++++++++++++++++++ Base/Graphables/ColumnTable.cs | 4 +- Base/Graphables/Equation.cs | 8 +- Base/Graphables/SlopeField.cs | 16 +-- Base/Graphables/TangentLine.cs | 10 +- Base/Range2d.cs | 27 ----- Testing/Program.cs | 16 ++- 14 files changed, 602 insertions(+), 71 deletions(-) create mode 100644 Base/Forms/Controls/PieChart.Designer.cs create mode 100644 Base/Forms/Controls/PieChart.cs create mode 100644 Base/Forms/Controls/PieChart.resx create mode 100644 Base/Forms/ViewCacheForm.Designer.cs create mode 100644 Base/Forms/ViewCacheForm.cs create mode 100644 Base/Forms/ViewCacheForm.resx delete mode 100644 Base/Range2d.cs diff --git a/Base/Base.csproj.user b/Base/Base.csproj.user index f3bd93a..32278f6 100644 --- a/Base/Base.csproj.user +++ b/Base/Base.csproj.user @@ -1,6 +1,9 @@  + + UserControl + Form @@ -10,5 +13,8 @@ Form + + Form + \ No newline at end of file diff --git a/Base/Forms/Controls/PieChart.Designer.cs b/Base/Forms/Controls/PieChart.Designer.cs new file mode 100644 index 0000000..18b52e0 --- /dev/null +++ b/Base/Forms/Controls/PieChart.Designer.cs @@ -0,0 +1,44 @@ +namespace Graphing.Forms.Controls +{ + partial class PieChart + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + SuspendLayout(); + // + // PieChart + // + AutoScaleDimensions = new SizeF(13F, 32F); + AutoScaleMode = AutoScaleMode.Font; + Name = "PieChart"; + Size = new Size(500, 500); + ResumeLayout(false); + } + + #endregion + } +} diff --git a/Base/Forms/Controls/PieChart.cs b/Base/Forms/Controls/PieChart.cs new file mode 100644 index 0000000..19d0e71 --- /dev/null +++ b/Base/Forms/Controls/PieChart.cs @@ -0,0 +1,59 @@ +using System.Drawing.Drawing2D; + +namespace Graphing.Forms.Controls; + +public partial class PieChart : UserControl +{ + public List<(Color, double)> Values { get; set; } + + public PieChart() + { + SetStyle(ControlStyles.OptimizedDoubleBuffer, true); + SetStyle(ControlStyles.AllPaintingInWmPaint, true); + SetStyle(ControlStyles.UserPaint, true); + + Values = []; + InitializeComponent(); + } + + protected override void OnPaint(PaintEventArgs e) + { + Graphics g = e.Graphics; + g.SmoothingMode = SmoothingMode.HighQuality; + int size = Math.Min(Width, Height); + Rectangle rect = new(5, 5, size - 10, size - 10); + + double sum = 0; + foreach ((Color, double v) item in Values) + sum += item.v; + + // Draw them. + double current = 0; + foreach ((Color color, double value) item in Values) + { + double start = 360 * current / sum, + end = 360 * (current + item.value) / sum; + + Brush filler = new SolidBrush(item.color); + g.FillPie(filler, rect, (float)start, (float)(end - start)); + + current += item.value; + } + + // Draw the outline. + Pen outlinePartsPen = new(Color.FromArgb(unchecked((int)0xFF_202020)), 3); + current = 0; + foreach ((Color, double value) item in Values) + { + double start = 360 * current / sum, + end = 360 * (current + item.value) / sum; + g.DrawPie(outlinePartsPen, rect, (float)start, (float)(end - start)); + + current += item.value; + } + + // Outline + Pen outlinePen = new(Color.FromArgb(unchecked((int)0xFF_202020)), 5); + g.DrawEllipse(outlinePen, rect); + } +} diff --git a/Base/Forms/Controls/PieChart.resx b/Base/Forms/Controls/PieChart.resx new file mode 100644 index 0000000..af32865 --- /dev/null +++ b/Base/Forms/Controls/PieChart.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Base/Forms/GraphForm.cs b/Base/Forms/GraphForm.cs index 3a2b587..5991244 100644 --- a/Base/Forms/GraphForm.cs +++ b/Base/Forms/GraphForm.cs @@ -7,6 +7,10 @@ namespace Graphing.Forms; public partial class GraphForm : Form { + public static readonly Color MainAxisColor = Color.Black; + public static readonly Color SemiAxisColor = Color.FromArgb(unchecked((int)0xFF_999999)); + public static readonly Color QuarterAxisColor = Color.FromArgb(unchecked((int)0xFF_E0E0E0)); + public Float2 ScreenCenter { get; private set; } public Float2 Dpi { get; private set; } @@ -97,7 +101,7 @@ public partial class GraphForm : Form double axisScale = Math.Pow(2, Math.Round(Math.Log2(ZoomLevel))); // Draw horizontal/vertical quarter-axis. - Brush quarterBrush = new SolidBrush(Color.FromArgb(unchecked((int)0xFF_E0E0E0))); + Brush quarterBrush = new SolidBrush(QuarterAxisColor); Pen quarterPen = new(quarterBrush, 2); for (double x = Math.Ceiling(MinVisibleGraph.x * 4 / axisScale) * axisScale / 4; x <= Math.Floor(MaxVisibleGraph.x * 4 / axisScale) * axisScale / 4; x += axisScale / 4) @@ -114,7 +118,7 @@ public partial class GraphForm : Form } // Draw horizontal/vertical semi-axis. - Brush semiBrush = new SolidBrush(Color.FromArgb(unchecked((int)0xFF_999999))); + Brush semiBrush = new SolidBrush(SemiAxisColor); Pen semiPen = new(semiBrush, 2); for (double x = Math.Ceiling(MinVisibleGraph.x / axisScale) * axisScale; x <= Math.Floor(MaxVisibleGraph.x / axisScale) * axisScale; x += axisScale) @@ -130,7 +134,7 @@ public partial class GraphForm : Form g.DrawLine(semiPen, startPos, endPos); } - Brush mainLineBrush = new SolidBrush(Color.Black); + Brush mainLineBrush = new SolidBrush(MainAxisColor); Pen mainLinePen = new(mainLineBrush, 3); // Draw the main axis (on top of the semi axis). @@ -251,14 +255,14 @@ public partial class GraphForm : Form colorItem.Click += (o, e) => GraphColorPickerButton_Click(able); MenuColors.DropDownItems.Add(colorItem); - if (able is Equation) + if (able is Equation equ) { ToolStripMenuItem derivativeItem = new() { ForeColor = able.Color, Text = able.Name }; - derivativeItem.Click += (o, e) => EquationComputeDerivative_Click((able as Equation)!); + derivativeItem.Click += (o, e) => EquationComputeDerivative_Click(equ); MenuEquationsDerivative.DropDownItems.Add(derivativeItem); ToolStripMenuItem integralItem = new() @@ -266,7 +270,7 @@ public partial class GraphForm : Form ForeColor = able.Color, Text = able.Name }; - integralItem.Click += (o, e) => EquationComputeIntegral_Click((able as Equation)!); + integralItem.Click += (o, e) => EquationComputeIntegral_Click(equ); MenuEquationsIntegral.DropDownItems.Add(integralItem); } } @@ -361,23 +365,13 @@ public partial class GraphForm : Form 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) + ViewCacheForm cacheForm = new(this) { - long size = able.GetCacheBytes(); - message.AppendLine($"{able.Name}: {size.FormatAsBytes()}"); - total += size; - } + StartPosition = FormStartPosition.Manual + }; - message.AppendLine($"\nTotal: {total.FormatAsBytes()}\n\nErase cache?"); - - DialogResult result = MessageBox.Show(message.ToString(), "Graph Caches", MessageBoxButtons.YesNo, MessageBoxIcon.Information); - if (result == DialogResult.Yes) - { - foreach (Graphable able in ables) able.EraseCache(); - } + cacheForm.Location = new Point(Location.X + ClientRectangle.Width + 10, + Location.Y + (ClientRectangle.Height - cacheForm.ClientRectangle.Height) / 2); + cacheForm.Show(); } } diff --git a/Base/Forms/ViewCacheForm.Designer.cs b/Base/Forms/ViewCacheForm.Designer.cs new file mode 100644 index 0000000..c847e6b --- /dev/null +++ b/Base/Forms/ViewCacheForm.Designer.cs @@ -0,0 +1,97 @@ +namespace Graphing.Forms +{ + partial class ViewCacheForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ViewCacheForm)); + CachePie = new Controls.PieChart(); + TotalCacheText = new Label(); + EraseAllCacheButton = new Button(); + SpecificCachePanel = new Panel(); + SuspendLayout(); + // + // CachePie + // + CachePie.Location = new Point(50, 50); + CachePie.Name = "CachePie"; + CachePie.Size = new Size(450, 450); + CachePie.TabIndex = 0; + // + // TotalCacheText + // + TotalCacheText.Font = new Font("Segoe UI Semibold", 10.125F, FontStyle.Bold, GraphicsUnit.Point, 0); + TotalCacheText.Location = new Point(62, 540); + TotalCacheText.Name = "TotalCacheText"; + TotalCacheText.Size = new Size(425, 45); + TotalCacheText.TabIndex = 1; + TotalCacheText.Text = "Total Cache: Something"; + TotalCacheText.TextAlign = ContentAlignment.TopCenter; + // + // EraseAllCacheButton + // + EraseAllCacheButton.Location = new Point(200, 580); + EraseAllCacheButton.Name = "EraseAllCacheButton"; + EraseAllCacheButton.Size = new Size(150, 46); + EraseAllCacheButton.TabIndex = 2; + EraseAllCacheButton.Text = "Erase All"; + EraseAllCacheButton.UseVisualStyleBackColor = true; + EraseAllCacheButton.Click += EraseAllCacheButton_Click; + // + // SpecificCachePanel + // + SpecificCachePanel.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right; + SpecificCachePanel.AutoScroll = true; + SpecificCachePanel.Location = new Point(520, 12); + SpecificCachePanel.Name = "SpecificCachePanel"; + SpecificCachePanel.Size = new Size(542, 657); + SpecificCachePanel.TabIndex = 3; + // + // ViewCacheForm + // + AutoScaleDimensions = new SizeF(13F, 32F); + AutoScaleMode = AutoScaleMode.Font; + ClientSize = new Size(1074, 679); + Controls.Add(SpecificCachePanel); + Controls.Add(EraseAllCacheButton); + Controls.Add(TotalCacheText); + Controls.Add(CachePie); + FormBorderStyle = FormBorderStyle.SizableToolWindow; + MinimumSize = new Size(885, 750); + Name = "ViewCacheForm"; + Text = "Graph Caches"; + ResumeLayout(false); + } + + #endregion + + private Controls.PieChart CachePie; + private Label TotalCacheText; + private Button EraseAllCacheButton; + private Panel SpecificCachePanel; + } +} \ No newline at end of file diff --git a/Base/Forms/ViewCacheForm.cs b/Base/Forms/ViewCacheForm.cs new file mode 100644 index 0000000..55cd1e4 --- /dev/null +++ b/Base/Forms/ViewCacheForm.cs @@ -0,0 +1,89 @@ +using Graphing.Extensions; + +namespace Graphing.Forms; + +public partial class ViewCacheForm : Form +{ + private readonly GraphForm refForm; + + private readonly List