From cd05a6829c1a7124959dc40f0862bd62a5e39eb0 Mon Sep 17 00:00:00 2001 From: That_One_Nerd Date: Thu, 14 Mar 2024 12:42:13 -0400 Subject: [PATCH 01/13] Dynamic line thickness. --- Base/Forms/Controls/PieChart.cs | 10 ++++++++-- Base/Forms/GraphColorPickerForm.cs | 2 +- Base/Forms/GraphForm.cs | 14 ++++++++++---- Base/Forms/ViewCacheForm.cs | 12 ++++++++---- Base/IGraphPart.cs | 2 +- Base/Parts/GraphLine.cs | 4 +--- Base/Parts/GraphRectangle.cs | 4 ++-- Base/Parts/GraphUiCircle.cs | 6 +++--- Testing/Program.cs | 20 +++----------------- 9 files changed, 37 insertions(+), 37 deletions(-) diff --git a/Base/Forms/Controls/PieChart.cs b/Base/Forms/Controls/PieChart.cs index 19d0e71..ed5b92f 100644 --- a/Base/Forms/Controls/PieChart.cs +++ b/Base/Forms/Controls/PieChart.cs @@ -6,12 +6,18 @@ public partial class PieChart : UserControl { public List<(Color, double)> Values { get; set; } + public float DpiFloat { get; private set; } + public PieChart() { SetStyle(ControlStyles.OptimizedDoubleBuffer, true); SetStyle(ControlStyles.AllPaintingInWmPaint, true); SetStyle(ControlStyles.UserPaint, true); + Graphics tempG = CreateGraphics(); + DpiFloat = (tempG.DpiX + tempG.DpiY) / 2; + tempG.Dispose(); + Values = []; InitializeComponent(); } @@ -41,7 +47,7 @@ public partial class PieChart : UserControl } // Draw the outline. - Pen outlinePartsPen = new(Color.FromArgb(unchecked((int)0xFF_202020)), 3); + Pen outlinePartsPen = new(Color.FromArgb(unchecked((int)0xFF_202020)), DpiFloat * 3 / 192); current = 0; foreach ((Color, double value) item in Values) { @@ -53,7 +59,7 @@ public partial class PieChart : UserControl } // Outline - Pen outlinePen = new(Color.FromArgb(unchecked((int)0xFF_202020)), 5); + Pen outlinePen = new(Color.FromArgb(unchecked((int)0xFF_202020)), DpiFloat * 5 / 192); g.DrawEllipse(outlinePen, rect); } } diff --git a/Base/Forms/GraphColorPickerForm.cs b/Base/Forms/GraphColorPickerForm.cs index b1ec674..98147cb 100644 --- a/Base/Forms/GraphColorPickerForm.cs +++ b/Base/Forms/GraphColorPickerForm.cs @@ -37,7 +37,7 @@ public partial class GraphColorPickerForm : Form MessageLabel.Text = $"Pick a color for {able.Name}."; // Add preset buttons. - const int size = 48; + int size = (int)(graph.DpiFloat * 48 / 192); int position = 0; foreach (uint cId in Graphable.DefaultColors) { diff --git a/Base/Forms/GraphForm.cs b/Base/Forms/GraphForm.cs index 5991244..37ef874 100644 --- a/Base/Forms/GraphForm.cs +++ b/Base/Forms/GraphForm.cs @@ -14,6 +14,8 @@ public partial class GraphForm : Form public Float2 ScreenCenter { get; private set; } public Float2 Dpi { get; private set; } + public float DpiFloat { get; private set; } + public double ZoomLevel { get => _zoomLevel; @@ -57,6 +59,9 @@ public partial class GraphForm : Form Graphics tempG = CreateGraphics(); Dpi = new(tempG.DpiX, tempG.DpiY); tempG.Dispose(); + + DpiFloat = (float)((Dpi.x + Dpi.y) / 2); + ables = []; ZoomLevel = 1; initialWindowPos = Location; @@ -102,7 +107,7 @@ public partial class GraphForm : Form // Draw horizontal/vertical quarter-axis. Brush quarterBrush = new SolidBrush(QuarterAxisColor); - Pen quarterPen = new(quarterBrush, 2); + Pen quarterPen = new(quarterBrush, DpiFloat * 2 / 192); for (double x = Math.Ceiling(MinVisibleGraph.x * 4 / axisScale) * axisScale / 4; x <= Math.Floor(MaxVisibleGraph.x * 4 / axisScale) * axisScale / 4; x += axisScale / 4) { @@ -119,7 +124,7 @@ public partial class GraphForm : Form // Draw horizontal/vertical semi-axis. Brush semiBrush = new SolidBrush(SemiAxisColor); - Pen semiPen = new(semiBrush, 2); + Pen semiPen = new(semiBrush, DpiFloat * 2 / 192); for (double x = Math.Ceiling(MinVisibleGraph.x / axisScale) * axisScale; x <= Math.Floor(MaxVisibleGraph.x / axisScale) * axisScale; x += axisScale) { @@ -135,7 +140,7 @@ public partial class GraphForm : Form } Brush mainLineBrush = new SolidBrush(MainAxisColor); - Pen mainLinePen = new(mainLineBrush, 3); + Pen mainLinePen = new(mainLineBrush, DpiFloat * 3 / 192); // Draw the main axis (on top of the semi axis). Int2 startCenterY = GraphSpaceToScreenSpace(new Float2(0, MinVisibleGraph.y)), @@ -162,7 +167,8 @@ public partial class GraphForm : Form { IEnumerable lines = ables[i].GetItemsToRender(this); Brush graphBrush = new SolidBrush(ables[i].Color); - foreach (IGraphPart gp in lines) gp.Render(this, g, graphBrush); + Pen graphPen = new(graphBrush, DpiFloat * 3 / 192); + foreach (IGraphPart gp in lines) gp.Render(this, g, graphPen); } base.OnPaint(e); diff --git a/Base/Forms/ViewCacheForm.cs b/Base/Forms/ViewCacheForm.cs index 55cd1e4..72be698 100644 --- a/Base/Forms/ViewCacheForm.cs +++ b/Base/Forms/ViewCacheForm.cs @@ -32,6 +32,10 @@ public partial class ViewCacheForm : Form CachePie.Values.Add((able.Color, thisBytes)); totalBytes += thisBytes; + int buttonHeight = (int)(refForm.DpiFloat * 46 / 192), + buttonWidth = (int)(refForm.DpiFloat * 92 / 192), + buttonSpaced = (int)(refForm.DpiFloat * 98 / 192); + if (index < labelCache.Count) { Label reuseLabel = labelCache[index]; @@ -45,9 +49,9 @@ public partial class ViewCacheForm : Form Anchor = AnchorStyles.Left | AnchorStyles.Top | AnchorStyles.Right, AutoEllipsis = true, ForeColor = able.Color, - Location = new Point(0, labelCache.Count * 46), + Location = new Point(0, labelCache.Count * buttonHeight), Parent = SpecificCachePanel, - Size = new Size(SpecificCachePanel.Width - 98, 46), + Size = new Size(SpecificCachePanel.Width - buttonSpaced, buttonHeight), Text = $"{able.Name}: {thisBytes.FormatAsBytes()}", TextAlign = ContentAlignment.MiddleLeft, }; @@ -59,9 +63,9 @@ public partial class ViewCacheForm : Form Button newButton = new() { Anchor = AnchorStyles.Top | AnchorStyles.Right, - Location = new Point(SpecificCachePanel.Width - 92, buttonCache.Count * 46), + Location = new Point(SpecificCachePanel.Width - buttonWidth, buttonCache.Count * buttonHeight), Parent = SpecificCachePanel, - Size = new Size(92, 46), + Size = new Size(buttonWidth, buttonHeight), Text = "Clear" }; newButton.Click += (o, e) => EraseSpecificGraphable_Click(able); diff --git a/Base/IGraphPart.cs b/Base/IGraphPart.cs index 0e3c592..083ef85 100644 --- a/Base/IGraphPart.cs +++ b/Base/IGraphPart.cs @@ -4,5 +4,5 @@ namespace Graphing; public interface IGraphPart { - public void Render(in GraphForm form, in Graphics g, in Brush brush); + public void Render(in GraphForm form, in Graphics g, in Pen pen); } diff --git a/Base/Parts/GraphLine.cs b/Base/Parts/GraphLine.cs index fff70f1..7c042e1 100644 --- a/Base/Parts/GraphLine.cs +++ b/Base/Parts/GraphLine.cs @@ -18,15 +18,13 @@ public record struct GraphLine : IGraphPart this.b = b; } - public readonly void Render(in GraphForm form, in Graphics g, in Brush brush) + public readonly void Render(in GraphForm form, in Graphics g, in Pen pen) { 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); - - Pen pen = new(brush, 3); g.DrawLine(pen, start, end); } } diff --git a/Base/Parts/GraphRectangle.cs b/Base/Parts/GraphRectangle.cs index a874a42..54b23fc 100644 --- a/Base/Parts/GraphRectangle.cs +++ b/Base/Parts/GraphRectangle.cs @@ -25,7 +25,7 @@ public record struct GraphRectangle : IGraphPart max = max }; - public void Render(in GraphForm form, in Graphics g, in Brush brush) + public void Render(in GraphForm form, in Graphics g, in Pen pen) { if (!double.IsFinite(max.x) || !double.IsFinite(max.y) || !double.IsFinite(min.x) || !double.IsFinite(min.y)) return; @@ -40,6 +40,6 @@ public record struct GraphRectangle : IGraphPart 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)); + g.FillRectangle(pen.Brush, new Rectangle(start.x, end.y, size.x, size.y)); } } diff --git a/Base/Parts/GraphUiCircle.cs b/Base/Parts/GraphUiCircle.cs index 28bb010..aa3fc96 100644 --- a/Base/Parts/GraphUiCircle.cs +++ b/Base/Parts/GraphUiCircle.cs @@ -18,14 +18,14 @@ public record struct GraphUiCircle : IGraphPart this.radius = radius; } - public readonly void Render(in GraphForm form, in Graphics g, in Brush brush) + public readonly void Render(in GraphForm form, in Graphics g, in Pen pen) { 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, - centerPix.y - radius), + g.FillEllipse(pen.Brush, new Rectangle(new Point(centerPix.x - radius, + centerPix.y - radius), new Size(radius * 2, radius * 2))); } } diff --git a/Testing/Program.cs b/Testing/Program.cs index 105bc57..b154bc8 100644 --- a/Testing/Program.cs +++ b/Testing/Program.cs @@ -10,28 +10,14 @@ internal static class Program { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); - Application.SetHighDpiMode(HighDpiMode.SystemAware); + Application.SetHighDpiMode(HighDpiMode.PerMonitorV2); GraphForm graph = new("One Of The Graphing Calculators Of All Time"); - Equation equ1 = 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. - - // Remove this loop to make the equation fast again. I didn't - // slow the engine down much more with this improvement, so any - // speed decrease you might notice is likely this function. - for (int i = 0; i < 1_000_000; i++) ; - return -x * x + 2; - }); + Equation equ1 = new(x => -x * x + 2); Equation equ2 = new(x => x); Equation equ3 = new(x => -Math.Sqrt(x)); - SlopeField sf = new(2, (x, y) => (x * x - y * y) / x); - graph.Graph(equ1, equ2, equ3, sf); + graph.Graph(equ1, equ2, equ3); // You can also now view and reset caches in the UI by going to // Misc > View Caches. -- 2.49.0.windows.1 From f87ef52a7d035a9120a7077e2b61fc5692cb1a8f Mon Sep 17 00:00:00 2001 From: That_One_Nerd Date: Thu, 14 Mar 2024 14:03:01 -0400 Subject: [PATCH 02/13] Added units. Good enough for now. Also increased max zoom. --- Base/Forms/GraphForm.cs | 65 ++++++++++++++++++++++++++++++++++------- Testing/Program.cs | 2 +- 2 files changed, 56 insertions(+), 11 deletions(-) diff --git a/Base/Forms/GraphForm.cs b/Base/Forms/GraphForm.cs index 37ef874..adf83a5 100644 --- a/Base/Forms/GraphForm.cs +++ b/Base/Forms/GraphForm.cs @@ -1,7 +1,5 @@ -using Graphing.Extensions; -using Graphing.Graphables; +using Graphing.Graphables; using System.Drawing.Drawing2D; -using System.Text; namespace Graphing.Forms; @@ -10,6 +8,7 @@ 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 static readonly Color UnitsTextColor = Color.Black; public Float2 ScreenCenter { get; private set; } public Float2 Dpi { get; private set; } @@ -23,7 +22,7 @@ public partial class GraphForm : Form { double oldZoom = ZoomLevel; - _zoomLevel = Math.Clamp(value, 1e-2, 1e3); + _zoomLevel = Math.Clamp(value, 1e-5, 1e3); int totalSegments = 0; foreach (Graphable able in ables) totalSegments += able.GetItemsToRender(this).Count(); @@ -151,6 +150,44 @@ public partial class GraphForm : Form g.DrawLine(mainLinePen, startCenterX, endCenterX); g.DrawLine(mainLinePen, startCenterY, endCenterY); } + protected virtual void PaintUnits(Graphics g) + { + double axisScale = Math.Pow(2, Math.Round(Math.Log(ZoomLevel, 2))); + Brush textBrush = new SolidBrush(UnitsTextColor); + Font textFont = new(Font.Name, 9, FontStyle.Regular); + + // X-axis + int minX = (int)(DpiFloat * 50 / 192), + maxX = ClientRectangle.Height - (int)(DpiFloat * 40 / 192); + for (double x = Math.Ceiling(MinVisibleGraph.x / axisScale) * axisScale; x <= MaxVisibleGraph.x; x += axisScale) + { + if (x == 0) x = 0; // Fixes -0 + + Int2 screenPos = GraphSpaceToScreenSpace(new Float2(x, 0)); + + if (screenPos.y < minX) screenPos.y = minX; + else if (screenPos.y > maxX) screenPos.y = maxX; + + g.DrawString($"{x}", textFont, textBrush, screenPos.x, screenPos.y); + } + + // Y-axis + int minY = (int)(DpiFloat * 10 / 192); + for (double y = Math.Ceiling(MinVisibleGraph.y / axisScale) * axisScale; y <= MaxVisibleGraph.y; y += axisScale) + { + if (y == 0) continue; + + Int2 screenPos = GraphSpaceToScreenSpace(new Float2(0, y)); + + string result = y.ToString(); + int maxY = ClientRectangle.Width - (int)(DpiFloat * (textFont.Height * result.Length * 0.40 + 15) / 192); + + if (screenPos.x < minY) screenPos.x = minY; + else if (screenPos.x > maxY) screenPos.x = maxY; + + g.DrawString($"{y}", textFont, textBrush, screenPos.x, screenPos.y); + } + } protected override void OnPaint(PaintEventArgs e) { @@ -161,6 +198,7 @@ public partial class GraphForm : Form g.FillRectangle(background, e.ClipRectangle); PaintGrid(g); + PaintUnits(g); // Draw the actual graphs. for (int i = 0; i < ables.Count; i++) @@ -232,7 +270,6 @@ public partial class GraphForm : Form ZoomLevel = 1; Invalidate(false); } - private void GraphColorPickerButton_Click(Graphable able) { GraphColorPickerForm picker = new(this, able) @@ -241,6 +278,13 @@ public partial class GraphForm : Form }; picker.Location = new Point(Location.X + ClientRectangle.Width + 10, Location.Y + (ClientRectangle.Height - picker.ClientRectangle.Height) / 2); + + if (picker.Location.X + picker.Width > Screen.FromControl(this).WorkingArea.Width) + { + picker.StartPosition = FormStartPosition.WindowsDefaultLocation; + } + + picker.TopMost = true; picker.ShowDialog(); RegenerateMenuItems(); } @@ -292,26 +336,22 @@ public partial class GraphForm : Form Location.Y + (ClientRectangle.Height - picker.ClientRectangle.Height) / 2); picker.ShowDialog(); } - private void ButtonViewportSetCenter_Click(object? sender, EventArgs e) { MessageBox.Show("TODO", "Set Center Position", MessageBoxButtons.OK, MessageBoxIcon.Error); } - private void ButtonViewportReset_Click(object? sender, EventArgs e) { ScreenCenter = new Float2(0, 0); ZoomLevel = 1; Invalidate(false); } - private void ButtonViewportResetWindow_Click(object? sender, EventArgs e) { Location = initialWindowPos; Size = initialWindowSize; WindowState = FormWindowState.Normal; } - private void EquationComputeDerivative_Click(Equation equation) { EquationDelegate equ = equation.GetDelegate(); @@ -332,7 +372,6 @@ public partial class GraphForm : Form return x => (e(x + step) - e(x)) / step; } } - private void EquationComputeIntegral_Click(Equation equation) { EquationDelegate equ = equation.GetDelegate(); @@ -378,6 +417,12 @@ public partial class GraphForm : Form cacheForm.Location = new Point(Location.X + ClientRectangle.Width + 10, Location.Y + (ClientRectangle.Height - cacheForm.ClientRectangle.Height) / 2); + + if (cacheForm.Location.X + cacheForm.Width > Screen.FromControl(this).WorkingArea.Width) + { + cacheForm.StartPosition = FormStartPosition.WindowsDefaultLocation; + } + cacheForm.TopMost = true; cacheForm.Show(); } } diff --git a/Testing/Program.cs b/Testing/Program.cs index b154bc8..993e0d1 100644 --- a/Testing/Program.cs +++ b/Testing/Program.cs @@ -10,7 +10,7 @@ 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"); -- 2.49.0.windows.1 From 255a7d3774983be0eec420e960c0992a722f64d5 Mon Sep 17 00:00:00 2001 From: That_One_Nerd Date: Sun, 17 Mar 2024 00:45:11 -0400 Subject: [PATCH 03/13] Starting progress on graph selection and preloading. More to come. --- Base/Forms/GraphForm.Designer.cs | 11 +++++- Base/Forms/GraphForm.cs | 57 +++++++++++++++++++++++++++++--- Base/Graphable.cs | 5 ++- Base/Graphables/ColumnTable.cs | 5 +++ Base/Graphables/Equation.cs | 24 ++++++++++++++ Base/Graphables/SlopeField.cs | 5 +++ Base/Graphables/TangentLine.cs | 5 +++ 7 files changed, 106 insertions(+), 6 deletions(-) diff --git a/Base/Forms/GraphForm.Designer.cs b/Base/Forms/GraphForm.Designer.cs index 15c0cfe..4bb2c83 100644 --- a/Base/Forms/GraphForm.Designer.cs +++ b/Base/Forms/GraphForm.Designer.cs @@ -41,6 +41,7 @@ MenuEquationsIntegral = new ToolStripMenuItem(); MenuMisc = new ToolStripMenuItem(); MenuMiscCaches = new ToolStripMenuItem(); + MiscMenuPreload = new ToolStripMenuItem(); GraphMenu.SuspendLayout(); SuspendLayout(); // @@ -129,7 +130,7 @@ // // MenuMisc // - MenuMisc.DropDownItems.AddRange(new ToolStripItem[] { MenuMiscCaches }); + MenuMisc.DropDownItems.AddRange(new ToolStripItem[] { MenuMiscCaches, MiscMenuPreload }); MenuMisc.Name = "MenuMisc"; MenuMisc.Size = new Size(83, 38); MenuMisc.Text = "Misc"; @@ -141,6 +142,13 @@ MenuMiscCaches.Text = "View Caches"; MenuMiscCaches.Click += MenuMiscCaches_Click; // + // MiscMenuPreload + // + MiscMenuPreload.Name = "MiscMenuPreload"; + MiscMenuPreload.Size = new Size(359, 44); + MiscMenuPreload.Text = "Preload Cache"; + MiscMenuPreload.Click += MiscMenuPreload_Click; + // // GraphForm // AutoScaleDimensions = new SizeF(13F, 32F); @@ -172,5 +180,6 @@ private ToolStripMenuItem MenuEquationsIntegral; private ToolStripMenuItem MenuMisc; private ToolStripMenuItem MenuMiscCaches; + private ToolStripMenuItem MiscMenuPreload; } } \ No newline at end of file diff --git a/Base/Forms/GraphForm.cs b/Base/Forms/GraphForm.cs index adf83a5..8523230 100644 --- a/Base/Forms/GraphForm.cs +++ b/Base/Forms/GraphForm.cs @@ -1,4 +1,5 @@ using Graphing.Graphables; +using Graphing.Parts; using System.Drawing.Drawing2D; namespace Graphing.Forms; @@ -200,6 +201,10 @@ public partial class GraphForm : Form PaintGrid(g); PaintUnits(g); + Point clientMousePos = PointToClient(Cursor.Position); + Float2 graphMousePos = ScreenSpaceToGraphSpace(new(clientMousePos.X, + clientMousePos.Y)); + // Draw the actual graphs. for (int i = 0; i < ables.Count; i++) { @@ -207,6 +212,18 @@ public partial class GraphForm : Form Brush graphBrush = new SolidBrush(ables[i].Color); Pen graphPen = new(graphBrush, DpiFloat * 3 / 192); foreach (IGraphPart gp in lines) gp.Render(this, g, graphPen); + + // Equation selection detection. + // This system lets you select multiple graphs, and that's cool by me. + if (ableDrag) + { + if (ables[i].ShouldSelectGraphable(this, graphMousePos, 2.5)) + { + Float2 selectedPoint = ables[i].GetSelectedPoint(this, graphMousePos); + GraphUiCircle select = new(selectedPoint, 8); + select.Render(this, g, graphPen); + } + } } base.OnPaint(e); @@ -227,11 +244,28 @@ public partial class GraphForm : Form private bool mouseDrag = false; private Int2 initialMouseLocation; private Float2 initialScreenCenter; + + private bool ableDrag = false; protected override void OnMouseDown(MouseEventArgs e) { - mouseDrag = true; - initialMouseLocation = new Int2(Cursor.Position.X, Cursor.Position.Y); - initialScreenCenter = ScreenCenter; + if (!mouseDrag) + { + Point clientMousePos = PointToClient(Cursor.Position); + Float2 graphMousePos = ScreenSpaceToGraphSpace(new(clientMousePos.X, + clientMousePos.Y)); + foreach (Graphable able in Graphables) + { + if (able.ShouldSelectGraphable(this, graphMousePos, 1)) ableDrag = true; + } + if (ableDrag) Invalidate(false); + } + + if (!ableDrag) + { + mouseDrag = true; + initialMouseLocation = new Int2(Cursor.Position.X, Cursor.Position.Y); + initialScreenCenter = ScreenCenter; + } } protected override void OnMouseUp(MouseEventArgs e) { @@ -242,9 +276,10 @@ public partial class GraphForm : Form Float2 graphDiff = new(pixelDiff.x * ZoomLevel / Dpi.x, pixelDiff.y * ZoomLevel / Dpi.y); ScreenCenter = new(initialScreenCenter.x + graphDiff.x, initialScreenCenter.y + graphDiff.y); - Invalidate(false); } mouseDrag = false; + ableDrag = false; + Invalidate(false); } protected override void OnMouseMove(MouseEventArgs e) { @@ -257,6 +292,7 @@ public partial class GraphForm : Form initialScreenCenter.y + graphDiff.y); Invalidate(false); } + else if (ableDrag) Invalidate(false); } protected override void OnMouseWheel(MouseEventArgs e) { @@ -425,4 +461,17 @@ public partial class GraphForm : Form cacheForm.TopMost = true; cacheForm.Show(); } + private void MiscMenuPreload_Click(object sender, EventArgs e) + { + 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; // + + 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); + Invalidate(false); + } } diff --git a/Base/Graphable.cs b/Base/Graphable.cs index 5f42e15..6db3278 100644 --- a/Base/Graphable.cs +++ b/Base/Graphable.cs @@ -1,5 +1,4 @@ using Graphing.Forms; -using Graphing.Parts; namespace Graphing; @@ -33,4 +32,8 @@ public abstract class Graphable public abstract void EraseCache(); public abstract long GetCacheBytes(); + public abstract void Preload(Float2 xRange, Float2 yRange); + + 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 876e070..567d07a 100644 --- a/Base/Graphables/ColumnTable.cs +++ b/Base/Graphables/ColumnTable.cs @@ -48,4 +48,9 @@ public class ColumnTable : Graphable return items; } + + 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) { } } diff --git a/Base/Graphables/Equation.cs b/Base/Graphables/Equation.cs index fb265e8..9cd34be 100644 --- a/Base/Graphables/Equation.cs +++ b/Base/Graphables/Equation.cs @@ -96,6 +96,30 @@ public class Equation : Graphable public override Graphable DeepCopy() => new Equation(equ); public override long GetCacheBytes() => cache.Count * 16; + + public override bool ShouldSelectGraphable(in GraphForm graph, Float2 graphMousePos, double factor) + { + Int2 screenMousePos = graph.GraphSpaceToScreenSpace(graphMousePos); + + (_, _, int index) = NearestCachedPoint(graphMousePos.x); + Int2 screenCachePos = graph.GraphSpaceToScreenSpace(cache[index]); + + double allowedDist = factor * graph.DpiFloat * 80 / 192; + + Int2 dist = new(screenCachePos.x - screenMousePos.x, + screenCachePos.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) + { + return new(graphMousePos.x, GetFromCache(graphMousePos.x, 0.001)); + } + + public override void Preload(Float2 xRange, Float2 yRange) + { + for (double x = xRange.x; x <= xRange.y; x += 1e-3) GetFromCache(x, 1e-4); + } } public delegate double EquationDelegate(double x); diff --git a/Base/Graphables/SlopeField.cs b/Base/Graphables/SlopeField.cs index 9fd63dc..a26200f 100644 --- a/Base/Graphables/SlopeField.cs +++ b/Base/Graphables/SlopeField.cs @@ -74,6 +74,11 @@ public class SlopeField : Graphable public override void EraseCache() => cache.Clear(); public override long GetCacheBytes() => cache.Count * 48; + + 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 delegate double SlopeFieldsDelegate(double x, double y); diff --git a/Base/Graphables/TangentLine.cs b/Base/Graphables/TangentLine.cs index 33ceb7f..aa4596f 100644 --- a/Base/Graphables/TangentLine.cs +++ b/Base/Graphables/TangentLine.cs @@ -48,4 +48,9 @@ public class TangentLine : Graphable public override void EraseCache() { } public override long GetCacheBytes() => 0; + + 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) { } } -- 2.49.0.windows.1 From 5c3cf9cafe300c9423522147367c554ed08b2960 Mon Sep 17 00:00:00 2001 From: That_One_Nerd Date: Mon, 18 Mar 2024 08:57:00 -0400 Subject: [PATCH 04/13] Made preload functions for most of the other graphables. --- Base/Forms/Controls/PieChart.cs | 21 +++++++++++------- Base/Forms/GraphForm.cs | 8 +++++-- Base/Graphable.cs | 2 +- Base/Graphables/ColumnTable.cs | 3 ++- Base/Graphables/Equation.cs | 4 ++-- Base/Graphables/SlopeField.cs | 11 +++++++++- Base/Graphables/TangentLine.cs | 39 ++++++++++++++++++++++++++++----- Testing/Program.cs | 8 +++---- 8 files changed, 71 insertions(+), 25 deletions(-) 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. -- 2.49.0.windows.1 From 7ff3720b7057585fc7612aef9b90026f43762a72 Mon Sep 17 00:00:00 2001 From: That_One_Nerd Date: Mon, 18 Mar 2024 09:14:56 -0400 Subject: [PATCH 05/13] Made most of the methods in virtual. --- Base/Graphable.cs | 12 ++++++------ Base/Graphables/ColumnTable.cs | 1 - Base/Graphables/Equation.cs | 6 ++---- Base/Graphables/TangentLine.cs | 2 +- README.md | 4 ++++ Testing/Program.cs | 11 ++++++----- 6 files changed, 19 insertions(+), 17 deletions(-) diff --git a/Base/Graphable.cs b/Base/Graphable.cs index 0608f58..15cbc3e 100644 --- a/Base/Graphable.cs +++ b/Base/Graphable.cs @@ -7,12 +7,12 @@ public abstract class Graphable private static int defaultColorsUsed; public static readonly uint[] DefaultColors = [ - 0xEF_B34D47, // Red - 0xEF_4769B3, // Blue - 0xEF_50B347, // Green - 0xEF_7047B3, // Purple - 0xEF_B38B47, // Orange - 0xEF_5B5B5B // Black + 0xFF_B34D47, // Red + 0xFF_4769B3, // Blue + 0xFF_50B347, // Green + 0xFF_7047B3, // Purple + 0xFF_B38B47, // Orange + 0xFF_5B5B5B // Black ]; public Color Color { get; set; } diff --git a/Base/Graphables/ColumnTable.cs b/Base/Graphables/ColumnTable.cs index 673cd40..4038ca6 100644 --- a/Base/Graphables/ColumnTable.cs +++ b/Base/Graphables/ColumnTable.cs @@ -32,7 +32,6 @@ public class ColumnTable : Graphable 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()); diff --git a/Base/Graphables/Equation.cs b/Base/Graphables/Equation.cs index 4828be2..a2cb3ef 100644 --- a/Base/Graphables/Equation.cs +++ b/Base/Graphables/Equation.cs @@ -111,10 +111,8 @@ public class Equation : Graphable double totalDist = Math.Sqrt(dist.x * dist.x + dist.y * dist.y); return totalDist <= allowedDist; } - public override Float2 GetSelectedPoint(in GraphForm graph, Float2 graphMousePos) - { - return new(graphMousePos.x, GetFromCache(graphMousePos.x, 0.001)); - } + public override Float2 GetSelectedPoint(in GraphForm graph, Float2 graphMousePos) => + new(graphMousePos.x, GetFromCache(graphMousePos.x, 1e-3)); public override void Preload(Float2 xRange, Float2 yRange, double step) { diff --git a/Base/Graphables/TangentLine.cs b/Base/Graphables/TangentLine.cs index d66736e..549c881 100644 --- a/Base/Graphables/TangentLine.cs +++ b/Base/Graphables/TangentLine.cs @@ -14,7 +14,7 @@ public class TangentLine : Graphable _position = value; } } - private double _position; + private double _position; // Private because it has exactly the same functionality as `Position`. protected readonly Equation parent; protected readonly EquationDelegate parentEqu; diff --git a/README.md b/README.md index 7266efe..8ba0c76 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,10 @@ Currently, it doesn't have a whole lot of features, but I'll be adding more in t - Graph an equation (duh). - There are currently some rendering issues with asymptotes which will be focused on at some point. - Graph a slope field of a `dy/dx =` style equation. +- View a tangent line of an equation. +- Display a vertical bar graph. + +However, you can develop your own features as well. The system does not and likely will not (at least for a while) support text-to-equation parsing. You must import this project as a library and add graphs that way. diff --git a/Testing/Program.cs b/Testing/Program.cs index 5be5d87..090be95 100644 --- a/Testing/Program.cs +++ b/Testing/Program.cs @@ -14,13 +14,14 @@ internal static class Program GraphForm graph = new("One Of The Graphing Calculators Of All Time"); - Equation possibleA = new(x => x * x * x); - SlopeField sf = new(2, (x, y) => 3 * x * x); - TangentLine tl = new(5, 2, possibleA); + Equation possibleA = new(x => Math.Sin(x)); + SlopeField sf = new(2, (x, y) => Math.Cos(x)); + TangentLine tl = new(2, 2, possibleA); graph.Graph(possibleA, sf, tl); - // You can also now view and reset caches in the UI by going to - // Misc > View Caches. + // You can preload graphs in by going Misc > Preload Cache. + // Keep in mind this uses more memory than usual and can take + // some time. Application.Run(graph); } -- 2.49.0.windows.1 From 8618442bba9d03e699cff856fa95a8b7ba523094 Mon Sep 17 00:00:00 2001 From: That_One_Nerd Date: Mon, 18 Mar 2024 09:24:34 -0400 Subject: [PATCH 06/13] Forgot this file, my bad. --- Base/Graphable.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Base/Graphable.cs b/Base/Graphable.cs index 15cbc3e..83e3666 100644 --- a/Base/Graphable.cs +++ b/Base/Graphable.cs @@ -30,10 +30,10 @@ public abstract class Graphable public abstract Graphable DeepCopy(); - public abstract void EraseCache(); - public abstract long GetCacheBytes(); - public abstract void Preload(Float2 xRange, Float2 yRange, double step); + public virtual void EraseCache() { } + public virtual long GetCacheBytes() => 0; + public virtual 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); + public virtual bool ShouldSelectGraphable(in GraphForm graph, Float2 graphMousePos, double factor) => false; + public virtual Float2 GetSelectedPoint(in GraphForm graph, Float2 graphMousePos) => default; } -- 2.49.0.windows.1 From 55cb7af2acc16f05f83bb8a344fea2d6cb27590d Mon Sep 17 00:00:00 2001 From: That_One_Nerd Date: Mon, 18 Mar 2024 13:14:30 -0400 Subject: [PATCH 07/13] More selection stuff. Do we really want to select tangent lines this way? --- Base/Graphables/ColumnTable.cs | 3 -- Base/Graphables/SlopeField.cs | 41 ++++++++++++++++++++++-- Base/Graphables/TangentLine.cs | 58 ++++++++++++++++++++++++++-------- Testing/Program.cs | 4 +-- 4 files changed, 85 insertions(+), 21 deletions(-) diff --git a/Base/Graphables/ColumnTable.cs b/Base/Graphables/ColumnTable.cs index 4038ca6..b438b62 100644 --- a/Base/Graphables/ColumnTable.cs +++ b/Base/Graphables/ColumnTable.cs @@ -48,9 +48,6 @@ public class ColumnTable : Graphable return items; } - public override bool ShouldSelectGraphable(in GraphForm graph, Float2 graphMousePos, double factor) => false; - public override Float2 GetSelectedPoint(in GraphForm graph, Float2 graphMousePos) => default; - // Nothing to preload, everything is already cached. public override void Preload(Float2 xRange, Float2 yRange, double step) { } } diff --git a/Base/Graphables/SlopeField.cs b/Base/Graphables/SlopeField.cs index b96d64e..858d2a9 100644 --- a/Base/Graphables/SlopeField.cs +++ b/Base/Graphables/SlopeField.cs @@ -74,9 +74,46 @@ public class SlopeField : Graphable public override void EraseCache() => cache.Clear(); public override long GetCacheBytes() => cache.Count * 48; + + public override bool ShouldSelectGraphable(in GraphForm graph, Float2 graphMousePos, double factor) + { + Float2 nearestPos = new(Math.Round(graphMousePos.x * detail) / detail, + Math.Round(graphMousePos.y * detail) / detail); - public override bool ShouldSelectGraphable(in GraphForm graph, Float2 graphMousePos, double factor) => false; - public override Float2 GetSelectedPoint(in GraphForm graph, Float2 graphMousePos) => default; + double epsilon = 1 / (detail * 2.0); + GraphLine line = GetFromCache(epsilon, nearestPos.x, nearestPos.y); + double slope = (line.b.y - line.a.y) / (line.b.x - line.a.x); + + if (graphMousePos.x < Math.Min(line.a.x, line.b.x) || + graphMousePos.x > Math.Max(line.a.x, line.b.x)) return false; + + double allowedDist = factor * graph.DpiFloat * 10 / 192; + + double lineX = graphMousePos.x, + lineY = slope * (lineX - nearestPos.x) + nearestPos.y; + + Int2 pointScreen = graph.GraphSpaceToScreenSpace(new Float2(lineX, lineY)); + Int2 mouseScreen = graph.GraphSpaceToScreenSpace(graphMousePos); + Int2 dist = new(pointScreen.x - mouseScreen.x, + pointScreen.y - mouseScreen.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) + { + Float2 nearestPos = new(Math.Round(graphMousePos.x * detail) / detail, + Math.Round(graphMousePos.y * detail) / detail); + + double epsilon = 1 / (detail * 2.0); + GraphLine line = GetFromCache(epsilon, nearestPos.x, nearestPos.y); + double slope = (line.b.y - line.a.y) / (line.b.x - line.a.x); + + double lineX = graphMousePos.x, + lineY = slope * (lineX - nearestPos.x) + nearestPos.y; + Float2 point = new(lineX, lineY); + + return point; + } public override void Preload(Float2 xRange, Float2 yRange, double step) { diff --git a/Base/Graphables/TangentLine.cs b/Base/Graphables/TangentLine.cs index 549c881..f32c460 100644 --- a/Base/Graphables/TangentLine.cs +++ b/Base/Graphables/TangentLine.cs @@ -21,10 +21,12 @@ public class TangentLine : Graphable protected readonly double length; - protected double currentSlope; + // X is slope, Y is height. + protected Float2 currentSlope; // No binary search for this, I want it to be exact. - protected Dictionary slopeCache; + // Value: X is slope, Y is height. + protected Dictionary slopeCache; public TangentLine(double length, double position, Equation parent) { @@ -39,27 +41,28 @@ public class TangentLine : Graphable public override IEnumerable GetItemsToRender(in GraphForm graph) { - Float2 point = new(Position, parentEqu(Position)); - return [MakeSlopeLine(point, currentSlope), - new GraphUiCircle(point, 8)]; + Float2 point = new(Position, currentSlope.y); + return [MakeSlopeLine(), new GraphUiCircle(point, 8)]; } - protected GraphLine MakeSlopeLine(Float2 position, double slope) + protected GraphLine MakeSlopeLine() { - double dirX = length, dirY = slope * length; + double dirX = length, dirY = currentSlope.x * 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)); + return new(new(Position + dirX, currentSlope.y + dirY), new(Position - dirX, currentSlope.y - dirY)); } - protected double DerivativeAtPoint(double x) + protected Float2 DerivativeAtPoint(double x) { // If value is already computed, return it. - if (slopeCache.TryGetValue(x, out double y)) return y; + if (slopeCache.TryGetValue(x, out Float2 val)) return val; const double step = 1e-3; - double result = (parentEqu(x + step) - parentEqu(x)) / step; + + double initial = parentEqu(x); + Float2 result = new((parentEqu(x + step) - initial) / step, initial); slopeCache.Add(x, result); return result; } @@ -67,10 +70,37 @@ public class TangentLine : Graphable public override Graphable DeepCopy() => new TangentLine(length, Position, parent); public override void EraseCache() => slopeCache.Clear(); - public override long GetCacheBytes() => slopeCache.Count * 16; + public override long GetCacheBytes() => slopeCache.Count * 24; - public override bool ShouldSelectGraphable(in GraphForm graph, Float2 graphMousePos, double factor) => false; - public override Float2 GetSelectedPoint(in GraphForm graph, Float2 graphMousePos) => default; + public override bool ShouldSelectGraphable(in GraphForm graph, Float2 graphMousePos, double factor) + { + GraphLine line = MakeSlopeLine(); + + if (graphMousePos.x < Math.Min(line.a.x - 0.25, line.b.x - 0.25) || + graphMousePos.x > Math.Max(line.a.x + 0.25, line.b.x + 0.25)) return false; + + double allowedDist = factor * graph.DpiFloat * 80 / 192; + + double lineX = graphMousePos.x, + lineY = currentSlope.x * (lineX - Position) + currentSlope.y; + + Int2 pointScreen = graph.GraphSpaceToScreenSpace(new Float2(lineX, lineY)); + Int2 mouseScreen = graph.GraphSpaceToScreenSpace(graphMousePos); + Int2 dist = new(pointScreen.x - mouseScreen.x, + pointScreen.y - mouseScreen.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) + { + GraphLine line = MakeSlopeLine(); + + double lineX = Math.Clamp(graphMousePos.x, + Math.Min(line.a.x, line.b.x), + Math.Max(line.a.x, line.b.x)), + lineY = currentSlope.x * (lineX - Position) + currentSlope.y; + return new Float2(lineX, lineY); + } public override void Preload(Float2 xRange, Float2 yRange, double step) { diff --git a/Testing/Program.cs b/Testing/Program.cs index 090be95..88beefd 100644 --- a/Testing/Program.cs +++ b/Testing/Program.cs @@ -14,8 +14,8 @@ internal static class Program GraphForm graph = new("One Of The Graphing Calculators Of All Time"); - Equation possibleA = new(x => Math.Sin(x)); - SlopeField sf = new(2, (x, y) => Math.Cos(x)); + Equation possibleA = new(x => x * x * x); + SlopeField sf = new(2, (x, y) => 1 / x); TangentLine tl = new(2, 2, possibleA); graph.Graph(possibleA, sf, tl); -- 2.49.0.windows.1 From c30ced7578f6333f5f20fc8759f4a48ffbf76553 Mon Sep 17 00:00:00 2001 From: That_One_Nerd Date: Mon, 18 Mar 2024 14:07:21 -0400 Subject: [PATCH 08/13] Almost done. Added coordinates for selected points. --- Base/Forms/GraphForm.cs | 24 ++++++++++++++++++++---- Base/Parts/GraphUiCircle.cs | 8 +++++--- Testing/Program.cs | 6 +++--- 3 files changed, 28 insertions(+), 10 deletions(-) diff --git a/Base/Forms/GraphForm.cs b/Base/Forms/GraphForm.cs index 44fe52c..5909cfe 100644 --- a/Base/Forms/GraphForm.cs +++ b/Base/Forms/GraphForm.cs @@ -206,22 +206,38 @@ public partial class GraphForm : Form clientMousePos.Y)); // Draw the actual graphs. + Pen[] graphPens = new Pen[ables.Count]; for (int i = 0; i < ables.Count; i++) { IEnumerable lines = ables[i].GetItemsToRender(this); Brush graphBrush = new SolidBrush(ables[i].Color); Pen graphPen = new(graphBrush, DpiFloat * 3 / 192); + graphPens[i] = graphPen; foreach (IGraphPart gp in lines) gp.Render(this, g, graphPen); + } - // Equation selection detection. - // This system lets you select multiple graphs, and that's cool by me. - if (ableDrag) + // Equation selection detection. + // This system lets you select multiple graphs, and that's cool by me. + if (ableDrag) + { + Font textFont = new(Font.Name, 8, FontStyle.Bold); + for (int i = 0; i < ables.Count; i++) { if (ables[i].ShouldSelectGraphable(this, graphMousePos, 2.5)) { Float2 selectedPoint = ables[i].GetSelectedPoint(this, graphMousePos); GraphUiCircle select = new(selectedPoint, 8); - select.Render(this, g, graphPen); + + Int2 textPos = GraphSpaceToScreenSpace(select.center); + textPos.y -= (int)(DpiFloat * 32 / 192); + + string content = $"({selectedPoint.x:0.00}, {selectedPoint.y:0.00})"; + + SizeF textSize = g.MeasureString(content, textFont); + g.FillRectangle(background, new Rectangle(textPos.x, textPos.y, + (int)textSize.Width, (int)textSize.Height)); + g.DrawString(content, textFont, graphPens[i].Brush, new Point(textPos.x, textPos.y)); + select.Render(this, g, graphPens[i]); } } } diff --git a/Base/Parts/GraphUiCircle.cs b/Base/Parts/GraphUiCircle.cs index aa3fc96..fc3be63 100644 --- a/Base/Parts/GraphUiCircle.cs +++ b/Base/Parts/GraphUiCircle.cs @@ -23,9 +23,11 @@ public record struct GraphUiCircle : IGraphPart if (!double.IsFinite(center.x) || !double.IsFinite(center.y) || !double.IsFinite(radius) || radius == 0) return; + int rad = (int)(form.DpiFloat * radius / 192); + Int2 centerPix = form.GraphSpaceToScreenSpace(center); - g.FillEllipse(pen.Brush, new Rectangle(new Point(centerPix.x - radius, - centerPix.y - radius), - new Size(radius * 2, radius * 2))); + g.FillEllipse(pen.Brush, new Rectangle(new Point(centerPix.x - rad, + centerPix.y - rad), + new Size(rad * 2, rad * 2))); } } diff --git a/Testing/Program.cs b/Testing/Program.cs index 88beefd..4770ac6 100644 --- a/Testing/Program.cs +++ b/Testing/Program.cs @@ -10,12 +10,12 @@ internal static class Program { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); - Application.SetHighDpiMode(HighDpiMode.SystemAware); + Application.SetHighDpiMode(HighDpiMode.PerMonitorV2); GraphForm graph = new("One Of The Graphing Calculators Of All Time"); - Equation possibleA = new(x => x * x * x); - SlopeField sf = new(2, (x, y) => 1 / x); + Equation possibleA = new(Math.Sin); + SlopeField sf = new(2, (x, y) => Math.Cos(x)); TangentLine tl = new(2, 2, possibleA); graph.Graph(possibleA, sf, tl); -- 2.49.0.windows.1 From 762a5f5a32ff855961b472b3fb4bf23d42c1bb84 Mon Sep 17 00:00:00 2001 From: That_One_Nerd Date: Tue, 19 Mar 2024 08:57:02 -0400 Subject: [PATCH 09/13] Fixed sampling not scaling with DPI. Also disabled implicit usings which is most of this commit. --- Base/Base.csproj | 2 +- Base/Base.csproj.user | 5 +++++ Base/Float2.cs | 4 +++- Base/Forms/Controls/PieChart.Designer.cs | 6 ++++-- Base/Forms/Controls/PieChart.cs | 6 +++++- Base/Forms/GraphColorPickerForm.Designer.cs | 5 ++++- Base/Forms/GraphColorPickerForm.cs | 6 +++++- Base/Forms/GraphForm.Designer.cs | 5 ++++- Base/Forms/GraphForm.cs | 5 +++++ Base/Forms/SetZoomForm.Designer.cs | 5 ++++- Base/Forms/SetZoomForm.cs | 5 ++++- Base/Forms/ViewCacheForm.Designer.cs | 5 ++++- Base/Forms/ViewCacheForm.cs | 4 ++++ Base/Graphable.cs | 2 ++ Base/Graphables/ColumnTable.cs | 3 +++ Base/Graphables/Equation.cs | 6 +++++- Base/Graphables/SlopeField.cs | 2 ++ Base/Graphables/TangentLine.cs | 2 ++ Base/IGraphPart.cs | 1 + Base/Int2.cs | 4 +++- Base/Parts/GraphLine.cs | 1 + Base/Parts/GraphRectangle.cs | 1 + Base/Parts/GraphUiCircle.cs | 1 + Testing/Program.cs | 8 +++++--- Testing/Testing.csproj | 2 +- 25 files changed, 79 insertions(+), 17 deletions(-) diff --git a/Base/Base.csproj b/Base/Base.csproj index a26f79f..63d9cae 100644 --- a/Base/Base.csproj +++ b/Base/Base.csproj @@ -5,7 +5,7 @@ net8.0-windows enable true - enable + disable Graphing ThatOneNerd.Graphing True diff --git a/Base/Base.csproj.user b/Base/Base.csproj.user index 4bb9e32..ef577eb 100644 --- a/Base/Base.csproj.user +++ b/Base/Base.csproj.user @@ -20,4 +20,9 @@ Form + + + Designer + + \ No newline at end of file diff --git a/Base/Float2.cs b/Base/Float2.cs index 07005bc..cabb225 100644 --- a/Base/Float2.cs +++ b/Base/Float2.cs @@ -1,4 +1,6 @@ -namespace Graphing; +using System.Drawing; + +namespace Graphing; public record struct Float2 { diff --git a/Base/Forms/Controls/PieChart.Designer.cs b/Base/Forms/Controls/PieChart.Designer.cs index 18b52e0..ccf2086 100644 --- a/Base/Forms/Controls/PieChart.Designer.cs +++ b/Base/Forms/Controls/PieChart.Designer.cs @@ -1,4 +1,6 @@ -namespace Graphing.Forms.Controls +using System.Drawing; + +namespace Graphing.Forms.Controls { partial class PieChart { @@ -33,7 +35,7 @@ // PieChart // AutoScaleDimensions = new SizeF(13F, 32F); - AutoScaleMode = AutoScaleMode.Font; + AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; Name = "PieChart"; Size = new Size(500, 500); ResumeLayout(false); diff --git a/Base/Forms/Controls/PieChart.cs b/Base/Forms/Controls/PieChart.cs index 2cb05f0..3245320 100644 --- a/Base/Forms/Controls/PieChart.cs +++ b/Base/Forms/Controls/PieChart.cs @@ -1,4 +1,8 @@ -using System.Drawing.Drawing2D; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Windows.Forms; namespace Graphing.Forms.Controls; diff --git a/Base/Forms/GraphColorPickerForm.Designer.cs b/Base/Forms/GraphColorPickerForm.Designer.cs index f50ae6c..8b708a4 100644 --- a/Base/Forms/GraphColorPickerForm.Designer.cs +++ b/Base/Forms/GraphColorPickerForm.Designer.cs @@ -1,4 +1,7 @@ -namespace Graphing.Forms +using System.Drawing; +using System.Windows.Forms; + +namespace Graphing.Forms { partial class GraphColorPickerForm { diff --git a/Base/Forms/GraphColorPickerForm.cs b/Base/Forms/GraphColorPickerForm.cs index 98147cb..42afe02 100644 --- a/Base/Forms/GraphColorPickerForm.cs +++ b/Base/Forms/GraphColorPickerForm.cs @@ -1,4 +1,8 @@ -namespace Graphing.Forms; +using System; +using System.Drawing; +using System.Windows.Forms; + +namespace Graphing.Forms; public partial class GraphColorPickerForm : Form { diff --git a/Base/Forms/GraphForm.Designer.cs b/Base/Forms/GraphForm.Designer.cs index 4bb2c83..f30584b 100644 --- a/Base/Forms/GraphForm.Designer.cs +++ b/Base/Forms/GraphForm.Designer.cs @@ -1,4 +1,7 @@ -namespace Graphing.Forms +using System.Drawing; +using System.Windows.Forms; + +namespace Graphing.Forms { partial class GraphForm { diff --git a/Base/Forms/GraphForm.cs b/Base/Forms/GraphForm.cs index 5909cfe..4d13472 100644 --- a/Base/Forms/GraphForm.cs +++ b/Base/Forms/GraphForm.cs @@ -1,6 +1,11 @@ using Graphing.Graphables; using Graphing.Parts; +using System; +using System.Collections.Generic; +using System.Drawing; using System.Drawing.Drawing2D; +using System.Linq; +using System.Windows.Forms; namespace Graphing.Forms; diff --git a/Base/Forms/SetZoomForm.Designer.cs b/Base/Forms/SetZoomForm.Designer.cs index e659456..52990db 100644 --- a/Base/Forms/SetZoomForm.Designer.cs +++ b/Base/Forms/SetZoomForm.Designer.cs @@ -1,4 +1,7 @@ -namespace Graphing.Forms +using System.Drawing; +using System.Windows.Forms; + +namespace Graphing.Forms { partial class SetZoomForm { diff --git a/Base/Forms/SetZoomForm.cs b/Base/Forms/SetZoomForm.cs index 1d30ee4..fc21296 100644 --- a/Base/Forms/SetZoomForm.cs +++ b/Base/Forms/SetZoomForm.cs @@ -1,4 +1,7 @@ -namespace Graphing.Forms; +using System; +using System.Windows.Forms; + +namespace Graphing.Forms; public partial class SetZoomForm : Form { diff --git a/Base/Forms/ViewCacheForm.Designer.cs b/Base/Forms/ViewCacheForm.Designer.cs index c847e6b..e2d9498 100644 --- a/Base/Forms/ViewCacheForm.Designer.cs +++ b/Base/Forms/ViewCacheForm.Designer.cs @@ -1,4 +1,7 @@ -namespace Graphing.Forms +using System.Drawing; +using System.Windows.Forms; + +namespace Graphing.Forms { partial class ViewCacheForm { diff --git a/Base/Forms/ViewCacheForm.cs b/Base/Forms/ViewCacheForm.cs index 72be698..7f767a9 100644 --- a/Base/Forms/ViewCacheForm.cs +++ b/Base/Forms/ViewCacheForm.cs @@ -1,4 +1,8 @@ using Graphing.Extensions; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Windows.Forms; namespace Graphing.Forms; diff --git a/Base/Graphable.cs b/Base/Graphable.cs index 83e3666..2844ef7 100644 --- a/Base/Graphable.cs +++ b/Base/Graphable.cs @@ -1,4 +1,6 @@ using Graphing.Forms; +using System.Collections.Generic; +using System.Drawing; namespace Graphing; diff --git a/Base/Graphables/ColumnTable.cs b/Base/Graphables/ColumnTable.cs index b438b62..0461cc3 100644 --- a/Base/Graphables/ColumnTable.cs +++ b/Base/Graphables/ColumnTable.cs @@ -1,5 +1,8 @@ using Graphing.Forms; using Graphing.Parts; +using System; +using System.Collections.Generic; +using System.Linq; namespace Graphing.Graphables; diff --git a/Base/Graphables/Equation.cs b/Base/Graphables/Equation.cs index a2cb3ef..50864f1 100644 --- a/Base/Graphables/Equation.cs +++ b/Base/Graphables/Equation.cs @@ -1,5 +1,7 @@ using Graphing.Forms; using Graphing.Parts; +using System; +using System.Collections.Generic; namespace Graphing.Graphables; @@ -22,15 +24,17 @@ public class Equation : Graphable 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; + epsilon *= graph.DpiFloat / 192; List lines = []; double previousX = graph.MinVisibleGraph.x; double previousY = GetFromCache(previousX, epsilon); - for (int i = 1; i < graph.ClientRectangle.Width; i += step) + for (int i = 0; i < graph.ClientRectangle.Width + step; i += step) { double currentX = graph.ScreenSpaceToGraphSpace(new Int2(i, 0)).x; double currentY = GetFromCache(currentX, epsilon); diff --git a/Base/Graphables/SlopeField.cs b/Base/Graphables/SlopeField.cs index 858d2a9..066f658 100644 --- a/Base/Graphables/SlopeField.cs +++ b/Base/Graphables/SlopeField.cs @@ -1,5 +1,7 @@ using Graphing.Forms; using Graphing.Parts; +using System; +using System.Collections.Generic; namespace Graphing.Graphables; diff --git a/Base/Graphables/TangentLine.cs b/Base/Graphables/TangentLine.cs index f32c460..f22eec0 100644 --- a/Base/Graphables/TangentLine.cs +++ b/Base/Graphables/TangentLine.cs @@ -1,5 +1,7 @@ using Graphing.Forms; using Graphing.Parts; +using System; +using System.Collections.Generic; namespace Graphing.Graphables; diff --git a/Base/IGraphPart.cs b/Base/IGraphPart.cs index 083ef85..fb4ad53 100644 --- a/Base/IGraphPart.cs +++ b/Base/IGraphPart.cs @@ -1,4 +1,5 @@ using Graphing.Forms; +using System.Drawing; namespace Graphing; diff --git a/Base/Int2.cs b/Base/Int2.cs index 6377498..92dfd18 100644 --- a/Base/Int2.cs +++ b/Base/Int2.cs @@ -1,4 +1,6 @@ -namespace Graphing; +using System.Drawing; + +namespace Graphing; public record struct Int2 { diff --git a/Base/Parts/GraphLine.cs b/Base/Parts/GraphLine.cs index 7c042e1..3b7527b 100644 --- a/Base/Parts/GraphLine.cs +++ b/Base/Parts/GraphLine.cs @@ -1,4 +1,5 @@ using Graphing.Forms; +using System.Drawing; namespace Graphing.Parts; diff --git a/Base/Parts/GraphRectangle.cs b/Base/Parts/GraphRectangle.cs index 54b23fc..4881397 100644 --- a/Base/Parts/GraphRectangle.cs +++ b/Base/Parts/GraphRectangle.cs @@ -1,4 +1,5 @@ using Graphing.Forms; +using System.Drawing; namespace Graphing.Parts; diff --git a/Base/Parts/GraphUiCircle.cs b/Base/Parts/GraphUiCircle.cs index fc3be63..7f46411 100644 --- a/Base/Parts/GraphUiCircle.cs +++ b/Base/Parts/GraphUiCircle.cs @@ -1,4 +1,5 @@ using Graphing.Forms; +using System.Drawing; namespace Graphing.Parts; diff --git a/Testing/Program.cs b/Testing/Program.cs index 4770ac6..d0253c6 100644 --- a/Testing/Program.cs +++ b/Testing/Program.cs @@ -1,5 +1,7 @@ using Graphing.Forms; using Graphing.Graphables; +using System; +using System.Windows.Forms; namespace Graphing.Testing; @@ -14,10 +16,10 @@ internal static class Program GraphForm graph = new("One Of The Graphing Calculators Of All Time"); - Equation possibleA = new(Math.Sin); + Equation equ = new(x => Math.Sin(x)); SlopeField sf = new(2, (x, y) => Math.Cos(x)); - TangentLine tl = new(2, 2, possibleA); - graph.Graph(possibleA, sf, tl); + 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 diff --git a/Testing/Testing.csproj b/Testing/Testing.csproj index fe3e2f2..6d21d69 100644 --- a/Testing/Testing.csproj +++ b/Testing/Testing.csproj @@ -4,7 +4,7 @@ net8.0-windows enable true - enable + disable Graphing.Testing ThatOneNerd.Graphing.Testing -- 2.49.0.windows.1 From 855a90b452a74a29fba0dd0142f2c035b8539b93 Mon Sep 17 00:00:00 2001 From: That_One_Nerd Date: Tue, 19 Mar 2024 10:22:00 -0400 Subject: [PATCH 10/13] Starters on the integral equation. Quite nice so far, I think. --- Base/Forms/GraphForm.cs | 33 +------ Base/Graphables/Equation.cs | 2 + Base/Graphables/IntegralEquation.cs | 133 ++++++++++++++++++++++++++++ Testing/Program.cs | 6 +- 4 files changed, 138 insertions(+), 36 deletions(-) create mode 100644 Base/Graphables/IntegralEquation.cs diff --git a/Base/Forms/GraphForm.cs b/Base/Forms/GraphForm.cs index 4d13472..2ea0c2f 100644 --- a/Base/Forms/GraphForm.cs +++ b/Base/Forms/GraphForm.cs @@ -431,38 +431,7 @@ public partial class GraphForm : Form } private void EquationComputeIntegral_Click(Equation equation) { - EquationDelegate equ = equation.GetDelegate(); - string oldName = equation.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; - // TODO: anti-derive (maybe) - - Graph(new Equation(x => Integrate(equ, 0, x)) - { - Name = newName - }); - - static double Integrate(EquationDelegate e, double lower, double upper) - { - // TODO: a better rendering method could make this much faster. - const double step = 1e-2; - - double factor = 1; - if (upper < lower) - { - factor = -1; - (lower, upper) = (upper, lower); - } - - double sum = 0; - for (double x = lower; x <= upper; x += step) - { - sum += e(x) * step; - } - - return sum * factor; - } + Graph(equation.Integrate()); } private void MenuMiscCaches_Click(object? sender, EventArgs e) diff --git a/Base/Graphables/Equation.cs b/Base/Graphables/Equation.cs index 50864f1..fa8859a 100644 --- a/Base/Graphables/Equation.cs +++ b/Base/Graphables/Equation.cs @@ -48,6 +48,8 @@ public class Equation : Graphable return lines; } + public IntegralEquation Integrate() => new(this); + public EquationDelegate GetDelegate() => equ; public override void EraseCache() => cache.Clear(); diff --git a/Base/Graphables/IntegralEquation.cs b/Base/Graphables/IntegralEquation.cs new file mode 100644 index 0000000..52ff821 --- /dev/null +++ b/Base/Graphables/IntegralEquation.cs @@ -0,0 +1,133 @@ +using Graphing.Forms; +using Graphing.Parts; +using System; +using System.Collections.Generic; + +namespace Graphing.Graphables; + +public class IntegralEquation : Graphable +{ + protected readonly Equation baseEqu; + protected readonly EquationDelegate baseEquDel; + + public IntegralEquation(Equation 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 = baseEquation; + baseEquDel = baseEquation.GetDelegate(); + } + + public override Graphable DeepCopy() => new IntegralEquation(baseEqu); + + 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; + epsilon *= graph.DpiFloat / 192; + 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; + + // Now we can start. + double previousX = start; + + 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; + } + } + 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; + + // Now we can start. + double previousX = start; + + 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; + } + } + else + { + // Origin is on-screen. + // We need to do two cycles. + + // Start with right. + double start = 0, end = graph.MaxVisibleGraph.x; + double previousX = start; + double previousY = 0; + + 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; + } + + // Now do left. + start = 0; + end = graph.MinVisibleGraph.x; + previousX = start; + previousY = 0; + + 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; + } + } + + return lines; + } + + public Equation AsEquation() => new(GetIntegralAtPoint); + + // Standard integral method. + // Inefficient for successive calls. + public double GetIntegralAtPoint(double x) + { + EquationDelegate equ = baseEqu.GetDelegate(); + + double start = Math.Min(0, x), end = Math.Max(0, x); + const double step = 1e-3; + double sum = 0; + + for (double t = start; t <= end; t += step) sum += equ(t) * step; + if (x < 0) sum = -sum; + + return sum; + } +} diff --git a/Testing/Program.cs b/Testing/Program.cs index d0253c6..036d4ee 100644 --- a/Testing/Program.cs +++ b/Testing/Program.cs @@ -16,10 +16,8 @@ internal static class Program GraphForm graph = new("One Of The Graphing Calculators Of All Time"); - Equation equ = new(x => Math.Sin(x)); - SlopeField sf = new(2, (x, y) => Math.Cos(x)); - TangentLine tl = new(2, 2, equ); - graph.Graph(equ, sf, tl); + Equation equ = new(Math.Sin); + graph.Graph(equ); // You can preload graphs in by going Misc > Preload Cache. // Keep in mind this uses more memory than usual and can take -- 2.49.0.windows.1 From f5107b72386381dbefe2b09833f9a47f5cb4c045 Mon Sep 17 00:00:00 2001 From: That_One_Nerd Date: Tue, 19 Mar 2024 10:41:02 -0400 Subject: [PATCH 11/13] Broke some menu items up into interfaces. Happy with this so far. --- Base/Abstract/IDerivable.cs | 8 +++++++ Base/Abstract/IIntegrable.cs | 8 +++++++ Base/Forms/GraphForm.cs | 37 +++++++---------------------- Base/Graphables/Equation.cs | 10 ++++++-- Base/Graphables/IntegralEquation.cs | 16 +++++++++---- Testing/Program.cs | 3 +++ 6 files changed, 47 insertions(+), 35 deletions(-) create mode 100644 Base/Abstract/IDerivable.cs create mode 100644 Base/Abstract/IIntegrable.cs diff --git a/Base/Abstract/IDerivable.cs b/Base/Abstract/IDerivable.cs new file mode 100644 index 0000000..1d0a7e9 --- /dev/null +++ b/Base/Abstract/IDerivable.cs @@ -0,0 +1,8 @@ +using Graphing.Graphables; + +namespace Graphing.Abstract; + +public interface IDerivable +{ + public Equation Derive(); +} diff --git a/Base/Abstract/IIntegrable.cs b/Base/Abstract/IIntegrable.cs new file mode 100644 index 0000000..1a942a9 --- /dev/null +++ b/Base/Abstract/IIntegrable.cs @@ -0,0 +1,8 @@ +using Graphing.Graphables; + +namespace Graphing.Abstract; + +public interface IIntegrable +{ + public IntegralEquation Integrate(); +} diff --git a/Base/Forms/GraphForm.cs b/Base/Forms/GraphForm.cs index 2ea0c2f..839fb3e 100644 --- a/Base/Forms/GraphForm.cs +++ b/Base/Forms/GraphForm.cs @@ -1,4 +1,5 @@ -using Graphing.Graphables; +using Graphing.Abstract; +using Graphing.Graphables; using Graphing.Parts; using System; using System.Collections.Generic; @@ -362,22 +363,24 @@ public partial class GraphForm : Form colorItem.Click += (o, e) => GraphColorPickerButton_Click(able); MenuColors.DropDownItems.Add(colorItem); - if (able is Equation equ) + if (able is IDerivable derivable) { ToolStripMenuItem derivativeItem = new() { ForeColor = able.Color, Text = able.Name }; - derivativeItem.Click += (o, e) => EquationComputeDerivative_Click(equ); + derivativeItem.Click += (o, e) => Graph(derivable.Derive()); MenuEquationsDerivative.DropDownItems.Add(derivativeItem); - + } + if (able is IIntegrable integrable) + { ToolStripMenuItem integralItem = new() { ForeColor = able.Color, Text = able.Name }; - integralItem.Click += (o, e) => EquationComputeIntegral_Click(equ); + integralItem.Click += (o, e) => Graph(integrable.Integrate()); MenuEquationsIntegral.DropDownItems.Add(integralItem); } } @@ -409,30 +412,6 @@ public partial class GraphForm : Form Size = initialWindowSize; WindowState = FormWindowState.Normal; } - private void EquationComputeDerivative_Click(Equation equation) - { - EquationDelegate equ = equation.GetDelegate(); - string oldName = equation.Name, newName; - if (oldName.StartsWith("Derivative of ")) newName = "Second Derivative of " + oldName[14..]; - else if (oldName.StartsWith("Second Derivative of ")) newName = "Third Derivative of " + oldName[21..]; - else newName = "Derivative of " + oldName; - // TODO: anti-integrate (maybe). - - Graph(new Equation(DerivativeAtPoint(equ)) - { - Name = newName - }); - - static EquationDelegate DerivativeAtPoint(EquationDelegate e) - { - const double step = 1e-3; - return x => (e(x + step) - e(x)) / step; - } - } - private void EquationComputeIntegral_Click(Equation equation) - { - Graph(equation.Integrate()); - } private void MenuMiscCaches_Click(object? sender, EventArgs e) { diff --git a/Base/Graphables/Equation.cs b/Base/Graphables/Equation.cs index fa8859a..a32b961 100644 --- a/Base/Graphables/Equation.cs +++ b/Base/Graphables/Equation.cs @@ -1,11 +1,12 @@ -using Graphing.Forms; +using Graphing.Abstract; +using Graphing.Forms; using Graphing.Parts; using System; using System.Collections.Generic; namespace Graphing.Graphables; -public class Equation : Graphable +public class Equation : Graphable, IIntegrable, IDerivable { private static int equationNum; @@ -48,6 +49,11 @@ public class Equation : Graphable return lines; } + public Equation Derive() => new(x => + { + const double step = 1e-3; + return (equ(x + step) - equ(x)) / step; + }); public IntegralEquation Integrate() => new(this); public EquationDelegate GetDelegate() => equ; diff --git a/Base/Graphables/IntegralEquation.cs b/Base/Graphables/IntegralEquation.cs index 52ff821..0441800 100644 --- a/Base/Graphables/IntegralEquation.cs +++ b/Base/Graphables/IntegralEquation.cs @@ -1,11 +1,12 @@ -using Graphing.Forms; +using Graphing.Abstract; +using Graphing.Forms; using Graphing.Parts; using System; using System.Collections.Generic; namespace Graphing.Graphables; -public class IntegralEquation : Graphable +public class IntegralEquation : Graphable, IIntegrable, IDerivable { protected readonly Equation baseEqu; protected readonly EquationDelegate baseEquDel; @@ -113,11 +114,18 @@ public class IntegralEquation : Graphable return lines; } - public Equation AsEquation() => new(GetIntegralAtPoint); + public Equation AsEquation() => new(IntegralAtPoint) + { + Name = Name, + Color = Color + }; + + public Equation Derive() => (Equation)baseEqu.DeepCopy(); + public IntegralEquation Integrate() => AsEquation().Integrate(); // Standard integral method. // Inefficient for successive calls. - public double GetIntegralAtPoint(double x) + public double IntegralAtPoint(double x) { EquationDelegate equ = baseEqu.GetDelegate(); diff --git a/Testing/Program.cs b/Testing/Program.cs index 036d4ee..9638860 100644 --- a/Testing/Program.cs +++ b/Testing/Program.cs @@ -23,6 +23,9 @@ internal static class Program // Keep in mind this uses more memory than usual and can take // some time. + // Integrating equations is now much smoother and less intensive. + // Try it out! + Application.Run(graph); } } -- 2.49.0.windows.1 From 9b4905233cc70e9e4df84778a3ae3b14bcb34e9d Mon Sep 17 00:00:00 2001 From: That_One_Nerd Date: Tue, 19 Mar 2024 12:34:31 -0400 Subject: [PATCH 12/13] 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); } } -- 2.49.0.windows.1 From f8c1788502816b92239a07a9157e815d18679721 Mon Sep 17 00:00:00 2001 From: That_One_Nerd Date: Wed, 20 Mar 2024 08:35:16 -0400 Subject: [PATCH 13/13] About ready for 1.2. --- .gitignore | 1 + Base/Base.csproj | 6 +- Base/Forms/GraphColorPickerForm.Designer.cs | 24 ++++---- Base/Graphables/IntegralEquation.cs | 59 +++++++++++-------- .../PublishProfiles/FolderProfile.pubxml.user | 2 +- README.md | 3 +- Testing/Program.cs | 12 ++-- 7 files changed, 59 insertions(+), 48 deletions(-) diff --git a/.gitignore b/.gitignore index 9b828e4..9b96ea0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .vs/ +.github/ Base/obj/ Base/bin/ diff --git a/Base/Base.csproj b/Base/Base.csproj index 63d9cae..2301302 100644 --- a/Base/Base.csproj +++ b/Base/Base.csproj @@ -12,18 +12,18 @@ True ThatOneNerd.Graphing ThatOneNerd.Graphing - 1.1.0 + 1.2.0 That_One_Nerd A fairly adept graphing calculator made in Windows Forms. MIT https://github.com/That-One-Nerd/Graphing README.md - graphing;graph;plot;math;calculus;visual;desmos + graphing;graph;plot;math;calculus;visual;desmos;slope field;slopefield;equation;visualizer MIT True snupkg View the GitHub release for the changelog: -https://github.com/That-One-Nerd/Graphing/releases/tag/1.1.0 +https://github.com/That-One-Nerd/Graphing/releases/tag/1.2.0 diff --git a/Base/Forms/GraphColorPickerForm.Designer.cs b/Base/Forms/GraphColorPickerForm.Designer.cs index 8b708a4..4bca829 100644 --- a/Base/Forms/GraphColorPickerForm.Designer.cs +++ b/Base/Forms/GraphColorPickerForm.Designer.cs @@ -43,7 +43,7 @@ namespace Graphing.Forms ResultView = new Panel(); BottomPanel = new Panel(); OkButton = new Button(); - CancelButton = new Button(); + CancellingButton = new Button(); RgbSliders.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)BlueTrackBar).BeginInit(); ((System.ComponentModel.ISupportInitialize)RedTrackBar).BeginInit(); @@ -172,7 +172,7 @@ namespace Graphing.Forms // BottomPanel.BackColor = SystemColors.Window; BottomPanel.Controls.Add(OkButton); - BottomPanel.Controls.Add(CancelButton); + BottomPanel.Controls.Add(CancellingButton); BottomPanel.Dock = DockStyle.Bottom; BottomPanel.Location = new Point(0, 517); BottomPanel.Margin = new Padding(0); @@ -194,15 +194,15 @@ namespace Graphing.Forms // // CancelButton // - CancelButton.Anchor = AnchorStyles.Right; - CancelButton.Location = new Point(384, 9); - CancelButton.Margin = new Padding(0); - CancelButton.Name = "CancelButton"; - CancelButton.Size = new Size(150, 46); - CancelButton.TabIndex = 0; - CancelButton.Text = "Cancel"; - CancelButton.UseVisualStyleBackColor = true; - CancelButton.Click += CancelButton_Click; + CancellingButton.Anchor = AnchorStyles.Right; + CancellingButton.Location = new Point(384, 9); + CancellingButton.Margin = new Padding(0); + CancellingButton.Name = "CancelButton"; + CancellingButton.Size = new Size(150, 46); + CancellingButton.TabIndex = 0; + CancellingButton.Text = "Cancel"; + CancellingButton.UseVisualStyleBackColor = true; + CancellingButton.Click += CancelButton_Click; // // GraphColorPickerForm // @@ -237,7 +237,7 @@ namespace Graphing.Forms private TrackBar BlueTrackBar; private TrackBar RedTrackBar; private Panel BottomPanel; - private Button CancelButton; + private Button CancellingButton; private Button OkButton; private TextBox RedValueBox; private TextBox BlueValueBox; diff --git a/Base/Graphables/IntegralEquation.cs b/Base/Graphables/IntegralEquation.cs index 5abd5a4..51742fd 100644 --- a/Base/Graphables/IntegralEquation.cs +++ b/Base/Graphables/IntegralEquation.cs @@ -63,7 +63,7 @@ public class IntegralEquation : Graphable, IIntegrable, IDerivable // Origin is off the left side of the screen. // Get to the left side from the origin. double start = graph.MinVisibleGraph.x, end = graph.MaxVisibleGraph.x; - SetInternalStepper(start, epsilon, null); + SetInternalStepper(start, epsilon); // Now we can start. double previousX = stepX; @@ -82,7 +82,7 @@ public class IntegralEquation : Graphable, IIntegrable, IDerivable // Origin is off the right side of the screen. // Get to the right side of the origin. double start = graph.MaxVisibleGraph.x, end = graph.MinVisibleGraph.x; - SetInternalStepper(start, epsilon, null); + SetInternalStepper(start, epsilon); // Now we can start. double previousX = stepX; @@ -103,7 +103,7 @@ public class IntegralEquation : Graphable, IIntegrable, IDerivable // Start with right. double start = 0, end = graph.MaxVisibleGraph.x; - SetInternalStepper(start, epsilon, null); + SetInternalStepper(start, epsilon); double previousX = stepX; double previousY = stepY; @@ -119,7 +119,7 @@ public class IntegralEquation : Graphable, IIntegrable, IDerivable // Now do left. start = 0; end = graph.MinVisibleGraph.x; - SetInternalStepper(start, epsilon, null); + SetInternalStepper(start, epsilon); previousX = stepX; previousY = stepY; @@ -139,27 +139,19 @@ public class IntegralEquation : Graphable, IIntegrable, IDerivable private double stepX = 0; private double stepY = 0; - private void SetInternalStepper(double x, double dX, Action? stepCallback) + private void SetInternalStepper(double x, double dX) { stepX = 0; stepY = 0; - if (usingAlt) altBaseEqu!.SetInternalStepper(0, dX, null); + if (usingAlt) altBaseEqu!.SetInternalStepper(0, dX); if (x > 0) { - while (stepX < x) - { - MoveInternalStepper(dX); - stepCallback?.Invoke(stepX, stepY); - } + while (stepX < x) MoveInternalStepper(dX); } else if (x < 0) { - while (x < stepX) - { - MoveInternalStepper(-dX); - stepCallback?.Invoke(stepX, stepY); - } + while (x < stepX) MoveInternalStepper(-dX); } } private void MoveInternalStepper(double dX) @@ -195,16 +187,37 @@ public class IntegralEquation : Graphable, IIntegrable, IDerivable // Inefficient for successive calls. public double IntegralAtPoint(double x) { - EquationDelegate equ = baseEqu.GetDelegate(); + if (x > 0) + { + double start = Math.Min(0, x), end = Math.Max(0, x); + const double step = 1e-3; + double sum = 0; - double start = Math.Min(0, x), end = Math.Max(0, x); - const double step = 1e-3; - double sum = 0; + SetInternalStepper(start, step); + for (double t = start; t <= end; t += step) + { + MoveInternalStepper(step); + sum += stepY * step; + } - for (double t = start; t <= end; t += step) sum += equ(t) * step; - if (x < 0) sum = -sum; + return sum; + } + else if (x < 0) + { + double start = Math.Max(0, x), end = Math.Min(0, x); + const double step = 1e-3; + double sum = 0; - return sum; + SetInternalStepper(start, step); + for (double t = start; t >= end; t -= step) + { + MoveInternalStepper(-step); + sum -= stepY * step; + } + + return sum; + } + else return 0; } public override bool ShouldSelectGraphable(in GraphForm graph, Float2 graphMousePos, double factor) diff --git a/Base/Properties/PublishProfiles/FolderProfile.pubxml.user b/Base/Properties/PublishProfiles/FolderProfile.pubxml.user index 083b367..706348e 100644 --- a/Base/Properties/PublishProfiles/FolderProfile.pubxml.user +++ b/Base/Properties/PublishProfiles/FolderProfile.pubxml.user @@ -4,7 +4,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121. --> - True|2024-03-13T14:31:43.4569441Z;False|2024-03-13T10:30:01.4347009-04:00;False|2024-03-13T10:27:31.9554551-04:00; + True|2024-03-20T12:39:01.6402921Z;True|2024-03-13T10:31:43.4569441-04:00;False|2024-03-13T10:30:01.4347009-04:00;False|2024-03-13T10:27:31.9554551-04:00; \ No newline at end of file diff --git a/README.md b/README.md index 8ba0c76..ac7ed2b 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ This is a graphing calculator I made initially for a Calculus project in a day o Currently, it doesn't have a whole lot of features, but I'll be adding more in the future. Here's currently what it can do: - Graph an equation (duh). - There are currently some rendering issues with asymptotes which will be focused on at some point. +- Integrate and derive equations. - Graph a slope field of a `dy/dx =` style equation. - View a tangent line of an equation. - Display a vertical bar graph. @@ -74,7 +75,7 @@ An equation requires a delegate such as the one you see. Alternatively, you can graph.Graph(new Equation(x => Math.Pow(2, x)) { Color = Color.Green, - Name = "2^x" + Name = "Exponential Base 2" }); ``` diff --git a/Testing/Program.cs b/Testing/Program.cs index e70142d..851e7f8 100644 --- a/Testing/Program.cs +++ b/Testing/Program.cs @@ -21,14 +21,10 @@ internal static class Program 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 - // some time. - - // Integrating equations is now much smoother and less intensive. - // Try it out! - - // You can click and drag on an equation to select specific points. + // Now, when integrating equations, the result is much less jagged + // and much faster. Try it out! You can also select points along + // equations and such as well. Click on an equation to see for + // yourself! Application.Run(graph); } -- 2.49.0.windows.1