Initial commit. Version 1.0 soon.

This commit is contained in:
That_One_Nerd 2024-02-27 14:49:00 -05:00
commit 8d5e2257fc
21 changed files with 1861 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
.vs/
Base/obj/
Base/bin/

12
Base/Base.csproj Normal file
View File

@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<UseWindowsForms>true</UseWindowsForms>
<ImplicitUsings>enable</ImplicitUsings>
<RootNamespace>Graphing</RootNamespace>
</PropertyGroup>
</Project>

14
Base/Base.csproj.user Normal file
View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Compile Update="Forms\GraphColorPickerForm.cs">
<SubType>Form</SubType>
</Compile>
<Compile Update="Forms\GraphForm.cs">
<SubType>Form</SubType>
</Compile>
<Compile Update="Forms\SetZoomForm.cs">
<SubType>Form</SubType>
</Compile>
</ItemGroup>
</Project>

20
Base/Float2.cs Normal file
View File

@ -0,0 +1,20 @@
namespace Graphing;
public record struct Float2
{
public double x;
public double y;
public Float2()
{
x = 0;
y = 0;
}
public Float2(double x, double y)
{
this.x = x;
this.y = y;
}
public static implicit operator PointF(Float2 v) => new((float)v.x, (float)v.y);
}

View File

@ -0,0 +1,243 @@
namespace Graphing.Forms
{
partial class GraphColorPickerForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
MessageLabel = new Label();
PresetButtonPanel = new Panel();
RgbSliders = new Panel();
BlueValueBox = new TextBox();
GreenValueBox = new TextBox();
RedValueBox = new TextBox();
BlueTrackBar = new TrackBar();
RedTrackBar = new TrackBar();
GreenTrackBar = new TrackBar();
ResultView = new Panel();
BottomPanel = new Panel();
OkButton = new Button();
CancelButton = new Button();
RgbSliders.SuspendLayout();
((System.ComponentModel.ISupportInitialize)BlueTrackBar).BeginInit();
((System.ComponentModel.ISupportInitialize)RedTrackBar).BeginInit();
((System.ComponentModel.ISupportInitialize)GreenTrackBar).BeginInit();
BottomPanel.SuspendLayout();
SuspendLayout();
//
// MessageLabel
//
MessageLabel.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right;
MessageLabel.AutoEllipsis = true;
MessageLabel.Location = new Point(84, 28);
MessageLabel.Margin = new Padding(75, 28, 75, 28);
MessageLabel.Name = "MessageLabel";
MessageLabel.Size = new Size(375, 101);
MessageLabel.TabIndex = 0;
MessageLabel.Text = "Pick a color for GRAPHABLE NAME HERE";
MessageLabel.TextAlign = ContentAlignment.MiddleCenter;
//
// PresetButtonPanel
//
PresetButtonPanel.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left;
PresetButtonPanel.AutoScroll = true;
PresetButtonPanel.Location = new Point(12, 160);
PresetButtonPanel.Name = "PresetButtonPanel";
PresetButtonPanel.Size = new Size(114, 334);
PresetButtonPanel.TabIndex = 1;
//
// RgbSliders
//
RgbSliders.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
RgbSliders.Controls.Add(BlueValueBox);
RgbSliders.Controls.Add(GreenValueBox);
RgbSliders.Controls.Add(RedValueBox);
RgbSliders.Controls.Add(BlueTrackBar);
RgbSliders.Controls.Add(RedTrackBar);
RgbSliders.Controls.Add(GreenTrackBar);
RgbSliders.Controls.Add(ResultView);
RgbSliders.Location = new Point(152, 160);
RgbSliders.Name = "RgbSliders";
RgbSliders.Size = new Size(379, 334);
RgbSliders.TabIndex = 2;
//
// BlueValueBox
//
BlueValueBox.Anchor = AnchorStyles.Right;
BlueValueBox.Location = new Point(154, 257);
BlueValueBox.MaxLength = 3;
BlueValueBox.Name = "BlueValueBox";
BlueValueBox.Size = new Size(48, 39);
BlueValueBox.TabIndex = 6;
BlueValueBox.TextAlign = HorizontalAlignment.Right;
BlueValueBox.TextChanged += BlueValueBox_TextChanged;
//
// GreenValueBox
//
GreenValueBox.Anchor = AnchorStyles.Right;
GreenValueBox.Location = new Point(154, 167);
GreenValueBox.MaxLength = 3;
GreenValueBox.Name = "GreenValueBox";
GreenValueBox.Size = new Size(48, 39);
GreenValueBox.TabIndex = 5;
GreenValueBox.TextAlign = HorizontalAlignment.Right;
GreenValueBox.TextChanged += GreenValueBox_TextChanged;
//
// RedValueBox
//
RedValueBox.Anchor = AnchorStyles.Right;
RedValueBox.Location = new Point(154, 77);
RedValueBox.MaxLength = 3;
RedValueBox.Name = "RedValueBox";
RedValueBox.Size = new Size(48, 39);
RedValueBox.TabIndex = 4;
RedValueBox.Text = "288";
RedValueBox.TextAlign = HorizontalAlignment.Right;
RedValueBox.TextChanged += RedValueBox_TextChanged;
//
// BlueTrackBar
//
BlueTrackBar.Anchor = AnchorStyles.Left | AnchorStyles.Right;
BlueTrackBar.Cursor = Cursors.SizeWE;
BlueTrackBar.LargeChange = 25;
BlueTrackBar.Location = new Point(3, 212);
BlueTrackBar.Maximum = 255;
BlueTrackBar.Name = "BlueTrackBar";
BlueTrackBar.Size = new Size(215, 90);
BlueTrackBar.TabIndex = 3;
BlueTrackBar.TickStyle = TickStyle.None;
BlueTrackBar.Scroll += BlueTrackBar_Scroll;
//
// RedTrackBar
//
RedTrackBar.Anchor = AnchorStyles.Left | AnchorStyles.Right;
RedTrackBar.Cursor = Cursors.SizeWE;
RedTrackBar.LargeChange = 25;
RedTrackBar.Location = new Point(3, 32);
RedTrackBar.Maximum = 255;
RedTrackBar.Name = "RedTrackBar";
RedTrackBar.Size = new Size(215, 90);
RedTrackBar.TabIndex = 2;
RedTrackBar.TickStyle = TickStyle.None;
RedTrackBar.Scroll += RedTrackBar_Scroll;
//
// GreenTrackBar
//
GreenTrackBar.Anchor = AnchorStyles.Left | AnchorStyles.Right;
GreenTrackBar.Cursor = Cursors.SizeWE;
GreenTrackBar.LargeChange = 25;
GreenTrackBar.Location = new Point(3, 122);
GreenTrackBar.Maximum = 255;
GreenTrackBar.Name = "GreenTrackBar";
GreenTrackBar.Size = new Size(215, 90);
GreenTrackBar.TabIndex = 1;
GreenTrackBar.TickStyle = TickStyle.None;
GreenTrackBar.Scroll += GreenTrackBar_Scroll;
//
// ResultView
//
ResultView.Anchor = AnchorStyles.Right;
ResultView.Location = new Point(224, 105);
ResultView.Name = "ResultView";
ResultView.Size = new Size(125, 125);
ResultView.TabIndex = 0;
//
// BottomPanel
//
BottomPanel.BackColor = SystemColors.Window;
BottomPanel.Controls.Add(OkButton);
BottomPanel.Controls.Add(CancelButton);
BottomPanel.Dock = DockStyle.Bottom;
BottomPanel.Location = new Point(0, 517);
BottomPanel.Margin = new Padding(0);
BottomPanel.Name = "BottomPanel";
BottomPanel.Size = new Size(543, 64);
BottomPanel.TabIndex = 3;
//
// OkButton
//
OkButton.Anchor = AnchorStyles.Right;
OkButton.Location = new Point(220, 9);
OkButton.Margin = new Padding(0);
OkButton.Name = "OkButton";
OkButton.Size = new Size(150, 46);
OkButton.TabIndex = 1;
OkButton.Text = "OK";
OkButton.UseVisualStyleBackColor = true;
OkButton.Click += OkButton_Click;
//
// 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;
//
// GraphColorPickerForm
//
AutoScaleDimensions = new SizeF(13F, 32F);
AutoScaleMode = AutoScaleMode.Font;
ClientSize = new Size(543, 581);
Controls.Add(BottomPanel);
Controls.Add(RgbSliders);
Controls.Add(PresetButtonPanel);
Controls.Add(MessageLabel);
FormBorderStyle = FormBorderStyle.SizableToolWindow;
MaximumSize = new Size(1000, 1000);
MinimumSize = new Size(450, 577);
Name = "GraphColorPickerForm";
Text = "GraphColorPickerForm";
RgbSliders.ResumeLayout(false);
RgbSliders.PerformLayout();
((System.ComponentModel.ISupportInitialize)BlueTrackBar).EndInit();
((System.ComponentModel.ISupportInitialize)RedTrackBar).EndInit();
((System.ComponentModel.ISupportInitialize)GreenTrackBar).EndInit();
BottomPanel.ResumeLayout(false);
ResumeLayout(false);
}
#endregion
private Label MessageLabel;
private Panel PresetButtonPanel;
private Panel RgbSliders;
private Panel ResultView;
private TrackBar GreenTrackBar;
private TrackBar BlueTrackBar;
private TrackBar RedTrackBar;
private Panel BottomPanel;
private Button CancelButton;
private Button OkButton;
private TextBox RedValueBox;
private TextBox BlueValueBox;
private TextBox GreenValueBox;
}
}

View File

@ -0,0 +1,201 @@
namespace Graphing.Forms;
public partial class GraphColorPickerForm : Form
{
public Color Result
{
get => _result;
set
{
_result = value;
if (able is not null) able.Color = value;
graph?.Invalidate(false);
}
}
private Color _result;
private readonly Color initialColor;
private readonly GraphForm graph;
private readonly Graphable able;
public GraphColorPickerForm(GraphForm graph, Graphable able)
{
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
SetStyle(ControlStyles.AllPaintingInWmPaint, true);
SetStyle(ControlStyles.UserPaint, true);
Result = able.Color;
initialColor = Result;
InitializeComponent();
this.graph = graph;
this.able = able;
Text = $"{able.Name} Color";
MessageLabel.Text = $"Pick a color for {able.Name}.";
// Add preset buttons.
const int size = 48;
int position = 0;
foreach (uint cId in Graphable.DefaultColors)
{
Color color = Color.FromArgb((int)cId);
Button button = new()
{
BackColor = color,
Height = size,
Location = new Point(0, position),
Parent = PresetButtonPanel,
Text = "",
Width = PresetButtonPanel.Width,
};
button.Click += (o, e) => SetPresetColor(button.BackColor);
position += size;
}
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
UpdateResult(false);
}
private void SetPresetColor(Color preset)
{
Result = preset;
UpdateResult(true);
}
private void UpdateResult(bool invalidate)
{
RedTrackBar.Value = Result.R;
GreenTrackBar.Value = Result.G;
BlueTrackBar.Value = Result.B;
ResultView.BackColor = Result;
RedValueBox.Text = Result.R.ToString();
GreenValueBox.Text = Result.G.ToString();
BlueValueBox.Text = Result.B.ToString();
if (invalidate) Invalidate(true);
}
private void UpdateColorFromTrackBars()
{
int a = 0xEF, r = RedTrackBar.Value, g = GreenTrackBar.Value, b = BlueTrackBar.Value;
Result = Color.FromArgb(b + (g << 8) + (r << 16) + (a << 24));
ResultView.BackColor = Result;
RedValueBox.Text = Result.R.ToString();
GreenValueBox.Text = Result.G.ToString();
BlueValueBox.Text = Result.B.ToString();
ResultView.Invalidate();
}
private void RedTrackBar_Scroll(object? sender, EventArgs e)
{
UpdateColorFromTrackBars();
}
private void GreenTrackBar_Scroll(object? sender, EventArgs e)
{
UpdateColorFromTrackBars();
}
private void BlueTrackBar_Scroll(object? sender, EventArgs e)
{
UpdateColorFromTrackBars();
}
private void CancelButton_Click(object? sender, EventArgs e)
{
Result = initialColor;
UpdateResult(true);
Close();
}
private void OkButton_Click(object? sender, EventArgs e)
{
Close();
}
private void RedValueBox_TextChanged(object? sender, EventArgs e)
{
int original = RedTrackBar.Value;
try
{
int value;
if (string.IsNullOrWhiteSpace(RedValueBox.Text))
{
return;
}
else
{
value = int.Parse(RedValueBox.Text);
if (value < 0 || value > 255) throw new();
}
RedTrackBar.Value = value;
}
catch
{
RedTrackBar.Value = original;
RedValueBox.Text = RedTrackBar.Value.ToString();
}
UpdateColorFromTrackBars();
}
private void GreenValueBox_TextChanged(object? sender, EventArgs e)
{
int original = GreenTrackBar.Value;
try
{
int value;
if (string.IsNullOrWhiteSpace(GreenValueBox.Text))
{
value = 0;
}
else
{
value = int.Parse(GreenValueBox.Text);
if (value < 0 || value > 255) throw new();
}
GreenTrackBar.Value = value;
}
catch
{
GreenTrackBar.Value = original;
GreenValueBox.Text = GreenTrackBar.Value.ToString();
}
UpdateColorFromTrackBars();
}
private void BlueValueBox_TextChanged(object sender, EventArgs e)
{
int original = BlueTrackBar.Value;
try
{
int value;
if (string.IsNullOrWhiteSpace(BlueValueBox.Text))
{
value = 0;
}
else
{
value = int.Parse(BlueValueBox.Text);
if (value < 0 || value > 255) throw new();
}
BlueTrackBar.Value = value;
}
catch
{
BlueTrackBar.Value = original;
BlueValueBox.Text = BlueTrackBar.Value.ToString();
}
UpdateColorFromTrackBars();
}
}

View File

@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

158
Base/Forms/GraphForm.Designer.cs generated Normal file
View File

@ -0,0 +1,158 @@
namespace Graphing.Forms
{
partial class GraphForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
ResetViewportButton = new Button();
GraphMenu = new MenuStrip();
MenuViewport = new ToolStripMenuItem();
ButtonViewportSetZoom = new ToolStripMenuItem();
ButtonViewportSetCenter = new ToolStripMenuItem();
ButtonViewportReset = new ToolStripMenuItem();
ButtonViewportResetWindow = new ToolStripMenuItem();
MenuColors = new ToolStripMenuItem();
MenuEquations = new ToolStripMenuItem();
MenuEquationsDerivative = new ToolStripMenuItem();
MenuEquationsIntegral = new ToolStripMenuItem();
GraphMenu.SuspendLayout();
SuspendLayout();
//
// ResetViewportButton
//
ResetViewportButton.Anchor = AnchorStyles.Top | AnchorStyles.Right;
ResetViewportButton.Font = new Font("Segoe UI Emoji", 13.875F, FontStyle.Regular, GraphicsUnit.Point, 0);
ResetViewportButton.Location = new Point(1373, 43);
ResetViewportButton.Name = "ResetViewportButton";
ResetViewportButton.Size = new Size(64, 64);
ResetViewportButton.TabIndex = 0;
ResetViewportButton.Text = "⌂";
ResetViewportButton.TextAlign = ContentAlignment.TopRight;
ResetViewportButton.UseVisualStyleBackColor = true;
ResetViewportButton.Click += ResetViewportButton_Click;
//
// GraphMenu
//
GraphMenu.ImageScalingSize = new Size(32, 32);
GraphMenu.Items.AddRange(new ToolStripItem[] { MenuViewport, MenuColors, MenuEquations });
GraphMenu.Location = new Point(0, 0);
GraphMenu.Name = "GraphMenu";
GraphMenu.Size = new Size(1449, 42);
GraphMenu.TabIndex = 1;
GraphMenu.Text = "menuStrip1";
//
// MenuViewport
//
MenuViewport.DropDownItems.AddRange(new ToolStripItem[] { ButtonViewportSetZoom, ButtonViewportSetCenter, ButtonViewportReset, ButtonViewportResetWindow });
MenuViewport.Name = "MenuViewport";
MenuViewport.Size = new Size(129, 38);
MenuViewport.Text = "Viewport";
//
// ButtonViewportSetZoom
//
ButtonViewportSetZoom.Name = "ButtonViewportSetZoom";
ButtonViewportSetZoom.Size = new Size(350, 44);
ButtonViewportSetZoom.Text = "Set Zoom";
ButtonViewportSetZoom.Click += ButtonViewportSetZoom_Click;
//
// ButtonViewportSetCenter
//
ButtonViewportSetCenter.Name = "ButtonViewportSetCenter";
ButtonViewportSetCenter.Size = new Size(350, 44);
ButtonViewportSetCenter.Text = "Set Center Position";
ButtonViewportSetCenter.Click += ButtonViewportSetCenter_Click;
//
// ButtonViewportReset
//
ButtonViewportReset.Name = "ButtonViewportReset";
ButtonViewportReset.Size = new Size(350, 44);
ButtonViewportReset.Text = "Reset Viewport";
ButtonViewportReset.Click += ButtonViewportReset_Click;
//
// ButtonViewportResetWindow
//
ButtonViewportResetWindow.Name = "ButtonViewportResetWindow";
ButtonViewportResetWindow.Size = new Size(350, 44);
ButtonViewportResetWindow.Text = "Reset Window Size";
ButtonViewportResetWindow.Click += ButtonViewportResetWindow_Click;
//
// MenuColors
//
MenuColors.Name = "MenuColors";
MenuColors.Size = new Size(101, 38);
MenuColors.Text = "Colors";
//
// MenuEquations
//
MenuEquations.DropDownItems.AddRange(new ToolStripItem[] { MenuEquationsDerivative, MenuEquationsIntegral });
MenuEquations.Name = "MenuEquations";
MenuEquations.Size = new Size(138, 38);
MenuEquations.Text = "Equations";
//
// MenuEquationsDerivative
//
MenuEquationsDerivative.Name = "MenuEquationsDerivative";
MenuEquationsDerivative.Size = new Size(360, 44);
MenuEquationsDerivative.Text = "Compute Derivative";
//
// MenuEquationsIntegral
//
MenuEquationsIntegral.Name = "MenuEquationsIntegral";
MenuEquationsIntegral.Size = new Size(360, 44);
MenuEquationsIntegral.Text = "Compute Integral";
//
// GraphForm
//
AutoScaleDimensions = new SizeF(13F, 32F);
AutoScaleMode = AutoScaleMode.Font;
ClientSize = new Size(1449, 907);
Controls.Add(ResetViewportButton);
Controls.Add(GraphMenu);
MainMenuStrip = GraphMenu;
Name = "GraphForm";
Text = "GraphFormBase";
GraphMenu.ResumeLayout(false);
GraphMenu.PerformLayout();
ResumeLayout(false);
PerformLayout();
}
#endregion
private Button ResetViewportButton;
private MenuStrip GraphMenu;
private ToolStripMenuItem MenuColors;
private ToolStripMenuItem MenuViewport;
private ToolStripMenuItem ButtonViewportSetZoom;
private ToolStripMenuItem ButtonViewportSetCenter;
private ToolStripMenuItem ButtonViewportReset;
private ToolStripMenuItem ButtonViewportResetWindow;
private ToolStripMenuItem MenuEquations;
private ToolStripMenuItem MenuEquationsDerivative;
private ToolStripMenuItem MenuEquationsIntegral;
}
}

367
Base/Forms/GraphForm.cs Normal file
View File

@ -0,0 +1,367 @@
using Graphing.Graphables;
namespace Graphing.Forms;
public partial class GraphForm : Form
{
public Float2 ScreenCenter { get; private set; }
public Float2 Dpi { get; private set; }
public double ZoomLevel
{
get => _zoomLevel;
set
{
double oldZoom = ZoomLevel;
_zoomLevel = Math.Clamp(value, 1e-2, 1e3);
int totalSegments = 0;
foreach (Graphable able in ables) totalSegments += able.GetItemsToRender(this).Count();
if (totalSegments > 10_000)
{
_zoomLevel = oldZoom;
return; // Too many segments, stop.
}
}
}
private double _zoomLevel;
private readonly Point initialWindowPos;
private readonly Size initialWindowSize;
public Graphable[] Graphables => ables.ToArray();
public Float2 MinVisibleGraph => ScreenSpaceToGraphSpace(new Int2(0, ClientRectangle.Height));
public Float2 MaxVisibleGraph => ScreenSpaceToGraphSpace(new Int2(ClientRectangle.Width, 0));
private readonly List<Graphable> ables;
public GraphForm(string title)
{
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
SetStyle(ControlStyles.AllPaintingInWmPaint, true);
SetStyle(ControlStyles.UserPaint, true);
InitializeComponent();
Text = title;
Graphics tempG = CreateGraphics();
Dpi = new(tempG.DpiX, tempG.DpiY);
tempG.Dispose();
ables = [];
ZoomLevel = 1;
initialWindowPos = Location;
initialWindowSize = Size;
}
public Int2 GraphSpaceToScreenSpace(Float2 graphPoint)
{
graphPoint.y = -graphPoint.y;
graphPoint.x -= ScreenCenter.x;
graphPoint.y -= ScreenCenter.y;
graphPoint.x *= Dpi.x / ZoomLevel;
graphPoint.y *= Dpi.y / ZoomLevel;
graphPoint.x += ClientRectangle.Width / 2.0;
graphPoint.y += ClientRectangle.Height / 2.0;
return new((int)graphPoint.x, (int)graphPoint.y);
}
public Float2 ScreenSpaceToGraphSpace(Int2 screenPoint)
{
Float2 result = new(screenPoint.x, screenPoint.y);
result.x -= ClientRectangle.Width / 2.0;
result.y -= ClientRectangle.Height / 2.0;
result.x /= Dpi.x / ZoomLevel;
result.y /= Dpi.y / ZoomLevel;
result.x += ScreenCenter.x;
result.y += ScreenCenter.y;
result.y = -result.y;
return result;
}
protected virtual void PaintGrid(Graphics g)
{
double axisScale = Math.Pow(2, Math.Round(Math.Log2(ZoomLevel)));
// Draw horizontal/vertical quarter-axis.
Brush quarterBrush = new SolidBrush(Color.FromArgb(unchecked((int)0xFF_E0E0E0)));
Pen quarterPen = new(quarterBrush, 2);
for (double x = Math.Ceiling(MinVisibleGraph.x * 4 / axisScale) * axisScale / 4; x <= Math.Floor(MaxVisibleGraph.x * 4 / axisScale) * axisScale / 4; x += axisScale / 4)
{
Int2 startPos = GraphSpaceToScreenSpace(new Float2(x, MinVisibleGraph.y)),
endPos = GraphSpaceToScreenSpace(new Float2(x, MaxVisibleGraph.y));
g.DrawLine(quarterPen, startPos, endPos);
}
for (double y = Math.Ceiling(MinVisibleGraph.y * 4 / axisScale) * axisScale / 4; y <= Math.Floor(MaxVisibleGraph.y * 4 / axisScale) * axisScale / 4; y += axisScale / 4)
{
Int2 startPos = GraphSpaceToScreenSpace(new Float2(MinVisibleGraph.x, y)),
endPos = GraphSpaceToScreenSpace(new Float2(MaxVisibleGraph.x, y));
g.DrawLine(quarterPen, startPos, endPos);
}
// Draw horizontal/vertical semi-axis.
Brush semiBrush = new SolidBrush(Color.FromArgb(unchecked((int)0xFF_999999)));
Pen semiPen = new(semiBrush, 2);
for (double x = Math.Ceiling(MinVisibleGraph.x / axisScale) * axisScale; x <= Math.Floor(MaxVisibleGraph.x / axisScale) * axisScale; x += axisScale)
{
Int2 startPos = GraphSpaceToScreenSpace(new Float2(x, MinVisibleGraph.y)),
endPos = GraphSpaceToScreenSpace(new Float2(x, MaxVisibleGraph.y));
g.DrawLine(semiPen, startPos, endPos);
}
for (double y = Math.Ceiling(MinVisibleGraph.y / axisScale) * axisScale; y <= Math.Floor(MaxVisibleGraph.y / axisScale) * axisScale; y += axisScale)
{
Int2 startPos = GraphSpaceToScreenSpace(new Float2(MinVisibleGraph.x, y)),
endPos = GraphSpaceToScreenSpace(new Float2(MaxVisibleGraph.x, y));
g.DrawLine(semiPen, startPos, endPos);
}
Brush mainLineBrush = new SolidBrush(Color.Black);
Pen mainLinePen = new(mainLineBrush, 3);
// Draw the main axis (on top of the semi axis).
Int2 startCenterY = GraphSpaceToScreenSpace(new Float2(0, MinVisibleGraph.y)),
endCenterY = GraphSpaceToScreenSpace(new Float2(0, MaxVisibleGraph.y)),
startCenterX = GraphSpaceToScreenSpace(new Float2(MinVisibleGraph.x, 0)),
endCenterX = GraphSpaceToScreenSpace(new Float2(MaxVisibleGraph.x, 0));
g.DrawLine(mainLinePen, startCenterX, endCenterX);
g.DrawLine(mainLinePen, startCenterY, endCenterY);
}
protected override void OnPaint(PaintEventArgs e)
{
Graphics g = e.Graphics;
Brush background = new SolidBrush(Color.White);
g.FillRectangle(background, e.ClipRectangle);
PaintGrid(g);
// Draw the actual graphs.
for (int i = 0; i < ables.Count; i++)
{
IEnumerable<Line2d> lines = ables[i].GetItemsToRender(this);
Brush graphBrush = new SolidBrush(ables[i].Color);
Pen penBrush = new(graphBrush, 3);
foreach (Line2d l in lines)
{
if (!double.IsNormal(l.a.x) || !double.IsNormal(l.a.y) ||
!double.IsNormal(l.b.x) || !double.IsNormal(l.b.y)) continue;
Int2 start = GraphSpaceToScreenSpace(l.a),
end = GraphSpaceToScreenSpace(l.b);
g.DrawLine(penBrush, start, end);
}
}
base.OnPaint(e);
}
protected override void OnResize(EventArgs e)
{
base.OnResize(e);
Invalidate(false);
}
public void Graph(Graphable able)
{
ables.Add(able);
RegenerateMenuItems();
Invalidate(false);
}
private bool mouseDrag = false;
private Int2 initialMouseLocation;
private Float2 initialScreenCenter;
protected override void OnMouseDown(MouseEventArgs e)
{
mouseDrag = true;
initialMouseLocation = new Int2(Cursor.Position.X, Cursor.Position.Y);
initialScreenCenter = ScreenCenter;
}
protected override void OnMouseUp(MouseEventArgs e)
{
if (mouseDrag)
{
Int2 pixelDiff = new(initialMouseLocation.x - Cursor.Position.X,
initialMouseLocation.y - Cursor.Position.Y);
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;
}
protected override void OnMouseMove(MouseEventArgs e)
{
if (mouseDrag)
{
Int2 pixelDiff = new(initialMouseLocation.x - Cursor.Position.X,
initialMouseLocation.y - Cursor.Position.Y);
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);
}
}
protected override void OnMouseWheel(MouseEventArgs e)
{
ZoomLevel *= 1 - e.Delta * 0.00075; // Zoom factor.
Invalidate(false);
}
private void ResetViewportButton_Click(object? sender, EventArgs e)
{
ScreenCenter = new Float2(0, 0);
ZoomLevel = 1;
Invalidate(false);
}
private void GraphColorPickerButton_Click(Graphable able)
{
GraphColorPickerForm picker = new(this, able)
{
StartPosition = FormStartPosition.Manual
};
picker.Location = new Point(Location.X + ClientRectangle.Width + 10,
Location.Y + (ClientRectangle.Height - picker.ClientRectangle.Height) / 2);
picker.ShowDialog();
RegenerateMenuItems();
}
private void RegenerateMenuItems()
{
MenuColors.DropDownItems.Clear();
MenuEquationsDerivative.DropDownItems.Clear();
MenuEquationsIntegral.DropDownItems.Clear();
foreach (Graphable able in ables)
{
ToolStripMenuItem colorItem = new()
{
ForeColor = able.Color,
Text = able.Name
};
colorItem.Click += (o, e) => GraphColorPickerButton_Click(able);
MenuColors.DropDownItems.Add(colorItem);
if (able is Equation)
{
ToolStripMenuItem derivativeItem = new()
{
ForeColor = able.Color,
Text = able.Name
};
derivativeItem.Click += (o, e) => EquationComputeDerivative_Click((able as Equation)!);
MenuEquationsDerivative.DropDownItems.Add(derivativeItem);
ToolStripMenuItem integralItem = new()
{
ForeColor = able.Color,
Text = able.Name
};
integralItem.Click += (o, e) => EquationComputeIntegral_Click((able as Equation)!);
MenuEquationsIntegral.DropDownItems.Add(integralItem);
}
}
}
private void ButtonViewportSetZoom_Click(object? sender, EventArgs e)
{
SetZoomForm picker = new(this)
{
StartPosition = FormStartPosition.Manual,
};
picker.Location = new Point(Location.X + ClientRectangle.Width + 10,
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();
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-1;
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;
}
}
}

123
Base/Forms/GraphForm.resx Normal file
View File

@ -0,0 +1,123 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<metadata name="GraphMenu.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 17</value>
</metadata>
</root>

117
Base/Forms/SetZoomForm.Designer.cs generated Normal file
View File

@ -0,0 +1,117 @@
namespace Graphing.Forms
{
partial class SetZoomForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
MessageLabel = new Label();
ZoomTrackBar = new TrackBar();
ValueLabel = new Label();
ZoomMinValue = new TextBox();
ZoomMaxValue = new TextBox();
((System.ComponentModel.ISupportInitialize)ZoomTrackBar).BeginInit();
SuspendLayout();
//
// MessageLabel
//
MessageLabel.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right;
MessageLabel.Location = new Point(52, 20);
MessageLabel.Name = "MessageLabel";
MessageLabel.Size = new Size(413, 35);
MessageLabel.TabIndex = 0;
MessageLabel.Text = "Set the zoom level for the graph.";
MessageLabel.TextAlign = ContentAlignment.MiddleCenter;
//
// ZoomTrackBar
//
ZoomTrackBar.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right;
ZoomTrackBar.LargeChange = 1000;
ZoomTrackBar.Location = new Point(12, 127);
ZoomTrackBar.Maximum = 10000;
ZoomTrackBar.Name = "ZoomTrackBar";
ZoomTrackBar.Size = new Size(489, 90);
ZoomTrackBar.TabIndex = 1;
ZoomTrackBar.TickStyle = TickStyle.None;
ZoomTrackBar.Scroll += ZoomTrackBar_Scroll;
//
// ValueLabel
//
ValueLabel.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right;
ValueLabel.Location = new Point(52, 91);
ValueLabel.Name = "ValueLabel";
ValueLabel.Size = new Size(413, 33);
ValueLabel.TabIndex = 2;
ValueLabel.Text = "1.00x";
ValueLabel.TextAlign = ContentAlignment.TopCenter;
//
// ZoomMinValue
//
ZoomMinValue.Location = new Point(12, 178);
ZoomMinValue.Name = "ZoomMinValue";
ZoomMinValue.Size = new Size(83, 39);
ZoomMinValue.TabIndex = 3;
ZoomMinValue.Text = "0.50";
ZoomMinValue.TextChanged += ZoomMinValue_TextChanged;
//
// ZoomMaxValue
//
ZoomMaxValue.Anchor = AnchorStyles.Top | AnchorStyles.Right;
ZoomMaxValue.Location = new Point(418, 178);
ZoomMaxValue.Name = "ZoomMaxValue";
ZoomMaxValue.Size = new Size(83, 39);
ZoomMaxValue.TabIndex = 4;
ZoomMaxValue.Text = "2.00";
ZoomMaxValue.TextAlign = HorizontalAlignment.Right;
ZoomMaxValue.TextChanged += ZoomMaxValue_TextChanged;
//
// SetZoomForm
//
AutoScaleDimensions = new SizeF(13F, 32F);
AutoScaleMode = AutoScaleMode.Font;
ClientSize = new Size(513, 230);
Controls.Add(ZoomMaxValue);
Controls.Add(ZoomMinValue);
Controls.Add(ValueLabel);
Controls.Add(ZoomTrackBar);
Controls.Add(MessageLabel);
FormBorderStyle = FormBorderStyle.FixedToolWindow;
Name = "SetZoomForm";
Text = "Zoom Level";
((System.ComponentModel.ISupportInitialize)ZoomTrackBar).EndInit();
ResumeLayout(false);
PerformLayout();
}
#endregion
private Label MessageLabel;
private TrackBar ZoomTrackBar;
private Label ValueLabel;
private TextBox ZoomMinValue;
private TextBox ZoomMaxValue;
}
}

131
Base/Forms/SetZoomForm.cs Normal file
View File

@ -0,0 +1,131 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Numerics;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Graphing.Forms
{
public partial class SetZoomForm : Form
{
private double minZoomRange;
private double maxZoomRange;
private double zoomLevel;
private readonly GraphForm form;
public SetZoomForm(GraphForm form)
{
InitializeComponent();
minZoomRange = 1 / (form.ZoomLevel * 2);
maxZoomRange = 2 / form.ZoomLevel;
zoomLevel = 1 / form.ZoomLevel;
ZoomTrackBar.Value = (int)(ZoomToFactor(zoomLevel) * (ZoomTrackBar.Maximum - ZoomTrackBar.Minimum) + ZoomTrackBar.Minimum);
this.form = form;
}
protected override void OnPaint(PaintEventArgs e)
{
ZoomMaxValue.Text = maxZoomRange.ToString("0.00");
ZoomMinValue.Text = minZoomRange.ToString("0.00");
ValueLabel.Text = $"{zoomLevel:0.00}x";
base.OnPaint(e);
form.ZoomLevel = 1 / zoomLevel;
form.Invalidate(false);
}
private double FactorToZoom(double factor)
{
return minZoomRange + (factor * factor) * (maxZoomRange - minZoomRange);
}
private double ZoomToFactor(double zoom)
{
double sqrValue = (zoom - minZoomRange) / (maxZoomRange - minZoomRange);
return Math.Sign(sqrValue) * Math.Sqrt(Math.Abs(sqrValue));
}
private void ZoomTrackBar_Scroll(object? sender, EventArgs e)
{
double factor = (ZoomTrackBar.Value - ZoomTrackBar.Minimum) / (double)(ZoomTrackBar.Maximum - ZoomTrackBar.Minimum);
zoomLevel = FactorToZoom(factor);
Invalidate(true);
}
private void ZoomMinValue_TextChanged(object? sender, EventArgs e)
{
double original = minZoomRange;
try
{
double value;
if (string.IsNullOrWhiteSpace(ZoomMinValue.Text) ||
ZoomMinValue.Text.EndsWith('.'))
{
return;
}
else
{
value = double.Parse(ZoomMinValue.Text);
if (value < 1e-2 || value > 1e3 || value > maxZoomRange) throw new();
}
minZoomRange = value;
ZoomTrackBar.Value = (int)Math.Clamp(ZoomToFactor(zoomLevel) * (ZoomTrackBar.Maximum - ZoomTrackBar.Minimum) + ZoomTrackBar.Minimum, ZoomTrackBar.Minimum, ZoomTrackBar.Maximum);
double factor = (ZoomTrackBar.Value - ZoomTrackBar.Minimum) / (double)(ZoomTrackBar.Maximum - ZoomTrackBar.Minimum);
double newZoom = FactorToZoom(factor);
zoomLevel = newZoom;
if (newZoom != factor) Invalidate(true);
}
catch
{
minZoomRange = original;
ZoomMinValue.Text = minZoomRange.ToString("0.00");
}
}
private void ZoomMaxValue_TextChanged(object sender, EventArgs e)
{
double original = maxZoomRange;
try
{
double value;
if (string.IsNullOrWhiteSpace(ZoomMaxValue.Text) ||
ZoomMaxValue.Text.EndsWith('.'))
{
return;
}
else
{
value = double.Parse(ZoomMaxValue.Text);
if (value < 1e-2 || value > 1e3 || value < minZoomRange) throw new();
}
maxZoomRange = value;
ZoomTrackBar.Value = (int)Math.Clamp(ZoomToFactor(zoomLevel) * (ZoomTrackBar.Maximum - ZoomTrackBar.Minimum) + ZoomTrackBar.Minimum, ZoomTrackBar.Minimum, ZoomTrackBar.Maximum);
double factor = (ZoomTrackBar.Value - ZoomTrackBar.Minimum) / (double)(ZoomTrackBar.Maximum - ZoomTrackBar.Minimum);
double newZoom = FactorToZoom(factor);
zoomLevel = newZoom;
if (newZoom != factor) Invalidate(true);
}
catch
{
maxZoomRange = original;
ZoomMaxValue.Text = maxZoomRange.ToString("0.00");
}
}
}
}

120
Base/Forms/SetZoomForm.resx Normal file
View File

@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

30
Base/Graphable.cs Normal file
View File

@ -0,0 +1,30 @@
using Graphing.Forms;
namespace Graphing;
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
];
public Color Color { get; set; }
public string Name { get; set; }
public Graphable()
{
Color = Color.FromArgb((int)DefaultColors[defaultColorsUsed % DefaultColors.Length]);
defaultColorsUsed++;
Name = "Unnamed Graphable.";
}
public abstract IEnumerable<Line2d> GetItemsToRender(in GraphForm graph);
}

View File

@ -0,0 +1,41 @@
using Graphing.Forms;
namespace Graphing.Graphables;
public class Equation : Graphable
{
private static int equationNum;
private readonly EquationDelegate equ;
public Equation(EquationDelegate equ)
{
equationNum++;
Name = $"Equation {equationNum}";
this.equ = equ;
}
public override IEnumerable<Line2d> GetItemsToRender(in GraphForm graph)
{
List<Line2d> lines = [];
double previousX = graph.MinVisibleGraph.x;
double previousY = equ(previousX);
for (int i = 1; i < graph.ClientRectangle.Width; i += 10)
{
double currentX = graph.ScreenSpaceToGraphSpace(new Int2(i, 0)).x;
double currentY = equ(currentX);
if (Math.Abs(currentY - previousY) <= 10)
{
lines.Add(new Line2d(new Float2(previousX, previousY), new Float2(currentX, currentY)));
}
previousX = currentX;
previousY = currentY;
}
return lines;
}
public EquationDelegate GetDelegate() => equ;
}
public delegate double EquationDelegate(double x);

View File

@ -0,0 +1,51 @@
using Graphing.Forms;
namespace Graphing.Graphables;
public class SlopeField : Graphable
{
private static int slopeFieldNum;
private readonly SlopeFieldsDelegate equ;
private readonly double detail;
public SlopeField(int detail, SlopeFieldsDelegate equ)
{
slopeFieldNum++;
Name = $"Slope Field {slopeFieldNum}";
this.equ = equ;
this.detail = detail;
}
public override IEnumerable<Line2d> GetItemsToRender(in GraphForm graph)
{
List<Line2d> lines = [];
for (double x = Math.Ceiling(graph.MinVisibleGraph.x - 1); x < graph.MaxVisibleGraph.x + 1; x += 1 / detail)
{
for (double y = Math.Ceiling(graph.MinVisibleGraph.y - 1); y < graph.MaxVisibleGraph.y + 1; y += 1 / detail)
{
double slope = equ(x, y);
lines.Add(MakeSlopeLine(new Float2(x, y), slope));
}
}
return lines;
}
private Line2d MakeSlopeLine(Float2 position, double slope)
{
double size = detail;
double dirX = size, dirY = slope * size;
double magnitude = Math.Sqrt(dirX * dirX + dirY * dirY);
dirX /= magnitude * size * 2;
dirY /= magnitude * size * 2;
return new(new(position.x + dirX, position.y + dirY), new(position.x - dirX, position.y - dirY));
}
}
public delegate double SlopeFieldsDelegate(double x, double y);

20
Base/Int2.cs Normal file
View File

@ -0,0 +1,20 @@
namespace Graphing;
public record struct Int2
{
public int x;
public int y;
public Int2()
{
x = 0;
y = 0;
}
public Int2(int x, int y)
{
this.x = x;
this.y = y;
}
public static implicit operator Point(Int2 v) => new(v.x, v.y);
}

18
Base/Line2d.cs Normal file
View File

@ -0,0 +1,18 @@
namespace Graphing;
public record struct Line2d
{
public Float2 a;
public Float2 b;
public Line2d()
{
a = new();
b = new();
}
public Line2d(Float2 a, Float2 b)
{
this.a = a;
this.b = b;
}
}

20
Base/Program.cs Normal file
View File

@ -0,0 +1,20 @@
using Graphing.Forms;
using Graphing.Graphables;
namespace Graphing;
internal static class Program
{
[STAThread]
public static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.SetHighDpiMode(HighDpiMode.PerMonitorV2);
GraphForm graph = new("One Of The Graphing Calculators Of All Time");
graph.Graph(new Equation(Math.Cos));
Application.Run(graph);
}
}

27
Base/Range2d.cs Normal file
View File

@ -0,0 +1,27 @@
namespace Graphing;
public record struct Range2d
{
public double minX;
public double minY;
public double maxX;
public double maxY;
public Range2d()
{
minX = 0;
minY = 0;
maxX = 0;
maxY = 0;
}
public Range2d(double minX, double minY, double maxX, double maxY)
{
this.minX = minX;
this.minY = minY;
this.maxX = maxX;
this.maxY = maxY;
}
public readonly bool Contains(Float2 p) =>
p.x >= minX && p.x <= maxX && p.y >= minY && p.y <= maxY;
}

25
Graphing.sln Normal file
View File

@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.8.34309.116
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Base", "Base\Base.csproj", "{2BA8787B-1341-4DBC-80E8-5DABA8ECBBD6}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{2BA8787B-1341-4DBC-80E8-5DABA8ECBBD6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2BA8787B-1341-4DBC-80E8-5DABA8ECBBD6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2BA8787B-1341-4DBC-80E8-5DABA8ECBBD6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2BA8787B-1341-4DBC-80E8-5DABA8ECBBD6}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {9A83C8E9-0686-4229-8B34-EAA7282E47B2}
EndGlobalSection
EndGlobal