Version 1.2 is ready.

This commit is contained in:
That_One_Nerd 2024-03-21 12:37:35 -04:00 committed by GitHub
commit a4e9ae3aa5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
31 changed files with 727 additions and 179 deletions

1
.gitignore vendored
View File

@ -1,4 +1,5 @@
.vs/
.github/
Base/obj/
Base/bin/

View File

@ -0,0 +1,8 @@
using Graphing.Graphables;
namespace Graphing.Abstract;
public interface IDerivable
{
public Graphable Derive();
}

View File

@ -0,0 +1,8 @@
using Graphing.Graphables;
namespace Graphing.Abstract;
public interface IIntegrable
{
public Graphable Integrate();
}

View File

@ -5,25 +5,25 @@
<TargetFramework>net8.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<UseWindowsForms>true</UseWindowsForms>
<ImplicitUsings>enable</ImplicitUsings>
<ImplicitUsings>disable</ImplicitUsings>
<RootNamespace>Graphing</RootNamespace>
<AssemblyName>ThatOneNerd.Graphing</AssemblyName>
<ProduceReferenceAssembly>True</ProduceReferenceAssembly>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<PackageId>ThatOneNerd.Graphing</PackageId>
<Title>ThatOneNerd.Graphing</Title>
<Version>1.1.0</Version>
<Version>1.2.0</Version>
<Authors>That_One_Nerd</Authors>
<Description>A fairly adept graphing calculator made in Windows Forms.</Description>
<Copyright>MIT</Copyright>
<RepositoryUrl>https://github.com/That-One-Nerd/Graphing</RepositoryUrl>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageTags>graphing;graph;plot;math;calculus;visual;desmos</PackageTags>
<PackageTags>graphing;graph;plot;math;calculus;visual;desmos;slope field;slopefield;equation;visualizer</PackageTags>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<IncludeSymbols>True</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<PackageReleaseNotes>View the GitHub release for the changelog:
https://github.com/That-One-Nerd/Graphing/releases/tag/1.1.0</PackageReleaseNotes>
https://github.com/That-One-Nerd/Graphing/releases/tag/1.2.0</PackageReleaseNotes>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">

View File

@ -20,4 +20,9 @@
<SubType>Form</SubType>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Forms\ViewCacheForm.resx">
<SubType>Designer</SubType>
</EmbeddedResource>
</ItemGroup>
</Project>

View File

@ -1,4 +1,6 @@
namespace Graphing;
using System.Drawing;
namespace Graphing;
public record struct Float2
{

View File

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

View File

@ -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;
@ -6,12 +10,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();
}
@ -40,20 +50,25 @@ public partial class PieChart : UserControl
current += item.value;
}
// Draw the outline.
Pen outlinePartsPen = new(Color.FromArgb(unchecked((int)0xFF_202020)), 3);
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
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);
}
}

View File

@ -1,4 +1,7 @@
namespace Graphing.Forms
using System.Drawing;
using System.Windows.Forms;
namespace Graphing.Forms
{
partial class GraphColorPickerForm
{
@ -40,7 +43,7 @@
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();
@ -169,7 +172,7 @@
//
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);
@ -191,15 +194,15 @@
//
// 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
//
@ -234,7 +237,7 @@
private TrackBar BlueTrackBar;
private TrackBar RedTrackBar;
private Panel BottomPanel;
private Button CancelButton;
private Button CancellingButton;
private Button OkButton;
private TextBox RedValueBox;
private TextBox BlueValueBox;

View File

@ -1,4 +1,8 @@
namespace Graphing.Forms;
using System;
using System.Drawing;
using System.Windows.Forms;
namespace Graphing.Forms;
public partial class GraphColorPickerForm : Form
{
@ -37,7 +41,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)
{

View File

@ -1,4 +1,7 @@
namespace Graphing.Forms
using System.Drawing;
using System.Windows.Forms;
namespace Graphing.Forms
{
partial class GraphForm
{
@ -41,6 +44,7 @@
MenuEquationsIntegral = new ToolStripMenuItem();
MenuMisc = new ToolStripMenuItem();
MenuMiscCaches = new ToolStripMenuItem();
MiscMenuPreload = new ToolStripMenuItem();
GraphMenu.SuspendLayout();
SuspendLayout();
//
@ -129,7 +133,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 +145,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 +183,6 @@
private ToolStripMenuItem MenuEquationsIntegral;
private ToolStripMenuItem MenuMisc;
private ToolStripMenuItem MenuMiscCaches;
private ToolStripMenuItem MiscMenuPreload;
}
}

View File

@ -1,7 +1,11 @@
using Graphing.Extensions;
using Graphing.Graphables;
using Graphing.Abstract;
using Graphing.Parts;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Text;
using System.Linq;
using System.Windows.Forms;
namespace Graphing.Forms;
@ -10,10 +14,13 @@ 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; }
public float DpiFloat { get; private set; }
public double ZoomLevel
{
get => _zoomLevel;
@ -21,7 +28,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();
@ -57,6 +64,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 +112,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 +129,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 +145,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)),
@ -146,6 +156,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)
{
@ -156,13 +204,47 @@ public partial class GraphForm : Form
g.FillRectangle(background, e.ClipRectangle);
PaintGrid(g);
PaintUnits(g);
Point clientMousePos = PointToClient(Cursor.Position);
Float2 graphMousePos = ScreenSpaceToGraphSpace(new(clientMousePos.X,
clientMousePos.Y));
// Draw the actual graphs.
Pen[] graphPens = new Pen[ables.Count];
for (int i = 0; i < ables.Count; i++)
{
IEnumerable<IGraphPart> 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);
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)
{
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);
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]);
}
}
}
base.OnPaint(e);
@ -183,11 +265,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)
{
@ -198,9 +297,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)
{
@ -213,6 +313,7 @@ public partial class GraphForm : Form
initialScreenCenter.y + graphDiff.y);
Invalidate(false);
}
else if (ableDrag) Invalidate(false);
}
protected override void OnMouseWheel(MouseEventArgs e)
{
@ -226,7 +327,6 @@ public partial class GraphForm : Form
ZoomLevel = 1;
Invalidate(false);
}
private void GraphColorPickerButton_Click(Graphable able)
{
GraphColorPickerForm picker = new(this, able)
@ -235,6 +335,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();
}
@ -255,22 +362,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);
}
}
@ -286,19 +395,16 @@ 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;
@ -306,63 +412,6 @@ public partial class GraphForm : Form
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)
{
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;
}
}
private void MenuMiscCaches_Click(object? sender, EventArgs e)
{
ViewCacheForm cacheForm = new(this)
@ -372,6 +421,29 @@ 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();
}
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; // Screen + 75%
Float2 xRange = new(min.x - add.x, max.x + add.x),
yRange = new(min.y - add.y, max.y + add.y);
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);
}
}

View File

@ -1,4 +1,7 @@
namespace Graphing.Forms
using System.Drawing;
using System.Windows.Forms;
namespace Graphing.Forms
{
partial class SetZoomForm
{

View File

@ -1,4 +1,7 @@
namespace Graphing.Forms;
using System;
using System.Windows.Forms;
namespace Graphing.Forms;
public partial class SetZoomForm : Form
{

View File

@ -1,4 +1,7 @@
namespace Graphing.Forms
using System.Drawing;
using System.Windows.Forms;
namespace Graphing.Forms
{
partial class ViewCacheForm
{

View File

@ -1,4 +1,8 @@
using Graphing.Extensions;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
namespace Graphing.Forms;
@ -32,6 +36,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 +53,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 +67,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);

View File

@ -1,5 +1,6 @@
using Graphing.Forms;
using Graphing.Parts;
using System.Collections.Generic;
using System.Drawing;
namespace Graphing;
@ -8,12 +9,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; }
@ -31,6 +32,10 @@ public abstract class Graphable
public abstract Graphable DeepCopy();
public abstract void EraseCache();
public abstract long GetCacheBytes();
public virtual void EraseCache() { }
public virtual long GetCacheBytes() => 0;
public virtual void Preload(Float2 xRange, Float2 yRange, double step) { }
public virtual bool ShouldSelectGraphable(in GraphForm graph, Float2 graphMousePos, double factor) => false;
public virtual Float2 GetSelectedPoint(in GraphForm graph, Float2 graphMousePos) => default;
}

View File

@ -1,5 +1,8 @@
using Graphing.Forms;
using Graphing.Parts;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Graphing.Graphables;
@ -32,7 +35,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());
@ -48,4 +50,7 @@ public class ColumnTable : Graphable
return items;
}
// Nothing to preload, everything is already cached.
public override void Preload(Float2 xRange, Float2 yRange, double step) { }
}

View File

@ -1,9 +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;
@ -22,15 +25,17 @@ public class Equation : Graphable
public override IEnumerable<IGraphPart> 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<IGraphPart> 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);
@ -44,6 +49,13 @@ public class Equation : Graphable
return lines;
}
public Graphable Derive() => new Equation(x =>
{
const double step = 1e-3;
return (equ(x + step) - equ(x)) / step;
});
public Graphable Integrate() => new IntegralEquation(this);
public EquationDelegate GetDelegate() => equ;
public override void EraseCache() => cache.Clear();
@ -59,8 +71,6 @@ public class Equation : Graphable
}
}
// 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);
@ -96,6 +106,28 @@ 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) =>
new(graphMousePos.x, GetFromCache(graphMousePos.x, 1e-3));
public override void Preload(Float2 xRange, Float2 yRange, double step)
{
for (double x = xRange.x; x <= xRange.y; x += step) GetFromCache(x, step);
}
}
public delegate double EquationDelegate(double x);

View File

@ -0,0 +1,239 @@
using Graphing.Abstract;
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 IntegralEquation? altBaseEqu;
protected readonly bool usingAlt;
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();
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(this);
public override IEnumerable<IGraphPart> 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<IGraphPart> 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 start = graph.MinVisibleGraph.x, end = graph.MaxVisibleGraph.x;
SetInternalStepper(start, epsilon);
// Now we can start.
double previousX = stepX;
double previousY = stepY;
for (double x = start; x <= end; x += epsilon)
{
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 start = graph.MaxVisibleGraph.x, end = graph.MinVisibleGraph.x;
SetInternalStepper(start, epsilon);
// Now we can start.
double previousX = stepX;
double previousY = stepY;
for (double x = start; x >= end; x -= epsilon)
{
MoveInternalStepper(-epsilon);
lines.Add(new GraphLine(new Float2(previousX, previousY),
new Float2(stepX, stepY)));
previousX = stepX;
previousY = stepY;
}
}
else
{
// Origin is on-screen.
// We need to do two cycles.
// Start with right.
double start = 0, end = graph.MaxVisibleGraph.x;
SetInternalStepper(start, epsilon);
double previousX = stepX;
double previousY = stepY;
for (double x = start; x <= end; x += epsilon)
{
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;
SetInternalStepper(start, epsilon);
previousX = stepX;
previousY = stepY;
for (double x = start; x >= end; x -= epsilon)
{
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)
{
stepX = 0;
stepY = 0;
if (usingAlt) altBaseEqu!.SetInternalStepper(0, dX);
if (x > 0)
{
while (stepX < x) MoveInternalStepper(dX);
}
else if (x < 0)
{
while (x < stepX) MoveInternalStepper(-dX);
}
}
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 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.
public double IntegralAtPoint(double x)
{
if (x > 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;
}
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;
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)
{
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));
}

View File

@ -1,5 +1,7 @@
using Graphing.Forms;
using Graphing.Parts;
using System;
using System.Collections.Generic;
namespace Graphing.Graphables;
@ -74,6 +76,57 @@ 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);
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)
{
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);

View File

@ -1,21 +1,40 @@
using Graphing.Forms;
using Graphing.Parts;
using System;
using System.Collections.Generic;
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; // Private because it has exactly the same functionality as `Position`.
protected readonly Equation parent;
protected readonly EquationDelegate parentEqu;
protected readonly double length;
// X is slope, Y is height.
protected Float2 currentSlope;
// No binary search for this, I want it to be exact.
// Value: X is slope, Y is height.
protected Dictionary<double, Float2> slopeCache;
public TangentLine(double length, double position, Equation parent)
{
Name = $"Tangent Line of {parent.Name}";
slopeCache = [];
parentEqu = parent.GetDelegate();
Position = position;
this.length = length;
@ -24,28 +43,73 @@ public class TangentLine : Graphable
public override IEnumerable<IGraphPart> GetItemsToRender(in GraphForm graph)
{
Float2 point = new(Position, parentEqu(Position));
return [MakeSlopeLine(point, DerivativeAtPoint(Position)),
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 Float2 val)) return val;
const double step = 1e-3;
return (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;
}
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 * 24;
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)
{
// 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);
}
}

View File

@ -1,8 +1,9 @@
using Graphing.Forms;
using System.Drawing;
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);
}

View File

@ -1,4 +1,6 @@
namespace Graphing;
using System.Drawing;
namespace Graphing;
public record struct Int2
{

View File

@ -1,4 +1,5 @@
using Graphing.Forms;
using System.Drawing;
namespace Graphing.Parts;
@ -18,15 +19,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);
}
}

View File

@ -1,4 +1,5 @@
using Graphing.Forms;
using System.Drawing;
namespace Graphing.Parts;
@ -25,7 +26,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 +41,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));
}
}

View File

@ -1,4 +1,5 @@
using Graphing.Forms;
using System.Drawing;
namespace Graphing.Parts;
@ -18,14 +19,16 @@ 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;
int rad = (int)(form.DpiFloat * radius / 192);
Int2 centerPix = form.GraphSpaceToScreenSpace(center);
g.FillEllipse(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)));
}
}

View File

@ -4,7 +4,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
-->
<Project>
<PropertyGroup>
<History>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;</History>
<History>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;</History>
<LastFailureDetails />
</PropertyGroup>
</Project>

View File

@ -5,7 +5,12 @@ 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.
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.
@ -70,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"
});
```

View File

@ -1,5 +1,7 @@
using Graphing.Forms;
using Graphing.Graphables;
using System;
using System.Windows.Forms;
namespace Graphing.Testing;
@ -10,31 +12,19 @@ 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.
Equation equ = new(Math.Sin);
SlopeField sf = new(2, (x, y) => Math.Cos(x));
TangentLine tl = new(2, 2, equ);
graph.Graph(equ, sf, tl);
// 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 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);
// You can also now view and reset caches in the UI by going to
// Misc > View Caches.
// 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);
}

View File

@ -4,7 +4,7 @@
<TargetFramework>net8.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<UseWindowsForms>true</UseWindowsForms>
<ImplicitUsings>enable</ImplicitUsings>
<ImplicitUsings>disable</ImplicitUsings>
<RootNamespace>Graphing.Testing</RootNamespace>
<AssemblyName>ThatOneNerd.Graphing.Testing</AssemblyName>
</PropertyGroup>