commit 8d5e2257fc150cb0dc65c01f687429482b4daefc Author: That_One_Nerd Date: Tue Feb 27 14:49:00 2024 -0500 Initial commit. Version 1.0 soon. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..06e1cb9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.vs/ +Base/obj/ +Base/bin/ diff --git a/Base/Base.csproj b/Base/Base.csproj new file mode 100644 index 0000000..5540514 --- /dev/null +++ b/Base/Base.csproj @@ -0,0 +1,12 @@ + + + + WinExe + net8.0-windows + enable + true + enable + Graphing + + + \ No newline at end of file diff --git a/Base/Base.csproj.user b/Base/Base.csproj.user new file mode 100644 index 0000000..f3bd93a --- /dev/null +++ b/Base/Base.csproj.user @@ -0,0 +1,14 @@ + + + + + Form + + + Form + + + Form + + + \ No newline at end of file diff --git a/Base/Float2.cs b/Base/Float2.cs new file mode 100644 index 0000000..07005bc --- /dev/null +++ b/Base/Float2.cs @@ -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); +} diff --git a/Base/Forms/GraphColorPickerForm.Designer.cs b/Base/Forms/GraphColorPickerForm.Designer.cs new file mode 100644 index 0000000..f50ae6c --- /dev/null +++ b/Base/Forms/GraphColorPickerForm.Designer.cs @@ -0,0 +1,243 @@ +namespace Graphing.Forms +{ + partial class GraphColorPickerForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + 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; + } +} \ No newline at end of file diff --git a/Base/Forms/GraphColorPickerForm.cs b/Base/Forms/GraphColorPickerForm.cs new file mode 100644 index 0000000..b1ec674 --- /dev/null +++ b/Base/Forms/GraphColorPickerForm.cs @@ -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(); + } +} diff --git a/Base/Forms/GraphColorPickerForm.resx b/Base/Forms/GraphColorPickerForm.resx new file mode 100644 index 0000000..af32865 --- /dev/null +++ b/Base/Forms/GraphColorPickerForm.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Base/Forms/GraphForm.Designer.cs b/Base/Forms/GraphForm.Designer.cs new file mode 100644 index 0000000..23704fe --- /dev/null +++ b/Base/Forms/GraphForm.Designer.cs @@ -0,0 +1,158 @@ +namespace Graphing.Forms +{ + partial class GraphForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + 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; + } +} \ No newline at end of file diff --git a/Base/Forms/GraphForm.cs b/Base/Forms/GraphForm.cs new file mode 100644 index 0000000..66fe8a5 --- /dev/null +++ b/Base/Forms/GraphForm.cs @@ -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 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 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; + } + } +} diff --git a/Base/Forms/GraphForm.resx b/Base/Forms/GraphForm.resx new file mode 100644 index 0000000..e85d35c --- /dev/null +++ b/Base/Forms/GraphForm.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + \ No newline at end of file diff --git a/Base/Forms/SetZoomForm.Designer.cs b/Base/Forms/SetZoomForm.Designer.cs new file mode 100644 index 0000000..e659456 --- /dev/null +++ b/Base/Forms/SetZoomForm.Designer.cs @@ -0,0 +1,117 @@ +namespace Graphing.Forms +{ + partial class SetZoomForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + 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; + } +} \ No newline at end of file diff --git a/Base/Forms/SetZoomForm.cs b/Base/Forms/SetZoomForm.cs new file mode 100644 index 0000000..30e2567 --- /dev/null +++ b/Base/Forms/SetZoomForm.cs @@ -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"); + } + } + } +} diff --git a/Base/Forms/SetZoomForm.resx b/Base/Forms/SetZoomForm.resx new file mode 100644 index 0000000..af32865 --- /dev/null +++ b/Base/Forms/SetZoomForm.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Base/Graphable.cs b/Base/Graphable.cs new file mode 100644 index 0000000..1ccd839 --- /dev/null +++ b/Base/Graphable.cs @@ -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 GetItemsToRender(in GraphForm graph); +} diff --git a/Base/Graphables/Equation.cs b/Base/Graphables/Equation.cs new file mode 100644 index 0000000..dd0f2f0 --- /dev/null +++ b/Base/Graphables/Equation.cs @@ -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 GetItemsToRender(in GraphForm graph) + { + List 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); diff --git a/Base/Graphables/SlopeField.cs b/Base/Graphables/SlopeField.cs new file mode 100644 index 0000000..a230240 --- /dev/null +++ b/Base/Graphables/SlopeField.cs @@ -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 GetItemsToRender(in GraphForm graph) + { + List 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); diff --git a/Base/Int2.cs b/Base/Int2.cs new file mode 100644 index 0000000..6377498 --- /dev/null +++ b/Base/Int2.cs @@ -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); +} diff --git a/Base/Line2d.cs b/Base/Line2d.cs new file mode 100644 index 0000000..8103f58 --- /dev/null +++ b/Base/Line2d.cs @@ -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; + } +} diff --git a/Base/Program.cs b/Base/Program.cs new file mode 100644 index 0000000..69ae922 --- /dev/null +++ b/Base/Program.cs @@ -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); + } +} diff --git a/Base/Range2d.cs b/Base/Range2d.cs new file mode 100644 index 0000000..88d8177 --- /dev/null +++ b/Base/Range2d.cs @@ -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; +} diff --git a/Graphing.sln b/Graphing.sln new file mode 100644 index 0000000..383ee05 --- /dev/null +++ b/Graphing.sln @@ -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