diff --git a/Base/Forms/Controls/PieChart.cs b/Base/Forms/Controls/PieChart.cs index ed5b92f..2cb05f0 100644 --- a/Base/Forms/Controls/PieChart.cs +++ b/Base/Forms/Controls/PieChart.cs @@ -46,16 +46,21 @@ public partial class PieChart : UserControl current += item.value; } - // Draw the outline. - Pen outlinePartsPen = new(Color.FromArgb(unchecked((int)0xFF_202020)), DpiFloat * 3 / 192); - current = 0; - foreach ((Color, double value) item in Values) + // Draw the outline of each slice. + // Only done if there is more than one slice. + if (Values.Count > 1) { - double start = 360 * current / sum, - end = 360 * (current + item.value) / sum; - g.DrawPie(outlinePartsPen, rect, (float)start, (float)(end - start)); + Pen outlinePartsPen = new(Color.FromArgb(unchecked((int)0xFF_202020)), DpiFloat * 3 / 192); + current = 0; + foreach ((Color, double value) item in Values) + { + double start = 360 * current / sum, + end = 360 * (current + item.value) / sum; + if (item.value > 0) + g.DrawPie(outlinePartsPen, rect, (float)start, (float)(end - start)); - current += item.value; + current += item.value; + } } // Outline diff --git a/Base/Forms/GraphForm.cs b/Base/Forms/GraphForm.cs index 8523230..44fe52c 100644 --- a/Base/Forms/GraphForm.cs +++ b/Base/Forms/GraphForm.cs @@ -466,12 +466,16 @@ public partial class GraphForm : Form Float2 min = MinVisibleGraph, max = MaxVisibleGraph; Float2 add = new(max.x - min.x, max.y - min.y); add.x *= 0.75; // Expansion - add.y *= 0.75; // + add.y *= 0.75; // Screen + 75% Float2 xRange = new(min.x - add.x, max.x + add.x), yRange = new(min.y - add.y, max.y + add.y); - foreach (Graphable able in Graphables) able.Preload(xRange, yRange); + double step = ScreenSpaceToGraphSpace(new Int2(1, 0)).x + - ScreenSpaceToGraphSpace(new Int2(0, 0)).x; + step /= 10; + + foreach (Graphable able in Graphables) able.Preload(xRange, yRange, step); Invalidate(false); } } diff --git a/Base/Graphable.cs b/Base/Graphable.cs index 6db3278..0608f58 100644 --- a/Base/Graphable.cs +++ b/Base/Graphable.cs @@ -32,7 +32,7 @@ public abstract class Graphable public abstract void EraseCache(); public abstract long GetCacheBytes(); - public abstract void Preload(Float2 xRange, Float2 yRange); + public abstract void Preload(Float2 xRange, Float2 yRange, double step); public abstract bool ShouldSelectGraphable(in GraphForm graph, Float2 graphMousePos, double factor); public abstract Float2 GetSelectedPoint(in GraphForm graph, Float2 graphMousePos); diff --git a/Base/Graphables/ColumnTable.cs b/Base/Graphables/ColumnTable.cs index 567d07a..673cd40 100644 --- a/Base/Graphables/ColumnTable.cs +++ b/Base/Graphables/ColumnTable.cs @@ -52,5 +52,6 @@ public class ColumnTable : Graphable public override bool ShouldSelectGraphable(in GraphForm graph, Float2 graphMousePos, double factor) => false; public override Float2 GetSelectedPoint(in GraphForm graph, Float2 graphMousePos) => default; - public override void Preload(Float2 xRange, Float2 yRange) { } + // Nothing to preload, everything is already cached. + public override void Preload(Float2 xRange, Float2 yRange, double step) { } } diff --git a/Base/Graphables/Equation.cs b/Base/Graphables/Equation.cs index 9cd34be..4828be2 100644 --- a/Base/Graphables/Equation.cs +++ b/Base/Graphables/Equation.cs @@ -116,9 +116,9 @@ public class Equation : Graphable return new(graphMousePos.x, GetFromCache(graphMousePos.x, 0.001)); } - public override void Preload(Float2 xRange, Float2 yRange) + public override void Preload(Float2 xRange, Float2 yRange, double step) { - for (double x = xRange.x; x <= xRange.y; x += 1e-3) GetFromCache(x, 1e-4); + for (double x = xRange.x; x <= xRange.y; x += step) GetFromCache(x, step); } } diff --git a/Base/Graphables/SlopeField.cs b/Base/Graphables/SlopeField.cs index a26200f..b96d64e 100644 --- a/Base/Graphables/SlopeField.cs +++ b/Base/Graphables/SlopeField.cs @@ -78,7 +78,16 @@ public class SlopeField : Graphable public override bool ShouldSelectGraphable(in GraphForm graph, Float2 graphMousePos, double factor) => false; public override Float2 GetSelectedPoint(in GraphForm graph, Float2 graphMousePos) => default; - public override void Preload(Float2 xRange, Float2 yRange) { } + public override void Preload(Float2 xRange, Float2 yRange, double step) + { + for (double x = Math.Ceiling(xRange.x - 1); x < xRange.y + 1; x += 1.0 / detail) + { + for (double y = Math.Ceiling(yRange.x - 1); y < yRange.y + 1; y += 1.0 / detail) + { + GetFromCache(step, x, y); + } + } + } } public delegate double SlopeFieldsDelegate(double x, double y); diff --git a/Base/Graphables/TangentLine.cs b/Base/Graphables/TangentLine.cs index aa4596f..d66736e 100644 --- a/Base/Graphables/TangentLine.cs +++ b/Base/Graphables/TangentLine.cs @@ -5,17 +5,32 @@ namespace Graphing.Graphables; public class TangentLine : Graphable { - public double Position { get; set; } + public double Position + { + get => _position; + set + { + currentSlope = DerivativeAtPoint(value); + _position = value; + } + } + private double _position; protected readonly Equation parent; protected readonly EquationDelegate parentEqu; protected readonly double length; + protected double currentSlope; + + // No binary search for this, I want it to be exact. + protected Dictionary slopeCache; + public TangentLine(double length, double position, Equation parent) { Name = $"Tangent Line of {parent.Name}"; + slopeCache = []; parentEqu = parent.GetDelegate(); Position = position; this.length = length; @@ -25,7 +40,7 @@ public class TangentLine : Graphable public override IEnumerable GetItemsToRender(in GraphForm graph) { Float2 point = new(Position, parentEqu(Position)); - return [MakeSlopeLine(point, DerivativeAtPoint(Position)), + return [MakeSlopeLine(point, currentSlope), new GraphUiCircle(point, 8)]; } protected GraphLine MakeSlopeLine(Float2 position, double slope) @@ -40,17 +55,29 @@ public class TangentLine : Graphable } protected double DerivativeAtPoint(double x) { + // If value is already computed, return it. + if (slopeCache.TryGetValue(x, out double y)) return y; + const double step = 1e-3; - return (parentEqu(x + step) - parentEqu(x)) / step; + double result = (parentEqu(x + step) - parentEqu(x)) / step; + slopeCache.Add(x, result); + return result; } public override Graphable DeepCopy() => new TangentLine(length, Position, parent); - public override void EraseCache() { } - public override long GetCacheBytes() => 0; + public override void EraseCache() => slopeCache.Clear(); + public override long GetCacheBytes() => slopeCache.Count * 16; public override bool ShouldSelectGraphable(in GraphForm graph, Float2 graphMousePos, double factor) => false; public override Float2 GetSelectedPoint(in GraphForm graph, Float2 graphMousePos) => default; - public override void Preload(Float2 xRange, Float2 yRange) { } + public override void Preload(Float2 xRange, Float2 yRange, double step) + { + // Despite the tangent line barely using any data, when preloaded it + // will always take as much memory as an equation. Seems like a bit much, + // but may be used when the tangent line is moved. Not sure there's much + // that can be changed. + for (double x = xRange.x; x <= xRange.y; x += step) DerivativeAtPoint(x); + } } diff --git a/Testing/Program.cs b/Testing/Program.cs index 993e0d1..5be5d87 100644 --- a/Testing/Program.cs +++ b/Testing/Program.cs @@ -14,10 +14,10 @@ internal static class Program GraphForm graph = new("One Of The Graphing Calculators Of All Time"); - Equation equ1 = new(x => -x * x + 2); - Equation equ2 = new(x => x); - Equation equ3 = new(x => -Math.Sqrt(x)); - graph.Graph(equ1, equ2, equ3); + Equation possibleA = new(x => x * x * x); + SlopeField sf = new(2, (x, y) => 3 * x * x); + TangentLine tl = new(5, 2, possibleA); + graph.Graph(possibleA, sf, tl); // You can also now view and reset caches in the UI by going to // Misc > View Caches.