From 9b4905233cc70e9e4df84778a3ae3b14bcb34e9d Mon Sep 17 00:00:00 2001 From: That_One_Nerd Date: Tue, 19 Mar 2024 12:34:31 -0400 Subject: [PATCH] Higher-order integrals are now supported. --- Base/Abstract/IDerivable.cs | 2 +- Base/Abstract/IIntegrable.cs | 2 +- Base/Forms/GraphForm.cs | 1 - Base/Graphables/Equation.cs | 6 +- Base/Graphables/IntegralEquation.cs | 161 +++++++++++++++++++++------- Testing/Program.cs | 6 +- 6 files changed, 132 insertions(+), 46 deletions(-) diff --git a/Base/Abstract/IDerivable.cs b/Base/Abstract/IDerivable.cs index 1d0a7e9..56571c4 100644 --- a/Base/Abstract/IDerivable.cs +++ b/Base/Abstract/IDerivable.cs @@ -4,5 +4,5 @@ namespace Graphing.Abstract; public interface IDerivable { - public Equation Derive(); + public Graphable Derive(); } diff --git a/Base/Abstract/IIntegrable.cs b/Base/Abstract/IIntegrable.cs index 1a942a9..3e3dd52 100644 --- a/Base/Abstract/IIntegrable.cs +++ b/Base/Abstract/IIntegrable.cs @@ -4,5 +4,5 @@ namespace Graphing.Abstract; public interface IIntegrable { - public IntegralEquation Integrate(); + public Graphable Integrate(); } diff --git a/Base/Forms/GraphForm.cs b/Base/Forms/GraphForm.cs index 839fb3e..152d852 100644 --- a/Base/Forms/GraphForm.cs +++ b/Base/Forms/GraphForm.cs @@ -1,5 +1,4 @@ using Graphing.Abstract; -using Graphing.Graphables; using Graphing.Parts; using System; using System.Collections.Generic; diff --git a/Base/Graphables/Equation.cs b/Base/Graphables/Equation.cs index a32b961..c3dfa4e 100644 --- a/Base/Graphables/Equation.cs +++ b/Base/Graphables/Equation.cs @@ -49,12 +49,12 @@ public class Equation : Graphable, IIntegrable, IDerivable return lines; } - public Equation Derive() => new(x => + public Graphable Derive() => new Equation(x => { const double step = 1e-3; return (equ(x + step) - equ(x)) / step; }); - public IntegralEquation Integrate() => new(this); + public Graphable Integrate() => new IntegralEquation(this); public EquationDelegate GetDelegate() => equ; @@ -71,8 +71,6 @@ public class Equation : Graphable, IIntegrable, IDerivable } } - // Pretty sure this works. Certainly works pretty well with "hard-to-compute" - // equations. protected (double dist, double y, int index) NearestCachedPoint(double x) { if (cache.Count == 0) return (double.PositiveInfinity, double.NaN, -1); diff --git a/Base/Graphables/IntegralEquation.cs b/Base/Graphables/IntegralEquation.cs index 0441800..5abd5a4 100644 --- a/Base/Graphables/IntegralEquation.cs +++ b/Base/Graphables/IntegralEquation.cs @@ -3,13 +3,18 @@ using Graphing.Forms; using Graphing.Parts; using System; using System.Collections.Generic; +using System.ComponentModel.Design; namespace Graphing.Graphables; public class IntegralEquation : Graphable, IIntegrable, IDerivable { - protected readonly Equation baseEqu; - protected readonly EquationDelegate baseEquDel; + protected readonly Equation? baseEqu; + protected readonly EquationDelegate? baseEquDel; + + protected readonly IntegralEquation? altBaseEqu; + + protected readonly bool usingAlt; public IntegralEquation(Equation baseEquation) { @@ -22,9 +27,27 @@ public class IntegralEquation : Graphable, IIntegrable, IDerivable baseEqu = baseEquation; baseEquDel = baseEquation.GetDelegate(); + + altBaseEqu = null; + usingAlt = false; + } + public IntegralEquation(IntegralEquation baseEquation) + { + string oldName = baseEquation.Name, newName; + if (oldName.StartsWith("Integral of ")) newName = "Second Integral of " + oldName[12..]; + else if (oldName.StartsWith("Second Integral of ")) newName = "Third Integral of " + oldName[19..]; + else newName = "Integral of " + oldName; + + Name = newName; + + baseEqu = null; + baseEquDel = null; + + altBaseEqu = baseEquation; + usingAlt = true; } - public override Graphable DeepCopy() => new IntegralEquation(baseEqu); + public override Graphable DeepCopy() => new IntegralEquation(this); public override IEnumerable GetItemsToRender(in GraphForm graph) { @@ -35,45 +58,42 @@ public class IntegralEquation : Graphable, IIntegrable, IDerivable List lines = []; Int2 originLocation = graph.GraphSpaceToScreenSpace(new Float2(0, 0)); - if (originLocation.x < 0) { // Origin is off the left side of the screen. // Get to the left side from the origin. - double previousY = 0; double start = graph.MinVisibleGraph.x, end = graph.MaxVisibleGraph.x; - for (double x = 0; x <= start; x += epsilon) previousY += baseEquDel(x) * epsilon; + SetInternalStepper(start, epsilon, null); // Now we can start. - double previousX = start; - + double previousX = stepX; + double previousY = stepY; for (double x = start; x <= end; x += epsilon) { - double currentX = x, currentY = previousY + baseEquDel(x) * epsilon; - lines.Add(new GraphLine(new Float2(previousX, previousY), new Float2(currentX, currentY))); - - previousX = currentX; - previousY = currentY; + MoveInternalStepper(epsilon); + lines.Add(new GraphLine(new Float2(previousX, previousY), + new Float2(stepX, stepY))); + previousX = stepX; + previousY = stepY; } } else if (originLocation.x > graph.ClientRectangle.Width) { // Origin is off the right side of the screen. // Get to the right side of the origin. - double previousY = 0; double start = graph.MaxVisibleGraph.x, end = graph.MinVisibleGraph.x; - for (double x = 0; x >= start; x -= epsilon) previousY -= baseEquDel(x) * epsilon; + SetInternalStepper(start, epsilon, null); // Now we can start. - double previousX = start; - + double previousX = stepX; + double previousY = stepY; for (double x = start; x >= end; x -= epsilon) { - double currentX = x, currentY = previousY - baseEquDel(x) * epsilon; - lines.Add(new GraphLine(new Float2(previousX, previousY), new Float2(currentX, currentY))); - - previousX = currentX; - previousY = currentY; + MoveInternalStepper(-epsilon); + lines.Add(new GraphLine(new Float2(previousX, previousY), + new Float2(stepX, stepY))); + previousX = stepX; + previousY = stepY; } } else @@ -83,45 +103,93 @@ public class IntegralEquation : Graphable, IIntegrable, IDerivable // Start with right. double start = 0, end = graph.MaxVisibleGraph.x; - double previousX = start; - double previousY = 0; + SetInternalStepper(start, epsilon, null); + double previousX = stepX; + double previousY = stepY; for (double x = start; x <= end; x += epsilon) { - double currentX = x, currentY = previousY + baseEquDel(x) * epsilon; - lines.Add(new GraphLine(new Float2(previousX, previousY), new Float2(currentX, currentY))); - - previousX = currentX; - previousY = currentY; + MoveInternalStepper(epsilon); + lines.Add(new GraphLine(new Float2(previousX, previousY), + new Float2(stepX, stepY))); + previousX = stepX; + previousY = stepY; } // Now do left. start = 0; end = graph.MinVisibleGraph.x; - previousX = start; - previousY = 0; + SetInternalStepper(start, epsilon, null); + + previousX = stepX; + previousY = stepY; for (double x = start; x >= end; x -= epsilon) { - double currentX = x, currentY = previousY - baseEquDel(x) * epsilon; - lines.Add(new GraphLine(new Float2(previousX, previousY), new Float2(currentX, currentY))); - - previousX = currentX; - previousY = currentY; + MoveInternalStepper(-epsilon); + lines.Add(new GraphLine(new Float2(previousX, previousY), + new Float2(stepX, stepY))); + previousX = stepX; + previousY = stepY; } } return lines; } + private double stepX = 0; + private double stepY = 0; + private void SetInternalStepper(double x, double dX, Action? stepCallback) + { + stepX = 0; + stepY = 0; + if (usingAlt) altBaseEqu!.SetInternalStepper(0, dX, null); + + if (x > 0) + { + while (stepX < x) + { + MoveInternalStepper(dX); + stepCallback?.Invoke(stepX, stepY); + } + } + else if (x < 0) + { + while (x < stepX) + { + MoveInternalStepper(-dX); + stepCallback?.Invoke(stepX, stepY); + } + } + } + private void MoveInternalStepper(double dX) + { + stepX += dX; + if (usingAlt) + { + altBaseEqu!.MoveInternalStepper(dX); + stepY += altBaseEqu!.stepY * dX; + } + else + { + stepY += baseEquDel!(stepX) * dX; + } + } + + // Try to avoid using this, as it converts the integral into a + // far less efficient format (uses the `IntegralAtPoint` method). public Equation AsEquation() => new(IntegralAtPoint) { Name = Name, Color = Color }; - public Equation Derive() => (Equation)baseEqu.DeepCopy(); - public IntegralEquation Integrate() => AsEquation().Integrate(); + public Graphable Derive() + { + if (usingAlt) return altBaseEqu!.DeepCopy(); + else return (Equation)baseEqu!.DeepCopy(); + } + public Graphable Integrate() => new IntegralEquation(this); // Standard integral method. // Inefficient for successive calls. @@ -138,4 +206,21 @@ public class IntegralEquation : Graphable, IIntegrable, IDerivable return sum; } + + public override bool ShouldSelectGraphable(in GraphForm graph, Float2 graphMousePos, double factor) + { + Int2 screenMousePos = graph.GraphSpaceToScreenSpace(graphMousePos); + + Int2 screenPos = graph.GraphSpaceToScreenSpace(new Float2(graphMousePos.x, + IntegralAtPoint(graphMousePos.x))); + + double allowedDist = factor * graph.DpiFloat * 80 / 192; + + Int2 dist = new(screenPos.x - screenMousePos.x, + screenPos.y - screenMousePos.y); + double totalDist = Math.Sqrt(dist.x * dist.x + dist.y * dist.y); + return totalDist <= allowedDist; + } + public override Float2 GetSelectedPoint(in GraphForm graph, Float2 graphMousePos) => + new(graphMousePos.x, IntegralAtPoint(graphMousePos.x)); } diff --git a/Testing/Program.cs b/Testing/Program.cs index 9638860..e70142d 100644 --- a/Testing/Program.cs +++ b/Testing/Program.cs @@ -17,7 +17,9 @@ internal static class Program GraphForm graph = new("One Of The Graphing Calculators Of All Time"); Equation equ = new(Math.Sin); - graph.Graph(equ); + SlopeField sf = new(2, (x, y) => Math.Cos(x)); + TangentLine tl = new(2, 2, equ); + graph.Graph(equ, sf, tl); // You can preload graphs in by going Misc > Preload Cache. // Keep in mind this uses more memory than usual and can take @@ -26,6 +28,8 @@ internal static class Program // Integrating equations is now much smoother and less intensive. // Try it out! + // You can click and drag on an equation to select specific points. + Application.Run(graph); } }