diff --git a/Base/Abstract/IConvertColumnTable.cs b/Base/Abstract/IConvertColumnTable.cs new file mode 100644 index 0000000..4b87aaf --- /dev/null +++ b/Base/Abstract/IConvertColumnTable.cs @@ -0,0 +1,10 @@ +using Graphing.Graphables; + +namespace Graphing.Abstract; + +public interface IConvertColumnTable +{ + public bool UngraphWhenConvertedToColumnTable { get; } + + public ColumnTable ToColumnTable(double start, double end, int detail); +} diff --git a/Base/Forms/GraphForm.cs b/Base/Forms/GraphForm.cs index c4bd78d..8d20d04 100644 --- a/Base/Forms/GraphForm.cs +++ b/Base/Forms/GraphForm.cs @@ -3,7 +3,6 @@ using Graphing.Helpers; using Graphing.Parts; using System; using System.Collections.Generic; -using System.Diagnostics; using System.Drawing; using System.Drawing.Drawing2D; using System.Linq; @@ -11,7 +10,6 @@ using System.Net.Http; using System.Reflection; using System.Text.Json; using System.Text.Json.Nodes; -using System.Threading.Tasks; using System.Windows.Forms; namespace Graphing.Forms; @@ -278,6 +276,13 @@ public partial class GraphForm : Form Invalidate(false); } + public bool IsGraphPointVisible(Float2 point) + { + Int2 pixelPos = GraphSpaceToScreenSpace(point); + return pixelPos.x >= 0 && pixelPos.x < ClientRectangle.Width && + pixelPos.y >= 0 && pixelPos.y < ClientRectangle.Height; + } + private bool mouseDrag = false; private Int2 initialMouseLocation; private Float2 initialScreenCenter; @@ -371,6 +376,8 @@ public partial class GraphForm : Form MenuConvertEquation.DropDownItems.Clear(); MenuConvertSlopeField.DropDownItems.Clear(); MenuOperationsTranslate.DropDownItems.Clear(); + // At some point, we'll have a Convert To Column Table button, + // but I'll need to make a form for the ranges when I do that. foreach (Graphable able in ables) { diff --git a/Base/Forms/TranslateForm.cs b/Base/Forms/TranslateForm.cs index fdd2240..d0ac022 100644 --- a/Base/Forms/TranslateForm.cs +++ b/Base/Forms/TranslateForm.cs @@ -100,6 +100,8 @@ public partial class TranslateForm : Form TitleLabel.Text = $"There doesn't seem to be anything you can translate for {ableRaw.Name}."; } + // TODO: Maybe replace these default limits with what's visible on screen? + // Tried it and it got a bit confusing so maybe not. minX = -10; maxX = 10; minY = -10; @@ -272,11 +274,10 @@ public partial class TranslateForm : Form private static double Lerp(double a, double b, double t) => a + t * (b - a); private static double InverseLerp(double a, double b, double c) => (c - a) / (b - a); - private void TrackX_Scroll(object? sender, EventArgs e) + private void TrackX_Scroll(object sender, EventArgs e) { UpdateFromSliderX(true); } - private void TrackY_Scroll(object sender, EventArgs e) { UpdateFromSliderY(true); diff --git a/Base/Graphables/ColumnTable.cs b/Base/Graphables/ColumnTable.cs index 55142d4..7b95593 100644 --- a/Base/Graphables/ColumnTable.cs +++ b/Base/Graphables/ColumnTable.cs @@ -23,6 +23,7 @@ public class ColumnTable : Graphable } public ColumnTable(double step, Equation equation, double min, double max) { + Color = equation.Color; Name = $"Column Table for {equation.Name}"; tableXY = []; @@ -45,7 +46,7 @@ public class ColumnTable : Graphable foreach (KeyValuePair col in tableXY) { items.Add(GraphRectangle.FromSize(new Float2(col.Key, col.Value / 2), - new Float2(width, col.Value))); + new Float2(width, col.Value), 0.625)); } return items; diff --git a/Base/Graphables/Equation.cs b/Base/Graphables/Equation.cs index cf0d6aa..a118299 100644 --- a/Base/Graphables/Equation.cs +++ b/Base/Graphables/Equation.cs @@ -6,10 +6,12 @@ using System.Collections.Generic; namespace Graphing.Graphables; -public class Equation : Graphable, IIntegrable, IDerivable, ITranslatableXY, IConvertSlopeField +public class Equation : Graphable, IIntegrable, IDerivable, ITranslatableXY, IConvertSlopeField, + IConvertColumnTable { private static int equationNum; + public bool UngraphWhenConvertedToColumnTable => false; public bool UngraphWhenConvertedToSlopeField => false; public double OffsetX { get; set; } @@ -65,7 +67,7 @@ public class Equation : Graphable, IIntegrable, IDerivable, ITranslatableXY, ICo protected double DerivativeAtPoint(double x) { const double step = 1e-3; - return (equ(x + step) - equ(x)) / step; + return (equ(x + step - OffsetX) - equ(x - OffsetX)) / step; } public Graphable Derive() => new Equation(DerivativeAtPoint); @@ -78,6 +80,8 @@ public class Equation : Graphable, IIntegrable, IDerivable, ITranslatableXY, ICo Color = Color, Name = $"Slope Field of {Name}" }; + public ColumnTable ToColumnTable(double start, double end, int detail) + => new(1.0 / detail, this, start, end); public override void EraseCache() => cache.Clear(); protected double GetFromCache(double x, double epsilon) diff --git a/Base/Graphables/IntegralEquation.cs b/Base/Graphables/IntegralEquation.cs index 2279b0d..8c49140 100644 --- a/Base/Graphables/IntegralEquation.cs +++ b/Base/Graphables/IntegralEquation.cs @@ -164,7 +164,7 @@ public class IntegralEquation : Graphable, IIntegrable, IDerivable } else { - stepY += baseEquDel!(stepX) * dX; + stepY += (baseEquDel!(stepX - baseEqu!.OffsetX) + baseEqu.OffsetY) * dX; } } diff --git a/Base/Graphables/ParametricEquation.cs b/Base/Graphables/ParametricEquation.cs index 0d92743..24f7f78 100644 --- a/Base/Graphables/ParametricEquation.cs +++ b/Base/Graphables/ParametricEquation.cs @@ -6,7 +6,7 @@ using System.Collections.Generic; namespace Graphing.Graphables; -public class ParametricEquation : Graphable, ITranslatableXY +public class ParametricEquation : Graphable, IDerivable, ITranslatableXY { private static int equationNum; @@ -17,6 +17,7 @@ public class ParametricEquation : Graphable, ITranslatableXY public double FinalT { get; set; } protected readonly ParametricDelegate equX, equY; + protected readonly List<(double t, Float2 point)> cache; public ParametricEquation(double initialT, double finalT, ParametricDelegate equX, ParametricDelegate equY) @@ -29,6 +30,7 @@ public class ParametricEquation : Graphable, ITranslatableXY this.equX = equX; this.equY = equY; + cache = []; } public override Graphable ShallowCopy() => new ParametricEquation(InitialT, FinalT, equX, equY); @@ -38,25 +40,94 @@ public class ParametricEquation : Graphable, ITranslatableXY const int step = 10; double epsilon = Math.Abs(graph.ScreenSpaceToGraphSpace(new Int2(0, 0)).x - - graph.ScreenSpaceToGraphSpace(new Int2(step / 2, 0)).x) / 5; - epsilon *= graph.DpiFloat / 192; + - graph.ScreenSpaceToGraphSpace(new Int2(step, 0)).x); List lines = []; - Float2 previousPoint = GetPointAt(InitialT); + Float2 previousPoint = GetFromCache(InitialT, epsilon); for (double t = InitialT; t <= FinalT; t += epsilon) { - Float2 currentPoint = GetPointAt(t); - lines.Add(new GraphLine(previousPoint, currentPoint)); + Float2 currentPoint = GetFromCache(t, epsilon); + if (graph.IsGraphPointVisible(currentPoint) || + graph.IsGraphPointVisible(previousPoint)) + lines.Add(new GraphLine(previousPoint, currentPoint)); previousPoint = currentPoint; } return lines; } - public Float2 GetPointAt(double t) + public Graphable Derive() => + new ParametricEquation(InitialT, FinalT, GetDerivativeAtPointX, GetDerivativeAtPointY); + + public ParametricDelegate GetXDelegate() => equX; + public ParametricDelegate GetYDelegate() => equY; + + public double GetDerivativeAtPointX(double t) { - return new(equX(t) + OffsetX, equY(t) + OffsetY); + const double step = 1e-3; + return (equX(t + step) - equX(t)) / step; + } + public double GetDerivativeAtPointY(double t) + { + const double step = 1e-3; + return (equY(t + step) - equY(t)) / step; + } + public Float2 GetDerivativeAtPoint(double t) => + new(GetDerivativeAtPointX(t), GetDerivativeAtPointY(t)); + + public Float2 GetPointAt(double t) => GetFromCache(t, 0); + + public override void EraseCache() => cache.Clear(); + protected Float2 GetFromCache(double t, double epsilon) + { + (double dist, Float2 nearest, int index) = NearestCachedPoint(t); + if (dist < epsilon) return new(nearest.x + OffsetX, nearest.y + OffsetY); + else + { + Float2 result = new(equX(t), equY(t)); + cache.Insert(index + 1, (t, result)); + return new(result.x + OffsetX, result.y + OffsetY); + } + } + public override long GetCacheBytes() => cache.Count * 24; + + protected (double dist, Float2 point, int index) NearestCachedPoint(double t) + { + if (cache.Count <= 1) return (double.PositiveInfinity, new(double.NaN, double.NaN), -1); + else if (cache.Count == 1) + { + (double resultT, Float2 resultPoint) = cache[0]; + return (Math.Abs(resultT - t), resultPoint, 0); + } + else + { + int boundA = 0, boundB = cache.Count; + do + { + int boundC = (boundA + boundB) / 2; + + (double thisT, Float2 thisPoint) = cache[boundC]; + if (thisT == t) return (0, thisPoint, boundC); + else if (thisT > t) + { + boundA = boundC; + } + else // thisT < t + { + boundB = boundC; + } + + } while (boundB - boundA > 1); + + (double resultT, Float2 resultPoint) = cache[boundA]; + return (Math.Abs(resultT - t), resultPoint, boundA); + } + } + + public override void Preload(Float2 xRange, Float2 yRange, double step) + { + for (double t = InitialT; t <= FinalT; t += step) GetFromCache(t, step); } } diff --git a/Base/Parts/GraphRectangle.cs b/Base/Parts/GraphRectangle.cs index 4881397..161d7e2 100644 --- a/Base/Parts/GraphRectangle.cs +++ b/Base/Parts/GraphRectangle.cs @@ -6,24 +6,28 @@ namespace Graphing.Parts; public record struct GraphRectangle : IGraphPart { public Float2 min, max; + public double opacity; public GraphRectangle() { min = new(); max = new(); + opacity = 1; } - public static GraphRectangle FromSize(Float2 center, Float2 size) => new() + public static GraphRectangle FromSize(Float2 center, Float2 size, double opacity = 1) => 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) + center.y + size.y / 2), + opacity = opacity, }; - public static GraphRectangle FromRange(Float2 min, Float2 max) => new() + public static GraphRectangle FromRange(Float2 min, Float2 max, double opacity = 1) => new() { min = min, - max = max + max = max, + opacity = opacity, }; public void Render(in GraphForm form, in Graphics g, in Pen pen) @@ -41,6 +45,7 @@ public record struct GraphRectangle : IGraphPart start.y - end.y); if (size.x == 0 || size.y == 0) return; + pen.Color = Color.FromArgb((int)(opacity * 255), pen.Color); g.FillRectangle(pen.Brush, new Rectangle(start.x, end.y, size.x, size.y)); } } diff --git a/Testing/Program.cs b/Testing/Program.cs index 333cacb..ef0bc3f 100644 --- a/Testing/Program.cs +++ b/Testing/Program.cs @@ -19,8 +19,8 @@ internal static class Program Equation equA = new(Math.Sin), equB = new(Math.Cos); EquationDifference diff = new(2, equA, equB); - ParametricEquation equC = new(0, 4, t => t * t - 1, t => Math.Sqrt(t) + t + 1); - graph.Graph(equA, equB, diff, equC); + ParametricEquation equC = new(0, 20, t => 0.0375 * t * Math.Cos(t), t => 0.0625 * t * Math.Sin(t) + 3); + graph.Graph(equA, equB, diff, equC, equA.ToColumnTable(-1, 1, 2)); Application.Run(graph); }