Some stuff. Satisfied for now with the slope-field generation and the parametric equations.

This commit is contained in:
That_One_Nerd 2024-04-03 12:27:53 -04:00
parent 3b6ebc7b99
commit 470a70a97a
9 changed files with 121 additions and 22 deletions

View File

@ -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);
}

View File

@ -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)
{

View File

@ -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);

View File

@ -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<double, double> 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;

View File

@ -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)

View File

@ -164,7 +164,7 @@ public class IntegralEquation : Graphable, IIntegrable, IDerivable
}
else
{
stepY += baseEquDel!(stepX) * dX;
stepY += (baseEquDel!(stepX - baseEqu!.OffsetX) + baseEqu.OffsetY) * dX;
}
}

View File

@ -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,15 +40,16 @@ 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<IGraphPart> lines = [];
Float2 previousPoint = GetPointAt(InitialT);
Float2 previousPoint = GetFromCache(InitialT, epsilon);
for (double t = InitialT; t <= FinalT; t += epsilon)
{
Float2 currentPoint = GetPointAt(t);
Float2 currentPoint = GetFromCache(t, epsilon);
if (graph.IsGraphPointVisible(currentPoint) ||
graph.IsGraphPointVisible(previousPoint))
lines.Add(new GraphLine(previousPoint, currentPoint));
previousPoint = currentPoint;
}
@ -54,9 +57,77 @@ public class ParametricEquation : Graphable, ITranslatableXY
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);
}
}

View File

@ -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));
}
}

View File

@ -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);
}