Version 1.3 is ready.
Changelog written, release coming out soon.
This commit is contained in:
commit
ea608656db
10
Base/Abstract/IConvertColumnTable.cs
Normal file
10
Base/Abstract/IConvertColumnTable.cs
Normal file
@ -0,0 +1,10 @@
|
||||
using Graphing.Graphables;
|
||||
|
||||
namespace Graphing.Abstract;
|
||||
|
||||
public interface IConvertColumnTable
|
||||
{
|
||||
public bool UngraphWhenConvertedToColumnTable { get; }
|
||||
|
||||
public ColumnTable ToColumnTable(double start, double end, int detail);
|
||||
}
|
||||
10
Base/Abstract/IConvertEquation.cs
Normal file
10
Base/Abstract/IConvertEquation.cs
Normal file
@ -0,0 +1,10 @@
|
||||
using Graphing.Graphables;
|
||||
|
||||
namespace Graphing.Abstract;
|
||||
|
||||
public interface IConvertEquation
|
||||
{
|
||||
public bool UngraphWhenConvertedToEquation { get; }
|
||||
|
||||
public Equation ToEquation();
|
||||
}
|
||||
10
Base/Abstract/IConvertSlopeField.cs
Normal file
10
Base/Abstract/IConvertSlopeField.cs
Normal file
@ -0,0 +1,10 @@
|
||||
using Graphing.Graphables;
|
||||
|
||||
namespace Graphing.Abstract;
|
||||
|
||||
public interface IConvertSlopeField
|
||||
{
|
||||
public bool UngraphWhenConvertedToSlopeField { get; }
|
||||
|
||||
public SlopeField ToSlopeField(int detail);
|
||||
}
|
||||
3
Base/Abstract/ITranslatable.cs
Normal file
3
Base/Abstract/ITranslatable.cs
Normal file
@ -0,0 +1,3 @@
|
||||
namespace Graphing.Abstract;
|
||||
|
||||
public interface ITranslatable { }
|
||||
6
Base/Abstract/ITranslatableX.cs
Normal file
6
Base/Abstract/ITranslatableX.cs
Normal file
@ -0,0 +1,6 @@
|
||||
namespace Graphing.Abstract;
|
||||
|
||||
public interface ITranslatableX : ITranslatable
|
||||
{
|
||||
public double OffsetX { get; set; }
|
||||
}
|
||||
3
Base/Abstract/ITranslatableXY.cs
Normal file
3
Base/Abstract/ITranslatableXY.cs
Normal file
@ -0,0 +1,3 @@
|
||||
namespace Graphing.Abstract;
|
||||
|
||||
public interface ITranslatableXY : ITranslatableX, ITranslatableY { }
|
||||
6
Base/Abstract/ITranslatableY.cs
Normal file
6
Base/Abstract/ITranslatableY.cs
Normal file
@ -0,0 +1,6 @@
|
||||
namespace Graphing.Abstract;
|
||||
|
||||
public interface ITranslatableY : ITranslatable
|
||||
{
|
||||
public double OffsetY { get; set; }
|
||||
}
|
||||
@ -12,18 +12,18 @@
|
||||
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
||||
<PackageId>ThatOneNerd.Graphing</PackageId>
|
||||
<Title>ThatOneNerd.Graphing</Title>
|
||||
<Version>1.2.0</Version>
|
||||
<Version>1.3.0</Version>
|
||||
<Authors>That_One_Nerd</Authors>
|
||||
<Description>A fairly adept graphing calculator made in Windows Forms.</Description>
|
||||
<Copyright>MIT</Copyright>
|
||||
<RepositoryUrl>https://github.com/That-One-Nerd/Graphing</RepositoryUrl>
|
||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||
<PackageTags>graphing;graph;plot;math;calculus;visual;desmos;slope field;slopefield;equation;visualizer</PackageTags>
|
||||
<PackageTags>graphing;graph;plot;math;calculus;visual;desmos;slope field;slopefield;equation;visualizer;parametric equation;parametric;difference;tangent</PackageTags>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
<IncludeSymbols>True</IncludeSymbols>
|
||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||
<PackageReleaseNotes>View the GitHub release for the changelog:
|
||||
https://github.com/That-One-Nerd/Graphing/releases/tag/1.2.0</PackageReleaseNotes>
|
||||
https://github.com/That-One-Nerd/Graphing/releases/tag/1.3.0</PackageReleaseNotes>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
|
||||
@ -16,6 +16,12 @@
|
||||
<Compile Update="Forms\SetZoomForm.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
<Compile Update="Forms\SlopeFieldDetailForm.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
<Compile Update="Forms\TranslateForm.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
<Compile Update="Forms\ViewCacheForm.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
|
||||
193
Base/Forms/GraphForm.Designer.cs
generated
193
Base/Forms/GraphForm.Designer.cs
generated
@ -38,33 +38,45 @@ namespace Graphing.Forms
|
||||
ButtonViewportSetCenter = new ToolStripMenuItem();
|
||||
ButtonViewportReset = new ToolStripMenuItem();
|
||||
ButtonViewportResetWindow = new ToolStripMenuItem();
|
||||
MenuColors = new ToolStripMenuItem();
|
||||
MenuEquations = new ToolStripMenuItem();
|
||||
MenuEquationsDerivative = new ToolStripMenuItem();
|
||||
MenuEquationsIntegral = new ToolStripMenuItem();
|
||||
MenuElements = new ToolStripMenuItem();
|
||||
MenuElementsColors = new ToolStripMenuItem();
|
||||
MenuElementsRemove = new ToolStripMenuItem();
|
||||
MenuOperations = new ToolStripMenuItem();
|
||||
MenuOperationsDerivative = new ToolStripMenuItem();
|
||||
MenuOperationsIntegral = new ToolStripMenuItem();
|
||||
MenuOperationsTranslate = new ToolStripMenuItem();
|
||||
MenuConvert = new ToolStripMenuItem();
|
||||
MenuConvertEquation = new ToolStripMenuItem();
|
||||
MenuConvertSlopeField = new ToolStripMenuItem();
|
||||
MenuMisc = new ToolStripMenuItem();
|
||||
MenuMiscCaches = new ToolStripMenuItem();
|
||||
MiscMenuPreload = new ToolStripMenuItem();
|
||||
UpdaterPopup = new Panel();
|
||||
UpdaterPopupDownloadButton = new Button();
|
||||
UpdaterPopupCloseButton = new Button();
|
||||
UpdaterPopupMessage = new Label();
|
||||
MenuElementsDetail = new ToolStripMenuItem();
|
||||
GraphMenu.SuspendLayout();
|
||||
UpdaterPopup.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.Font = new Font("Segoe UI Emoji", 12F, FontStyle.Regular, GraphicsUnit.Point, 0);
|
||||
ResetViewportButton.Location = new Point(1372, 43);
|
||||
ResetViewportButton.Margin = new Padding(4, 2, 4, 2);
|
||||
ResetViewportButton.Name = "ResetViewportButton";
|
||||
ResetViewportButton.Size = new Size(64, 64);
|
||||
ResetViewportButton.Size = new Size(63, 64);
|
||||
ResetViewportButton.TabIndex = 0;
|
||||
ResetViewportButton.Text = "⌂";
|
||||
ResetViewportButton.TextAlign = ContentAlignment.TopRight;
|
||||
ResetViewportButton.Text = "🏠";
|
||||
ResetViewportButton.UseVisualStyleBackColor = true;
|
||||
ResetViewportButton.Click += ResetViewportButton_Click;
|
||||
//
|
||||
// GraphMenu
|
||||
//
|
||||
GraphMenu.ImageScalingSize = new Size(32, 32);
|
||||
GraphMenu.Items.AddRange(new ToolStripItem[] { MenuViewport, MenuColors, MenuEquations, MenuMisc });
|
||||
GraphMenu.Items.AddRange(new ToolStripItem[] { MenuViewport, MenuElements, MenuOperations, MenuConvert, MenuMisc });
|
||||
GraphMenu.Location = new Point(0, 0);
|
||||
GraphMenu.Name = "GraphMenu";
|
||||
GraphMenu.Size = new Size(1449, 42);
|
||||
@ -81,55 +93,93 @@ namespace Graphing.Forms
|
||||
// ButtonViewportSetZoom
|
||||
//
|
||||
ButtonViewportSetZoom.Name = "ButtonViewportSetZoom";
|
||||
ButtonViewportSetZoom.Size = new Size(350, 44);
|
||||
ButtonViewportSetZoom.Size = new Size(359, 44);
|
||||
ButtonViewportSetZoom.Text = "Set Zoom";
|
||||
ButtonViewportSetZoom.Click += ButtonViewportSetZoom_Click;
|
||||
//
|
||||
// ButtonViewportSetCenter
|
||||
//
|
||||
ButtonViewportSetCenter.Name = "ButtonViewportSetCenter";
|
||||
ButtonViewportSetCenter.Size = new Size(350, 44);
|
||||
ButtonViewportSetCenter.Size = new Size(359, 44);
|
||||
ButtonViewportSetCenter.Text = "Set Center Position";
|
||||
ButtonViewportSetCenter.Click += ButtonViewportSetCenter_Click;
|
||||
//
|
||||
// ButtonViewportReset
|
||||
//
|
||||
ButtonViewportReset.Name = "ButtonViewportReset";
|
||||
ButtonViewportReset.Size = new Size(350, 44);
|
||||
ButtonViewportReset.Size = new Size(359, 44);
|
||||
ButtonViewportReset.Text = "Reset Viewport";
|
||||
ButtonViewportReset.Click += ButtonViewportReset_Click;
|
||||
//
|
||||
// ButtonViewportResetWindow
|
||||
//
|
||||
ButtonViewportResetWindow.Name = "ButtonViewportResetWindow";
|
||||
ButtonViewportResetWindow.Size = new Size(350, 44);
|
||||
ButtonViewportResetWindow.Size = new Size(359, 44);
|
||||
ButtonViewportResetWindow.Text = "Reset Window Size";
|
||||
ButtonViewportResetWindow.Click += ButtonViewportResetWindow_Click;
|
||||
//
|
||||
// MenuColors
|
||||
// MenuElements
|
||||
//
|
||||
MenuColors.Name = "MenuColors";
|
||||
MenuColors.Size = new Size(101, 38);
|
||||
MenuColors.Text = "Colors";
|
||||
MenuElements.DropDownItems.AddRange(new ToolStripItem[] { MenuElementsColors, MenuElementsDetail, MenuElementsRemove });
|
||||
MenuElements.Name = "MenuElements";
|
||||
MenuElements.Size = new Size(131, 38);
|
||||
MenuElements.Text = "Elements";
|
||||
//
|
||||
// MenuEquations
|
||||
// MenuElementsColors
|
||||
//
|
||||
MenuEquations.DropDownItems.AddRange(new ToolStripItem[] { MenuEquationsDerivative, MenuEquationsIntegral });
|
||||
MenuEquations.Name = "MenuEquations";
|
||||
MenuEquations.Size = new Size(138, 38);
|
||||
MenuEquations.Text = "Equations";
|
||||
MenuElementsColors.Name = "MenuElementsColors";
|
||||
MenuElementsColors.Size = new Size(359, 44);
|
||||
MenuElementsColors.Text = "Colors";
|
||||
//
|
||||
// MenuEquationsDerivative
|
||||
// MenuElementsRemove
|
||||
//
|
||||
MenuEquationsDerivative.Name = "MenuEquationsDerivative";
|
||||
MenuEquationsDerivative.Size = new Size(360, 44);
|
||||
MenuEquationsDerivative.Text = "Compute Derivative";
|
||||
MenuElementsRemove.Name = "MenuElementsRemove";
|
||||
MenuElementsRemove.Size = new Size(359, 44);
|
||||
MenuElementsRemove.Text = "Remove";
|
||||
//
|
||||
// MenuEquationsIntegral
|
||||
// MenuOperations
|
||||
//
|
||||
MenuEquationsIntegral.Name = "MenuEquationsIntegral";
|
||||
MenuEquationsIntegral.Size = new Size(360, 44);
|
||||
MenuEquationsIntegral.Text = "Compute Integral";
|
||||
MenuOperations.DropDownItems.AddRange(new ToolStripItem[] { MenuOperationsDerivative, MenuOperationsIntegral, MenuOperationsTranslate });
|
||||
MenuOperations.Name = "MenuOperations";
|
||||
MenuOperations.Size = new Size(151, 38);
|
||||
MenuOperations.Text = "Operations";
|
||||
//
|
||||
// MenuOperationsDerivative
|
||||
//
|
||||
MenuOperationsDerivative.Name = "MenuOperationsDerivative";
|
||||
MenuOperationsDerivative.Size = new Size(360, 44);
|
||||
MenuOperationsDerivative.Text = "Compute Derivative";
|
||||
//
|
||||
// MenuOperationsIntegral
|
||||
//
|
||||
MenuOperationsIntegral.Name = "MenuOperationsIntegral";
|
||||
MenuOperationsIntegral.Size = new Size(360, 44);
|
||||
MenuOperationsIntegral.Text = "Compute Integral";
|
||||
//
|
||||
// MenuOperationsTranslate
|
||||
//
|
||||
MenuOperationsTranslate.Name = "MenuOperationsTranslate";
|
||||
MenuOperationsTranslate.Size = new Size(360, 44);
|
||||
MenuOperationsTranslate.Text = "Translate";
|
||||
//
|
||||
// MenuConvert
|
||||
//
|
||||
MenuConvert.DropDownItems.AddRange(new ToolStripItem[] { MenuConvertEquation, MenuConvertSlopeField });
|
||||
MenuConvert.Name = "MenuConvert";
|
||||
MenuConvert.Size = new Size(118, 38);
|
||||
MenuConvert.Text = "Convert";
|
||||
//
|
||||
// MenuConvertEquation
|
||||
//
|
||||
MenuConvertEquation.Name = "MenuConvertEquation";
|
||||
MenuConvertEquation.Size = new Size(297, 44);
|
||||
MenuConvertEquation.Text = "To Equation";
|
||||
//
|
||||
// MenuConvertSlopeField
|
||||
//
|
||||
MenuConvertSlopeField.Name = "MenuConvertSlopeField";
|
||||
MenuConvertSlopeField.Size = new Size(297, 44);
|
||||
MenuConvertSlopeField.Text = "To Slope Field";
|
||||
//
|
||||
// MenuMisc
|
||||
//
|
||||
@ -141,29 +191,87 @@ namespace Graphing.Forms
|
||||
// MenuMiscCaches
|
||||
//
|
||||
MenuMiscCaches.Name = "MenuMiscCaches";
|
||||
MenuMiscCaches.Size = new Size(359, 44);
|
||||
MenuMiscCaches.Size = new Size(299, 44);
|
||||
MenuMiscCaches.Text = "View Caches";
|
||||
MenuMiscCaches.Click += MenuMiscCaches_Click;
|
||||
//
|
||||
// MiscMenuPreload
|
||||
//
|
||||
MiscMenuPreload.Name = "MiscMenuPreload";
|
||||
MiscMenuPreload.Size = new Size(359, 44);
|
||||
MiscMenuPreload.Size = new Size(299, 44);
|
||||
MiscMenuPreload.Text = "Preload Cache";
|
||||
MiscMenuPreload.Click += MiscMenuPreload_Click;
|
||||
//
|
||||
// UpdaterPopup
|
||||
//
|
||||
UpdaterPopup.Anchor = AnchorStyles.Bottom | AnchorStyles.Right;
|
||||
UpdaterPopup.BackColor = SystemColors.HighlightText;
|
||||
UpdaterPopup.BorderStyle = BorderStyle.FixedSingle;
|
||||
UpdaterPopup.Controls.Add(UpdaterPopupDownloadButton);
|
||||
UpdaterPopup.Controls.Add(UpdaterPopupCloseButton);
|
||||
UpdaterPopup.Controls.Add(UpdaterPopupMessage);
|
||||
UpdaterPopup.Location = new Point(966, 791);
|
||||
UpdaterPopup.Margin = new Padding(6, 6, 6, 6);
|
||||
UpdaterPopup.Name = "UpdaterPopup";
|
||||
UpdaterPopup.Size = new Size(483, 115);
|
||||
UpdaterPopup.TabIndex = 2;
|
||||
UpdaterPopup.Visible = false;
|
||||
//
|
||||
// UpdaterPopupDownloadButton
|
||||
//
|
||||
UpdaterPopupDownloadButton.Anchor = AnchorStyles.Bottom | AnchorStyles.Right;
|
||||
UpdaterPopupDownloadButton.Location = new Point(336, 58);
|
||||
UpdaterPopupDownloadButton.Margin = new Padding(6, 6, 6, 6);
|
||||
UpdaterPopupDownloadButton.Name = "UpdaterPopupDownloadButton";
|
||||
UpdaterPopupDownloadButton.Size = new Size(139, 49);
|
||||
UpdaterPopupDownloadButton.TabIndex = 2;
|
||||
UpdaterPopupDownloadButton.Text = "Visit";
|
||||
UpdaterPopupDownloadButton.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// UpdaterPopupCloseButton
|
||||
//
|
||||
UpdaterPopupCloseButton.Anchor = AnchorStyles.Top | AnchorStyles.Right;
|
||||
UpdaterPopupCloseButton.Location = new Point(435, 2);
|
||||
UpdaterPopupCloseButton.Margin = new Padding(2, 2, 2, 2);
|
||||
UpdaterPopupCloseButton.Name = "UpdaterPopupCloseButton";
|
||||
UpdaterPopupCloseButton.Size = new Size(45, 51);
|
||||
UpdaterPopupCloseButton.TabIndex = 1;
|
||||
UpdaterPopupCloseButton.Text = "X";
|
||||
UpdaterPopupCloseButton.UseVisualStyleBackColor = true;
|
||||
UpdaterPopupCloseButton.Click += UpdaterPopupCloseButton_Click;
|
||||
//
|
||||
// UpdaterPopupMessage
|
||||
//
|
||||
UpdaterPopupMessage.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left;
|
||||
UpdaterPopupMessage.Font = new Font("Segoe UI", 9.75F, FontStyle.Bold, GraphicsUnit.Point, 0);
|
||||
UpdaterPopupMessage.Location = new Point(6, 6);
|
||||
UpdaterPopupMessage.Margin = new Padding(6, 6, 6, 6);
|
||||
UpdaterPopupMessage.Name = "UpdaterPopupMessage";
|
||||
UpdaterPopupMessage.Size = new Size(423, 100);
|
||||
UpdaterPopupMessage.TabIndex = 0;
|
||||
UpdaterPopupMessage.Text = "A <type> update is available!\r\nA.B.C → E.F.G";
|
||||
//
|
||||
// MenuElementsDetail
|
||||
//
|
||||
MenuElementsDetail.Name = "MenuElementsDetail";
|
||||
MenuElementsDetail.Size = new Size(359, 44);
|
||||
MenuElementsDetail.Text = "Detail";
|
||||
//
|
||||
// GraphForm
|
||||
//
|
||||
AutoScaleDimensions = new SizeF(13F, 32F);
|
||||
AutoScaleMode = AutoScaleMode.Font;
|
||||
ClientSize = new Size(1449, 907);
|
||||
Controls.Add(UpdaterPopup);
|
||||
Controls.Add(ResetViewportButton);
|
||||
Controls.Add(GraphMenu);
|
||||
MainMenuStrip = GraphMenu;
|
||||
Margin = new Padding(4, 2, 4, 2);
|
||||
Name = "GraphForm";
|
||||
Text = "GraphFormBase";
|
||||
GraphMenu.ResumeLayout(false);
|
||||
GraphMenu.PerformLayout();
|
||||
UpdaterPopup.ResumeLayout(false);
|
||||
ResumeLayout(false);
|
||||
PerformLayout();
|
||||
}
|
||||
@ -172,17 +280,28 @@ namespace Graphing.Forms
|
||||
|
||||
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;
|
||||
private ToolStripMenuItem MenuOperations;
|
||||
private ToolStripMenuItem MenuOperationsDerivative;
|
||||
private ToolStripMenuItem MenuOperationsIntegral;
|
||||
private ToolStripMenuItem MenuMisc;
|
||||
private ToolStripMenuItem MenuMiscCaches;
|
||||
private ToolStripMenuItem MiscMenuPreload;
|
||||
private ToolStripMenuItem MenuConvert;
|
||||
private ToolStripMenuItem MenuConvertEquation;
|
||||
private ToolStripMenuItem MenuElements;
|
||||
private ToolStripMenuItem MenuElementsColors;
|
||||
private ToolStripMenuItem MenuElementsRemove;
|
||||
private ToolStripMenuItem MenuOperationsTranslate;
|
||||
private ToolStripMenuItem MenuConvertSlopeField;
|
||||
private Panel UpdaterPopup;
|
||||
private Label UpdaterPopupMessage;
|
||||
private Button UpdaterPopupCloseButton;
|
||||
private Button UpdaterPopupDownloadButton;
|
||||
private ToolStripMenuItem MenuElementsDetail;
|
||||
}
|
||||
}
|
||||
@ -1,46 +1,71 @@
|
||||
using Graphing.Abstract;
|
||||
using Graphing.Parts;
|
||||
using Graphing.Graphables;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Drawing2D;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Reflection;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace Graphing.Forms;
|
||||
|
||||
public partial class GraphForm : Form
|
||||
{
|
||||
public static readonly Color BackgroundColor = Color.White;
|
||||
public static readonly Color MainAxisColor = Color.Black;
|
||||
public static readonly Color SemiAxisColor = Color.FromArgb(unchecked((int)0xFF_999999));
|
||||
public static readonly Color QuarterAxisColor = Color.FromArgb(unchecked((int)0xFF_E0E0E0));
|
||||
public static readonly Color SemiAxisColor = Color.FromArgb(unchecked((int)0xFF_999999)); // Grayish
|
||||
public static readonly Color QuarterAxisColor = Color.FromArgb(unchecked((int)0xFF_E0E0E0)); // Lighter grayish
|
||||
public static readonly Color UnitsTextColor = Color.Black;
|
||||
public static readonly Color ZoomBoxColor = Color.Black;
|
||||
|
||||
public Float2 ScreenCenter { get; private set; }
|
||||
public static readonly Color MajorUpdateColor = Color.FromArgb(unchecked((int)0xFF_F74434)); // Red
|
||||
public static readonly Color MinorUpdateColor = Color.FromArgb(unchecked((int)0xFF_FCA103)); // Orange
|
||||
|
||||
public Float2 ScreenCenter { get; set; }
|
||||
public Float2 Dpi { get; private set; }
|
||||
|
||||
public float DpiFloat { get; private set; }
|
||||
|
||||
public double ZoomLevel
|
||||
public Float2 ZoomLevel
|
||||
{
|
||||
get => _zoomLevel;
|
||||
set
|
||||
{
|
||||
double oldZoom = ZoomLevel;
|
||||
|
||||
_zoomLevel = Math.Clamp(value, 1e-5, 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.
|
||||
}
|
||||
_zoomLevel = new(Math.Clamp(value.x, 1e-5, 1e3),
|
||||
Math.Clamp(value.y, 1e-5, 1e3));
|
||||
OnZoomLevelChanged(this, new());
|
||||
Invalidate(false);
|
||||
}
|
||||
}
|
||||
private double _zoomLevel;
|
||||
private Float2 _zoomLevel;
|
||||
|
||||
public bool ViewportLocked
|
||||
{
|
||||
get => _viewportLocked;
|
||||
set
|
||||
{
|
||||
if (value)
|
||||
{
|
||||
FormBorderStyle = FormBorderStyle.FixedSingle;
|
||||
ResetViewportButton.Text = "🔒";
|
||||
}
|
||||
else
|
||||
{
|
||||
FormBorderStyle = FormBorderStyle.Sizable;
|
||||
ResetViewportButton.Text = "🏠";
|
||||
}
|
||||
MaximizeBox = !value;
|
||||
ResetViewportButton.Enabled = !value;
|
||||
|
||||
_viewportLocked = value;
|
||||
}
|
||||
}
|
||||
private bool _viewportLocked;
|
||||
|
||||
private readonly Point initialWindowPos;
|
||||
private readonly Size initialWindowSize;
|
||||
@ -52,6 +77,8 @@ public partial class GraphForm : Form
|
||||
|
||||
private readonly List<Graphable> ables;
|
||||
|
||||
public event EventHandler OnZoomLevelChanged = delegate { };
|
||||
|
||||
public GraphForm(string title)
|
||||
{
|
||||
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
|
||||
@ -68,9 +95,11 @@ public partial class GraphForm : Form
|
||||
DpiFloat = (float)((Dpi.x + Dpi.y) / 2);
|
||||
|
||||
ables = [];
|
||||
ZoomLevel = 1;
|
||||
ZoomLevel = new(1, 1);
|
||||
initialWindowPos = Location;
|
||||
initialWindowSize = Size;
|
||||
|
||||
RunUpdateChecker();
|
||||
}
|
||||
|
||||
public Int2 GraphSpaceToScreenSpace(Float2 graphPoint)
|
||||
@ -80,8 +109,8 @@ public partial class GraphForm : Form
|
||||
graphPoint.x -= ScreenCenter.x;
|
||||
graphPoint.y -= ScreenCenter.y;
|
||||
|
||||
graphPoint.x *= Dpi.x / ZoomLevel;
|
||||
graphPoint.y *= Dpi.y / ZoomLevel;
|
||||
graphPoint.x *= Dpi.x / ZoomLevel.x;
|
||||
graphPoint.y *= Dpi.y / ZoomLevel.y;
|
||||
|
||||
graphPoint.x += ClientRectangle.Width / 2.0;
|
||||
graphPoint.y += ClientRectangle.Height / 2.0;
|
||||
@ -95,8 +124,8 @@ public partial class GraphForm : Form
|
||||
result.x -= ClientRectangle.Width / 2.0;
|
||||
result.y -= ClientRectangle.Height / 2.0;
|
||||
|
||||
result.x /= Dpi.x / ZoomLevel;
|
||||
result.y /= Dpi.y / ZoomLevel;
|
||||
result.x /= Dpi.x / ZoomLevel.x;
|
||||
result.y /= Dpi.y / ZoomLevel.y;
|
||||
|
||||
result.x += ScreenCenter.x;
|
||||
result.y += ScreenCenter.y;
|
||||
@ -108,19 +137,20 @@ public partial class GraphForm : Form
|
||||
|
||||
protected virtual void PaintGrid(Graphics g)
|
||||
{
|
||||
double axisScale = Math.Pow(2, Math.Round(Math.Log2(ZoomLevel)));
|
||||
double axisScaleX = Math.Pow(2, Math.Round(Math.Log2(ZoomLevel.x))),
|
||||
axisScaleY = Math.Pow(2, Math.Round(Math.Log2(ZoomLevel.y)));
|
||||
|
||||
// Draw horizontal/vertical quarter-axis.
|
||||
Brush quarterBrush = new SolidBrush(QuarterAxisColor);
|
||||
Pen quarterPen = new(quarterBrush, DpiFloat * 2 / 192);
|
||||
|
||||
for (double x = Math.Ceiling(MinVisibleGraph.x * 4 / axisScale) * axisScale / 4; x <= Math.Floor(MaxVisibleGraph.x * 4 / axisScale) * axisScale / 4; x += axisScale / 4)
|
||||
for (double x = Math.Ceiling(MinVisibleGraph.x * 4 / axisScaleX) * axisScaleX / 4; x <= Math.Floor(MaxVisibleGraph.x * 4 / axisScaleX) * axisScaleX / 4; x += axisScaleX / 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)
|
||||
for (double y = Math.Ceiling(MinVisibleGraph.y * 4 / axisScaleY) * axisScaleY / 4; y <= Math.Floor(MaxVisibleGraph.y * 4 / axisScaleY) * axisScaleY / 4; y += axisScaleY / 4)
|
||||
{
|
||||
Int2 startPos = GraphSpaceToScreenSpace(new Float2(MinVisibleGraph.x, y)),
|
||||
endPos = GraphSpaceToScreenSpace(new Float2(MaxVisibleGraph.x, y));
|
||||
@ -131,13 +161,13 @@ public partial class GraphForm : Form
|
||||
Brush semiBrush = new SolidBrush(SemiAxisColor);
|
||||
Pen semiPen = new(semiBrush, DpiFloat * 2 / 192);
|
||||
|
||||
for (double x = Math.Ceiling(MinVisibleGraph.x / axisScale) * axisScale; x <= Math.Floor(MaxVisibleGraph.x / axisScale) * axisScale; x += axisScale)
|
||||
for (double x = Math.Ceiling(MinVisibleGraph.x / axisScaleX) * axisScaleX; x <= Math.Floor(MaxVisibleGraph.x / axisScaleX) * axisScaleX; x += axisScaleX)
|
||||
{
|
||||
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)
|
||||
for (double y = Math.Ceiling(MinVisibleGraph.y / axisScaleY) * axisScaleY; y <= Math.Floor(MaxVisibleGraph.y / axisScaleY) * axisScaleY; y += axisScaleY)
|
||||
{
|
||||
Int2 startPos = GraphSpaceToScreenSpace(new Float2(MinVisibleGraph.x, y)),
|
||||
endPos = GraphSpaceToScreenSpace(new Float2(MaxVisibleGraph.x, y));
|
||||
@ -158,14 +188,15 @@ public partial class GraphForm : Form
|
||||
}
|
||||
protected virtual void PaintUnits(Graphics g)
|
||||
{
|
||||
double axisScale = Math.Pow(2, Math.Round(Math.Log(ZoomLevel, 2)));
|
||||
double axisScaleX = Math.Pow(2, Math.Round(Math.Log2(ZoomLevel.x))),
|
||||
axisScaleY = Math.Pow(2, Math.Round(Math.Log2(ZoomLevel.y)));
|
||||
Brush textBrush = new SolidBrush(UnitsTextColor);
|
||||
Font textFont = new(Font.Name, 9, FontStyle.Regular);
|
||||
|
||||
// X-axis
|
||||
int minX = (int)(DpiFloat * 50 / 192),
|
||||
maxX = ClientRectangle.Height - (int)(DpiFloat * 40 / 192);
|
||||
for (double x = Math.Ceiling(MinVisibleGraph.x / axisScale) * axisScale; x <= MaxVisibleGraph.x; x += axisScale)
|
||||
for (double x = Math.Ceiling(MinVisibleGraph.x / axisScaleX) * axisScaleX; x <= MaxVisibleGraph.x; x += axisScaleX)
|
||||
{
|
||||
if (x == 0) x = 0; // Fixes -0
|
||||
|
||||
@ -179,7 +210,7 @@ public partial class GraphForm : Form
|
||||
|
||||
// Y-axis
|
||||
int minY = (int)(DpiFloat * 10 / 192);
|
||||
for (double y = Math.Ceiling(MinVisibleGraph.y / axisScale) * axisScale; y <= MaxVisibleGraph.y; y += axisScale)
|
||||
for (double y = Math.Ceiling(MinVisibleGraph.y / axisScaleY) * axisScaleY; y <= MaxVisibleGraph.y; y += axisScaleY)
|
||||
{
|
||||
if (y == 0) continue;
|
||||
|
||||
@ -200,7 +231,7 @@ public partial class GraphForm : Form
|
||||
Graphics g = e.Graphics;
|
||||
g.SmoothingMode = SmoothingMode.HighQuality;
|
||||
|
||||
Brush background = new SolidBrush(Color.White);
|
||||
Brush background = new SolidBrush(BackgroundColor);
|
||||
g.FillRectangle(background, e.ClipRectangle);
|
||||
|
||||
PaintGrid(g);
|
||||
@ -223,29 +254,31 @@ public partial class GraphForm : Form
|
||||
|
||||
// Equation selection detection.
|
||||
// This system lets you select multiple graphs, and that's cool by me.
|
||||
if (ableDrag)
|
||||
if (selectState == SelectionState.GraphSelect)
|
||||
{
|
||||
Font textFont = new(Font.Name, 8, FontStyle.Bold);
|
||||
for (int i = 0; i < ables.Count; i++)
|
||||
{
|
||||
if (ables[i].ShouldSelectGraphable(this, graphMousePos, 2.5))
|
||||
{
|
||||
Float2 selectedPoint = ables[i].GetSelectedPoint(this, graphMousePos);
|
||||
GraphUiCircle select = new(selectedPoint, 8);
|
||||
|
||||
Int2 textPos = GraphSpaceToScreenSpace(select.center);
|
||||
textPos.y -= (int)(DpiFloat * 32 / 192);
|
||||
|
||||
string content = $"({selectedPoint.x:0.00}, {selectedPoint.y:0.00})";
|
||||
|
||||
SizeF textSize = g.MeasureString(content, textFont);
|
||||
g.FillRectangle(background, new Rectangle(textPos.x, textPos.y,
|
||||
(int)textSize.Width, (int)textSize.Height));
|
||||
g.DrawString(content, textFont, graphPens[i].Brush, new Point(textPos.x, textPos.y));
|
||||
select.Render(this, g, graphPens[i]);
|
||||
IEnumerable<IGraphPart> selectionParts = ables[i].GetSelectionItemsToRender(this, graphMousePos);
|
||||
foreach (IGraphPart selPart in selectionParts) selPart.Render(this, g, graphPens[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (selectState == SelectionState.ZoomBox)
|
||||
{
|
||||
// Draw the current box selection.
|
||||
Int2 boxPosA = GraphSpaceToScreenSpace(boxSelectA),
|
||||
boxPosB = GraphSpaceToScreenSpace(boxSelectB);
|
||||
|
||||
if (boxPosA.x > boxPosB.x) (boxPosA.x, boxPosB.x) = (boxPosB.x, boxPosA.x);
|
||||
if (boxPosA.y > boxPosB.y) (boxPosA.y, boxPosB.y) = (boxPosB.y, boxPosA.y);
|
||||
|
||||
Pen boxPen = new(ZoomBoxColor, 2 * DpiFloat / 192);
|
||||
g.DrawRectangle(boxPen, new(boxPosA.x, boxPosA.y,
|
||||
boxPosB.x - boxPosA.x,
|
||||
boxPosB.y - boxPosA.y));
|
||||
}
|
||||
|
||||
base.OnPaint(e);
|
||||
}
|
||||
@ -255,77 +288,152 @@ public partial class GraphForm : Form
|
||||
Invalidate(false);
|
||||
}
|
||||
|
||||
public void Graph(params Graphable[] able)
|
||||
public void Graph(params Graphable[] newAbles)
|
||||
{
|
||||
ables.AddRange(able);
|
||||
ables.AddRange(newAbles);
|
||||
RegenerateMenuItems();
|
||||
Invalidate(false);
|
||||
}
|
||||
public void Ungraph(params Graphable[] ables)
|
||||
{
|
||||
this.ables.RemoveAll(x => ables.Contains(x));
|
||||
RegenerateMenuItems();
|
||||
Invalidate(false);
|
||||
}
|
||||
|
||||
private bool mouseDrag = false;
|
||||
public bool IsGraphPointVisible(Float2 point)
|
||||
{
|
||||
Int2 pixelPos = GraphSpaceToScreenSpace(point);
|
||||
return pixelPos.x >= 0 && pixelPos.x < ClientRectangle.Width &&
|
||||
pixelPos.y >= 0 && pixelPos.y < ClientRectangle.Height;
|
||||
}
|
||||
|
||||
private SelectionState selectState = SelectionState.None;
|
||||
internal bool canBoxSelect;
|
||||
private SetZoomForm? setZoomForm;
|
||||
|
||||
private Int2 initialMouseLocation;
|
||||
private Float2 initialScreenCenter;
|
||||
|
||||
private bool ableDrag = false;
|
||||
private Float2 boxSelectA, boxSelectB;
|
||||
|
||||
protected override void OnMouseDown(MouseEventArgs e)
|
||||
{
|
||||
if (!mouseDrag)
|
||||
if (selectState == SelectionState.None && canBoxSelect)
|
||||
{
|
||||
Point clientMousePos = PointToClient(Cursor.Position);
|
||||
Float2 graphMousePos = ScreenSpaceToGraphSpace(new(clientMousePos.X,
|
||||
clientMousePos.Y));
|
||||
|
||||
boxSelectA = graphMousePos;
|
||||
boxSelectB = graphMousePos;
|
||||
|
||||
selectState = SelectionState.ZoomBox;
|
||||
}
|
||||
|
||||
if (selectState == SelectionState.None)
|
||||
{
|
||||
Point clientMousePos = PointToClient(Cursor.Position);
|
||||
Float2 graphMousePos = ScreenSpaceToGraphSpace(new(clientMousePos.X,
|
||||
clientMousePos.Y));
|
||||
foreach (Graphable able in Graphables)
|
||||
{
|
||||
if (able.ShouldSelectGraphable(this, graphMousePos, 1)) ableDrag = true;
|
||||
if (able.ShouldSelectGraphable(this, graphMousePos, 1))
|
||||
selectState = SelectionState.GraphSelect;
|
||||
}
|
||||
if (ableDrag) Invalidate(false);
|
||||
if (selectState == SelectionState.GraphSelect) Invalidate(false);
|
||||
}
|
||||
|
||||
if (!ableDrag)
|
||||
if (selectState == SelectionState.None && !ViewportLocked)
|
||||
{
|
||||
mouseDrag = true;
|
||||
selectState = SelectionState.ViewportDrag;
|
||||
initialMouseLocation = new Int2(Cursor.Position.X, Cursor.Position.Y);
|
||||
initialScreenCenter = ScreenCenter;
|
||||
}
|
||||
}
|
||||
protected override void OnMouseUp(MouseEventArgs e)
|
||||
{
|
||||
if (mouseDrag)
|
||||
if (selectState == SelectionState.None) return;
|
||||
else if (selectState == SelectionState.ViewportDrag)
|
||||
{
|
||||
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);
|
||||
Float2 graphDiff = new(pixelDiff.x * ZoomLevel.x / Dpi.x, pixelDiff.y * ZoomLevel.y / Dpi.y);
|
||||
ScreenCenter = new(initialScreenCenter.x + graphDiff.x,
|
||||
initialScreenCenter.y + graphDiff.y);
|
||||
}
|
||||
mouseDrag = false;
|
||||
ableDrag = false;
|
||||
else if (selectState == SelectionState.ZoomBox)
|
||||
{
|
||||
Point clientMousePos = PointToClient(Cursor.Position);
|
||||
Float2 graphMousePos = ScreenSpaceToGraphSpace(new(clientMousePos.X,
|
||||
clientMousePos.Y));
|
||||
boxSelectB = graphMousePos;
|
||||
|
||||
// Set center.
|
||||
ScreenCenter = new((boxSelectA.x + boxSelectB.x) * 0.5,
|
||||
-(boxSelectA.y + boxSelectB.y) * 0.5);
|
||||
|
||||
// Set zoom. Kind of weird but it works.
|
||||
Float2 minGraph = MinVisibleGraph, maxGraph = MaxVisibleGraph;
|
||||
Float2 oldDist = new(maxGraph.x - minGraph.x,
|
||||
maxGraph.y - minGraph.y);
|
||||
Float2 newDist = new(Math.Abs(boxSelectB.x - boxSelectA.x),
|
||||
Math.Abs(boxSelectB.y - boxSelectA.y));
|
||||
ZoomLevel = new(ZoomLevel.x * newDist.x / oldDist.x,
|
||||
ZoomLevel.y * newDist.y / oldDist.y);
|
||||
|
||||
setZoomForm!.CompleteBoxSelection();
|
||||
|
||||
boxSelectA = new(0, 0);
|
||||
boxSelectB = new(0, 0);
|
||||
}
|
||||
selectState = SelectionState.None;
|
||||
Invalidate(false);
|
||||
}
|
||||
protected override void OnMouseMove(MouseEventArgs e)
|
||||
{
|
||||
if (mouseDrag)
|
||||
if (selectState == SelectionState.None) return;
|
||||
else if (selectState == SelectionState.ViewportDrag)
|
||||
{
|
||||
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);
|
||||
Float2 graphDiff = new(pixelDiff.x * ZoomLevel.x / Dpi.x, pixelDiff.y * ZoomLevel.y / Dpi.y);
|
||||
ScreenCenter = new(initialScreenCenter.x + graphDiff.x,
|
||||
initialScreenCenter.y + graphDiff.y);
|
||||
Invalidate(false);
|
||||
}
|
||||
else if (ableDrag) Invalidate(false);
|
||||
else if (selectState == SelectionState.ZoomBox)
|
||||
{
|
||||
Point clientMousePos = PointToClient(Cursor.Position);
|
||||
Float2 graphMousePos = ScreenSpaceToGraphSpace(new(clientMousePos.X,
|
||||
clientMousePos.Y));
|
||||
boxSelectB = graphMousePos;
|
||||
}
|
||||
Invalidate(false);
|
||||
}
|
||||
protected override void OnMouseWheel(MouseEventArgs e)
|
||||
{
|
||||
ZoomLevel *= 1 - e.Delta * 0.00075; // Zoom factor.
|
||||
if (ViewportLocked) return;
|
||||
|
||||
Point clientMousePos = PointToClient(Cursor.Position);
|
||||
Int2 mousePos = new(clientMousePos.X, clientMousePos.Y);
|
||||
Float2 mouseOver = ScreenSpaceToGraphSpace(mousePos);
|
||||
|
||||
Float2 newZoom = ZoomLevel;
|
||||
newZoom.x *= 1 - e.Delta * 0.00075; // Zoom factor.
|
||||
newZoom.y *= 1 - e.Delta * 0.00075;
|
||||
ZoomLevel = newZoom;
|
||||
|
||||
// Keep the mouse as the zoom hotspot.
|
||||
Float2 newOver = ScreenSpaceToGraphSpace(mousePos);
|
||||
Float2 delta = new(newOver.x - mouseOver.x, newOver.y - mouseOver.y);
|
||||
ScreenCenter = new(ScreenCenter.x - delta.x, ScreenCenter.y + delta.y);
|
||||
|
||||
Invalidate(false);
|
||||
}
|
||||
|
||||
private void ResetViewportButton_Click(object? sender, EventArgs e)
|
||||
{
|
||||
ScreenCenter = new Float2(0, 0);
|
||||
ZoomLevel = 1;
|
||||
Invalidate(false);
|
||||
ResetAllViewport();
|
||||
}
|
||||
private void GraphColorPickerButton_Click(Graphable able)
|
||||
{
|
||||
@ -346,11 +454,46 @@ public partial class GraphForm : Form
|
||||
RegenerateMenuItems();
|
||||
}
|
||||
|
||||
private readonly Dictionary<SlopeField, SlopeFieldDetailForm> sfDetailForms = [];
|
||||
private void ChangeSlopeFieldDetail(SlopeField sf)
|
||||
{
|
||||
if (sfDetailForms.TryGetValue(sf, out SlopeFieldDetailForm? preexistingForm))
|
||||
{
|
||||
preexistingForm.Focus();
|
||||
return;
|
||||
}
|
||||
|
||||
SlopeFieldDetailForm detailForm = new(this, sf)
|
||||
{
|
||||
StartPosition = FormStartPosition.Manual
|
||||
};
|
||||
sfDetailForms.Add(sf, detailForm);
|
||||
|
||||
detailForm.Location = new Point(Location.X + ClientRectangle.Width + 10,
|
||||
Location.Y + (ClientRectangle.Height - detailForm.ClientRectangle.Height) / 2);
|
||||
|
||||
if (detailForm.Location.X + detailForm.Width > Screen.FromControl(this).WorkingArea.Width)
|
||||
{
|
||||
detailForm.StartPosition = FormStartPosition.WindowsDefaultLocation;
|
||||
}
|
||||
detailForm.TopMost = true;
|
||||
detailForm.Show();
|
||||
|
||||
detailForm.FormClosed += (o, e) => sfDetailForms.Remove(sf);
|
||||
}
|
||||
|
||||
private void RegenerateMenuItems()
|
||||
{
|
||||
MenuColors.DropDownItems.Clear();
|
||||
MenuEquationsDerivative.DropDownItems.Clear();
|
||||
MenuEquationsIntegral.DropDownItems.Clear();
|
||||
MenuElementsColors.DropDownItems.Clear();
|
||||
MenuElementsDetail.DropDownItems.Clear();
|
||||
MenuElementsRemove.DropDownItems.Clear();
|
||||
MenuOperationsDerivative.DropDownItems.Clear();
|
||||
MenuOperationsIntegral.DropDownItems.Clear();
|
||||
MenuConvertEquation.DropDownItems.Clear();
|
||||
MenuConvertSlopeField.DropDownItems.Clear();
|
||||
MenuOperationsTranslate.DropDownItems.Clear();
|
||||
// At some point, we'll have a Convert To Column Table button,
|
||||
// but I'll need to make a form for the ranges when I do that.
|
||||
|
||||
foreach (Graphable able in ables)
|
||||
{
|
||||
@ -360,7 +503,26 @@ public partial class GraphForm : Form
|
||||
Text = able.Name
|
||||
};
|
||||
colorItem.Click += (o, e) => GraphColorPickerButton_Click(able);
|
||||
MenuColors.DropDownItems.Add(colorItem);
|
||||
MenuElementsColors.DropDownItems.Add(colorItem);
|
||||
|
||||
ToolStripMenuItem removeItem = new()
|
||||
{
|
||||
ForeColor = able.Color,
|
||||
Text = able.Name
|
||||
};
|
||||
removeItem.Click += (o, e) => Ungraph(able);
|
||||
MenuElementsRemove.DropDownItems.Add(removeItem);
|
||||
|
||||
if (able is SlopeField sf)
|
||||
{
|
||||
ToolStripMenuItem sfDetailItem = new()
|
||||
{
|
||||
ForeColor = able.Color,
|
||||
Text = able.Name
|
||||
};
|
||||
sfDetailItem.Click += (o, e) => ChangeSlopeFieldDetail(sf);
|
||||
MenuElementsDetail.DropDownItems.Add(sfDetailItem);
|
||||
}
|
||||
|
||||
if (able is IDerivable derivable)
|
||||
{
|
||||
@ -370,7 +532,7 @@ public partial class GraphForm : Form
|
||||
Text = able.Name
|
||||
};
|
||||
derivativeItem.Click += (o, e) => Graph(derivable.Derive());
|
||||
MenuEquationsDerivative.DropDownItems.Add(derivativeItem);
|
||||
MenuOperationsDerivative.DropDownItems.Add(derivativeItem);
|
||||
}
|
||||
if (able is IIntegrable integrable)
|
||||
{
|
||||
@ -380,20 +542,76 @@ public partial class GraphForm : Form
|
||||
Text = able.Name
|
||||
};
|
||||
integralItem.Click += (o, e) => Graph(integrable.Integrate());
|
||||
MenuEquationsIntegral.DropDownItems.Add(integralItem);
|
||||
MenuOperationsIntegral.DropDownItems.Add(integralItem);
|
||||
}
|
||||
if (able is IConvertEquation equConvert)
|
||||
{
|
||||
ToolStripMenuItem equItem = new()
|
||||
{
|
||||
ForeColor = able.Color,
|
||||
Text = able.Name
|
||||
};
|
||||
equItem.Click += (o, e) =>
|
||||
{
|
||||
if (equConvert.UngraphWhenConvertedToEquation) Ungraph(able);
|
||||
Graph(equConvert.ToEquation());
|
||||
};
|
||||
MenuConvertEquation.DropDownItems.Add(equItem);
|
||||
}
|
||||
if (able is IConvertSlopeField sfConvert)
|
||||
{
|
||||
ToolStripMenuItem sfItem = new()
|
||||
{
|
||||
ForeColor = able.Color,
|
||||
Text = able.Name
|
||||
};
|
||||
sfItem.Click += (o, e) =>
|
||||
{
|
||||
if (sfConvert.UngraphWhenConvertedToSlopeField) Ungraph(able);
|
||||
Graph(sfConvert.ToSlopeField(2));
|
||||
};
|
||||
MenuConvertSlopeField.DropDownItems.Add(sfItem);
|
||||
}
|
||||
if (able is ITranslatable translatable)
|
||||
{
|
||||
ToolStripMenuItem transItem = new()
|
||||
{
|
||||
ForeColor = able.Color,
|
||||
Text = able.Name
|
||||
};
|
||||
transItem.Click += (o, e) => ElementsOperationsTranslate_Click(able, translatable);
|
||||
MenuOperationsTranslate.DropDownItems.Add(transItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ButtonViewportSetZoom_Click(object? sender, EventArgs e)
|
||||
{
|
||||
SetZoomForm picker = new(this)
|
||||
if (setZoomForm is not null)
|
||||
{
|
||||
setZoomForm.Focus();
|
||||
return;
|
||||
}
|
||||
|
||||
SetZoomForm zoomForm = new(this)
|
||||
{
|
||||
StartPosition = FormStartPosition.Manual,
|
||||
};
|
||||
picker.Location = new Point(Location.X + ClientRectangle.Width + 10,
|
||||
Location.Y + (ClientRectangle.Height - picker.ClientRectangle.Height) / 2);
|
||||
picker.ShowDialog();
|
||||
zoomForm.Location = new Point(Location.X + ClientRectangle.Width + 10,
|
||||
Location.Y + (ClientRectangle.Height - zoomForm.ClientRectangle.Height) / 2);
|
||||
|
||||
if (zoomForm.Location.X + zoomForm.Width > Screen.FromControl(this).WorkingArea.Width)
|
||||
{
|
||||
zoomForm.StartPosition = FormStartPosition.WindowsDefaultLocation;
|
||||
}
|
||||
|
||||
setZoomForm = zoomForm;
|
||||
zoomForm.Show();
|
||||
zoomForm.FormClosing += (o, e) =>
|
||||
{
|
||||
zoomForm.CompleteBoxSelection();
|
||||
setZoomForm = null;
|
||||
};
|
||||
}
|
||||
private void ButtonViewportSetCenter_Click(object? sender, EventArgs e)
|
||||
{
|
||||
@ -402,7 +620,7 @@ public partial class GraphForm : Form
|
||||
private void ButtonViewportReset_Click(object? sender, EventArgs e)
|
||||
{
|
||||
ScreenCenter = new Float2(0, 0);
|
||||
ZoomLevel = 1;
|
||||
ZoomLevel = new(1, 1);
|
||||
Invalidate(false);
|
||||
}
|
||||
private void ButtonViewportResetWindow_Click(object? sender, EventArgs e)
|
||||
@ -412,12 +630,30 @@ public partial class GraphForm : Form
|
||||
WindowState = FormWindowState.Normal;
|
||||
}
|
||||
|
||||
public void ResetAllViewport()
|
||||
{
|
||||
ScreenCenter = new Float2(0, 0);
|
||||
ZoomLevel = new(1, 1);
|
||||
Location = initialWindowPos;
|
||||
Size = initialWindowSize;
|
||||
WindowState = FormWindowState.Normal;
|
||||
Invalidate(false);
|
||||
}
|
||||
|
||||
private ViewCacheForm? cacheForm;
|
||||
private void MenuMiscCaches_Click(object? sender, EventArgs e)
|
||||
{
|
||||
if (this.cacheForm is not null)
|
||||
{
|
||||
this.cacheForm.Focus();
|
||||
return;
|
||||
}
|
||||
|
||||
ViewCacheForm cacheForm = new(this)
|
||||
{
|
||||
StartPosition = FormStartPosition.Manual
|
||||
};
|
||||
this.cacheForm = cacheForm;
|
||||
|
||||
cacheForm.Location = new Point(Location.X + ClientRectangle.Width + 10,
|
||||
Location.Y + (ClientRectangle.Height - cacheForm.ClientRectangle.Height) / 2);
|
||||
@ -429,7 +665,7 @@ public partial class GraphForm : Form
|
||||
cacheForm.TopMost = true;
|
||||
cacheForm.Show();
|
||||
}
|
||||
private void MiscMenuPreload_Click(object sender, EventArgs e)
|
||||
private void MiscMenuPreload_Click(object? sender, EventArgs e)
|
||||
{
|
||||
Float2 min = MinVisibleGraph, max = MaxVisibleGraph;
|
||||
Float2 add = new(max.x - min.x, max.y - min.y);
|
||||
@ -446,4 +682,95 @@ public partial class GraphForm : Form
|
||||
foreach (Graphable able in Graphables) able.Preload(xRange, yRange, step);
|
||||
Invalidate(false);
|
||||
}
|
||||
private void UpdaterPopupCloseButton_Click(object? sender, EventArgs e)
|
||||
{
|
||||
UpdaterPopup.Dispose();
|
||||
}
|
||||
|
||||
private void ElementsOperationsTranslate_Click(Graphable ableRaw, ITranslatable ableTrans)
|
||||
{
|
||||
TranslateForm shifter = new(this, ableRaw, ableTrans)
|
||||
{
|
||||
StartPosition = FormStartPosition.Manual,
|
||||
};
|
||||
shifter.Location = new Point(Location.X + ClientRectangle.Width + 10,
|
||||
Location.Y + (ClientRectangle.Height - shifter.ClientRectangle.Height) / 2);
|
||||
if (shifter.Location.X + shifter.Width > Screen.FromControl(this).WorkingArea.Width)
|
||||
{
|
||||
shifter.StartPosition = FormStartPosition.WindowsDefaultLocation;
|
||||
}
|
||||
shifter.Show();
|
||||
}
|
||||
|
||||
private async void RunUpdateChecker()
|
||||
{
|
||||
try
|
||||
{
|
||||
HttpClient http = new();
|
||||
HttpRequestMessage request = new(HttpMethod.Get, "https://api.github.com/repos/That-One-Nerd/Graphing/releases");
|
||||
request.Headers.Add("User-Agent", "ThatOneNerd.Graphing-Update-Checker");
|
||||
|
||||
HttpResponseMessage result = await http.SendAsync(request);
|
||||
if (!result.IsSuccessStatusCode)
|
||||
{
|
||||
Console.WriteLine($"Failed to check for updates.");
|
||||
return;
|
||||
}
|
||||
|
||||
JsonArray arr = JsonSerializer.Deserialize<JsonArray>(await result.Content.ReadAsStreamAsync())!;
|
||||
JsonObject latest = arr[0]!.AsObject();
|
||||
|
||||
Version curVersion = Version.Parse(Assembly.GetAssembly(typeof(GraphForm))!.FullName!.Split(',')[1].Trim()[8..^2]);
|
||||
Version newVersion = Version.Parse(latest["tag_name"]!.GetValue<string>());
|
||||
|
||||
if (newVersion > curVersion)
|
||||
{
|
||||
string type;
|
||||
|
||||
if (newVersion.Major > curVersion.Major || // x.0.0
|
||||
newVersion.Minor > curVersion.Minor) // 0.x.0
|
||||
{
|
||||
type = "major";
|
||||
UpdaterPopupMessage.ForeColor = MajorUpdateColor;
|
||||
}
|
||||
else // 0.0.x
|
||||
{
|
||||
type = "minor";
|
||||
UpdaterPopupMessage.ForeColor = MinorUpdateColor;
|
||||
}
|
||||
|
||||
UpdaterPopupMessage.Text = $"A {type} update is available!\n{curVersion} → {newVersion}";
|
||||
UpdaterPopup.Visible = true;
|
||||
|
||||
string url = latest["html_url"]!.GetValue<string>();
|
||||
Console.WriteLine($"An update is available! {curVersion} -> {newVersion}\n{url}");
|
||||
UpdaterPopupDownloadButton.Click += (o, e) =>
|
||||
{
|
||||
ProcessStartInfo website = new()
|
||||
{
|
||||
FileName = url,
|
||||
UseShellExecute = true
|
||||
};
|
||||
Process.Start(website);
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("Up-to-date.");
|
||||
UpdaterPopup.Dispose();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Failed to check for updates:\n{ex}");
|
||||
}
|
||||
}
|
||||
|
||||
private enum SelectionState
|
||||
{
|
||||
None = 0,
|
||||
ViewportDrag,
|
||||
GraphSelect,
|
||||
ZoomBox,
|
||||
}
|
||||
}
|
||||
|
||||
200
Base/Forms/SetZoomForm.Designer.cs
generated
200
Base/Forms/SetZoomForm.Designer.cs
generated
@ -1,7 +1,4 @@
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace Graphing.Forms
|
||||
namespace Graphing.Forms
|
||||
{
|
||||
partial class SetZoomForm
|
||||
{
|
||||
@ -31,90 +28,157 @@ namespace Graphing.Forms
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
MessageLabel = new Label();
|
||||
ZoomTrackBar = new TrackBar();
|
||||
ValueLabel = new Label();
|
||||
ZoomMinValue = new TextBox();
|
||||
ZoomMaxValue = new TextBox();
|
||||
((System.ComponentModel.ISupportInitialize)ZoomTrackBar).BeginInit();
|
||||
EnableBoxSelect = new System.Windows.Forms.Button();
|
||||
MatchAspectButton = new System.Windows.Forms.Button();
|
||||
ResetButton = new System.Windows.Forms.Button();
|
||||
NormalizeButton = new System.Windows.Forms.Button();
|
||||
MinBoxX = new System.Windows.Forms.TextBox();
|
||||
TextX = new System.Windows.Forms.Label();
|
||||
MaxBoxX = new System.Windows.Forms.TextBox();
|
||||
MaxBoxY = new System.Windows.Forms.TextBox();
|
||||
TextY = new System.Windows.Forms.Label();
|
||||
MinBoxY = new System.Windows.Forms.TextBox();
|
||||
ViewportLock = new System.Windows.Forms.CheckBox();
|
||||
SuspendLayout();
|
||||
//
|
||||
// MessageLabel
|
||||
// EnableBoxSelect
|
||||
//
|
||||
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;
|
||||
EnableBoxSelect.Location = new System.Drawing.Point(12, 12);
|
||||
EnableBoxSelect.Name = "EnableBoxSelect";
|
||||
EnableBoxSelect.Size = new System.Drawing.Size(187, 46);
|
||||
EnableBoxSelect.TabIndex = 0;
|
||||
EnableBoxSelect.Text = "Box Select";
|
||||
EnableBoxSelect.UseVisualStyleBackColor = true;
|
||||
EnableBoxSelect.Click += EnableBoxSelect_Click;
|
||||
//
|
||||
// ZoomTrackBar
|
||||
// MatchAspectButton
|
||||
//
|
||||
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;
|
||||
MatchAspectButton.Location = new System.Drawing.Point(12, 64);
|
||||
MatchAspectButton.Name = "MatchAspectButton";
|
||||
MatchAspectButton.Size = new System.Drawing.Size(187, 46);
|
||||
MatchAspectButton.TabIndex = 1;
|
||||
MatchAspectButton.Text = "Match Aspect";
|
||||
MatchAspectButton.UseVisualStyleBackColor = true;
|
||||
MatchAspectButton.Click += MatchAspectButton_Click;
|
||||
//
|
||||
// ValueLabel
|
||||
// ResetButton
|
||||
//
|
||||
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;
|
||||
ResetButton.Location = new System.Drawing.Point(12, 168);
|
||||
ResetButton.Name = "ResetButton";
|
||||
ResetButton.Size = new System.Drawing.Size(187, 46);
|
||||
ResetButton.TabIndex = 2;
|
||||
ResetButton.Text = "Reset";
|
||||
ResetButton.UseVisualStyleBackColor = true;
|
||||
ResetButton.Click += ResetButton_Click;
|
||||
//
|
||||
// ZoomMinValue
|
||||
// NormalizeButton
|
||||
//
|
||||
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;
|
||||
NormalizeButton.Location = new System.Drawing.Point(12, 116);
|
||||
NormalizeButton.Name = "NormalizeButton";
|
||||
NormalizeButton.Size = new System.Drawing.Size(187, 46);
|
||||
NormalizeButton.TabIndex = 3;
|
||||
NormalizeButton.Text = "Normalize";
|
||||
NormalizeButton.UseVisualStyleBackColor = true;
|
||||
NormalizeButton.Click += NormalizeButton_Click;
|
||||
//
|
||||
// ZoomMaxValue
|
||||
// MinBoxX
|
||||
//
|
||||
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;
|
||||
MinBoxX.Location = new System.Drawing.Point(227, 49);
|
||||
MinBoxX.Margin = new System.Windows.Forms.Padding(25, 3, 0, 3);
|
||||
MinBoxX.Name = "MinBoxX";
|
||||
MinBoxX.Size = new System.Drawing.Size(108, 39);
|
||||
MinBoxX.TabIndex = 4;
|
||||
//
|
||||
// TextX
|
||||
//
|
||||
TextX.Location = new System.Drawing.Point(335, 49);
|
||||
TextX.Margin = new System.Windows.Forms.Padding(0);
|
||||
TextX.Name = "TextX";
|
||||
TextX.Size = new System.Drawing.Size(77, 39);
|
||||
TextX.TabIndex = 5;
|
||||
TextX.Text = "≤ x ≤";
|
||||
TextX.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
|
||||
//
|
||||
// MaxBoxX
|
||||
//
|
||||
MaxBoxX.Location = new System.Drawing.Point(412, 49);
|
||||
MaxBoxX.Margin = new System.Windows.Forms.Padding(0, 3, 25, 3);
|
||||
MaxBoxX.Name = "MaxBoxX";
|
||||
MaxBoxX.Size = new System.Drawing.Size(108, 39);
|
||||
MaxBoxX.TabIndex = 6;
|
||||
//
|
||||
// MaxBoxY
|
||||
//
|
||||
MaxBoxY.Location = new System.Drawing.Point(412, 94);
|
||||
MaxBoxY.Margin = new System.Windows.Forms.Padding(0, 3, 25, 3);
|
||||
MaxBoxY.Name = "MaxBoxY";
|
||||
MaxBoxY.Size = new System.Drawing.Size(108, 39);
|
||||
MaxBoxY.TabIndex = 9;
|
||||
//
|
||||
// TextY
|
||||
//
|
||||
TextY.Location = new System.Drawing.Point(335, 94);
|
||||
TextY.Margin = new System.Windows.Forms.Padding(0);
|
||||
TextY.Name = "TextY";
|
||||
TextY.Size = new System.Drawing.Size(77, 39);
|
||||
TextY.TabIndex = 8;
|
||||
TextY.Text = "≤ y ≤";
|
||||
TextY.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
|
||||
//
|
||||
// MinBoxY
|
||||
//
|
||||
MinBoxY.Location = new System.Drawing.Point(227, 94);
|
||||
MinBoxY.Margin = new System.Windows.Forms.Padding(25, 3, 0, 3);
|
||||
MinBoxY.Name = "MinBoxY";
|
||||
MinBoxY.Size = new System.Drawing.Size(108, 39);
|
||||
MinBoxY.TabIndex = 7;
|
||||
//
|
||||
// ViewportLock
|
||||
//
|
||||
ViewportLock.Location = new System.Drawing.Point(227, 139);
|
||||
ViewportLock.Name = "ViewportLock";
|
||||
ViewportLock.Size = new System.Drawing.Size(293, 39);
|
||||
ViewportLock.TabIndex = 10;
|
||||
ViewportLock.Text = "Lock Viewport";
|
||||
ViewportLock.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
|
||||
ViewportLock.UseVisualStyleBackColor = true;
|
||||
ViewportLock.CheckedChanged += ViewportLock_CheckedChanged;
|
||||
//
|
||||
// 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;
|
||||
AutoScaleDimensions = new System.Drawing.SizeF(13F, 32F);
|
||||
AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
ClientSize = new System.Drawing.Size(533, 227);
|
||||
Controls.Add(ViewportLock);
|
||||
Controls.Add(MaxBoxY);
|
||||
Controls.Add(TextY);
|
||||
Controls.Add(MinBoxY);
|
||||
Controls.Add(MaxBoxX);
|
||||
Controls.Add(TextX);
|
||||
Controls.Add(MinBoxX);
|
||||
Controls.Add(NormalizeButton);
|
||||
Controls.Add(ResetButton);
|
||||
Controls.Add(MatchAspectButton);
|
||||
Controls.Add(EnableBoxSelect);
|
||||
FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow;
|
||||
Name = "SetZoomForm";
|
||||
Text = "Zoom Level";
|
||||
((System.ComponentModel.ISupportInitialize)ZoomTrackBar).EndInit();
|
||||
Text = "Set Viewport Zoom";
|
||||
ResumeLayout(false);
|
||||
PerformLayout();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private Label MessageLabel;
|
||||
private TrackBar ZoomTrackBar;
|
||||
private Label ValueLabel;
|
||||
private TextBox ZoomMinValue;
|
||||
private TextBox ZoomMaxValue;
|
||||
private System.Windows.Forms.Button EnableBoxSelect;
|
||||
private System.Windows.Forms.Button MatchAspectButton;
|
||||
private System.Windows.Forms.Button ResetButton;
|
||||
private System.Windows.Forms.Button NormalizeButton;
|
||||
private System.Windows.Forms.TextBox MinBoxX;
|
||||
private System.Windows.Forms.Label TextX;
|
||||
private System.Windows.Forms.TextBox MaxBoxX;
|
||||
private System.Windows.Forms.TextBox MaxBoxY;
|
||||
private System.Windows.Forms.Label TextY;
|
||||
private System.Windows.Forms.TextBox MinBoxY;
|
||||
private System.Windows.Forms.CheckBox ViewportLock;
|
||||
}
|
||||
}
|
||||
@ -5,118 +5,223 @@ namespace Graphing.Forms;
|
||||
|
||||
public partial class SetZoomForm : Form
|
||||
{
|
||||
private double minZoomRange;
|
||||
private double maxZoomRange;
|
||||
private readonly GraphForm refForm;
|
||||
|
||||
private double zoomLevel;
|
||||
private bool boxSelectEnabled;
|
||||
|
||||
private readonly GraphForm form;
|
||||
|
||||
public SetZoomForm(GraphForm form)
|
||||
public SetZoomForm(GraphForm refForm)
|
||||
{
|
||||
InitializeComponent();
|
||||
this.refForm = refForm;
|
||||
|
||||
minZoomRange = 1 / (form.ZoomLevel * 2);
|
||||
maxZoomRange = 2 / form.ZoomLevel;
|
||||
zoomLevel = 1 / form.ZoomLevel;
|
||||
refForm.Paint += (o, e) => RedeclareValues();
|
||||
RedeclareValues();
|
||||
|
||||
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
|
||||
MinBoxX.Leave += MinBoxX_Finish;
|
||||
MinBoxX.KeyDown += (o, e) =>
|
||||
{
|
||||
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();
|
||||
}
|
||||
if (e.KeyCode == Keys.Enter) MinBoxX_Finish(o, e);
|
||||
};
|
||||
MaxBoxX.Leave += MaxBoxX_Finish;
|
||||
MaxBoxX.KeyDown += (o, e) =>
|
||||
{
|
||||
if (e.KeyCode == Keys.Enter) MaxBoxX_Finish(o, e);
|
||||
};
|
||||
|
||||
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);
|
||||
MinBoxY.Leave += MinBoxY_Finish;
|
||||
MinBoxY.KeyDown += (o, e) =>
|
||||
{
|
||||
if (e.KeyCode == Keys.Enter) MinBoxY_Finish(o, e);
|
||||
};
|
||||
MaxBoxY.Leave += MaxBoxY_Finish;
|
||||
MaxBoxY.KeyDown += (o, e) =>
|
||||
{
|
||||
if (e.KeyCode == Keys.Enter) MaxBoxY_Finish(o, e);
|
||||
};
|
||||
}
|
||||
|
||||
zoomLevel = newZoom;
|
||||
if (newZoom != factor) Invalidate(true);
|
||||
private void EnableBoxSelect_Click(object? sender, EventArgs e)
|
||||
{
|
||||
boxSelectEnabled = !boxSelectEnabled;
|
||||
refForm.canBoxSelect = boxSelectEnabled;
|
||||
|
||||
if (boxSelectEnabled)
|
||||
{
|
||||
EnableBoxSelect.Text = $"Cancel ...";
|
||||
refForm.Focus();
|
||||
}
|
||||
catch
|
||||
else
|
||||
{
|
||||
minZoomRange = original;
|
||||
ZoomMinValue.Text = minZoomRange.ToString("0.00");
|
||||
EnableBoxSelect.Text = "Box Select";
|
||||
}
|
||||
}
|
||||
|
||||
private void ZoomMaxValue_TextChanged(object sender, EventArgs e)
|
||||
private void MatchAspectButton_Click(object? sender, EventArgs e)
|
||||
{
|
||||
double original = maxZoomRange;
|
||||
try
|
||||
double zoomXFactor = refForm.ZoomLevel.x / refForm.ZoomLevel.y;
|
||||
double actualXFactor = refForm.ClientRectangle.Width / refForm.ClientRectangle.Height;
|
||||
|
||||
double diff = actualXFactor / zoomXFactor;
|
||||
int newWidth = (int)(refForm.Width / diff);
|
||||
refForm.ZoomLevel = new(refForm.ZoomLevel.x * diff, refForm.ZoomLevel.y);
|
||||
|
||||
int maxScreenWidth = Screen.FromControl(refForm).WorkingArea.Width;
|
||||
if (newWidth >= maxScreenWidth)
|
||||
{
|
||||
double value;
|
||||
if (string.IsNullOrWhiteSpace(ZoomMaxValue.Text) ||
|
||||
ZoomMaxValue.Text.EndsWith('.'))
|
||||
refForm.Location = new(0, refForm.Location.Y);
|
||||
|
||||
double xScaleFactor = (double)maxScreenWidth / newWidth;
|
||||
newWidth = maxScreenWidth;
|
||||
refForm.Height = (int)(refForm.Height * xScaleFactor);
|
||||
refForm.ZoomLevel = new(refForm.ZoomLevel.x * xScaleFactor, refForm.ZoomLevel.y * xScaleFactor);
|
||||
}
|
||||
|
||||
refForm.Width = newWidth;
|
||||
}
|
||||
private void NormalizeButton_Click(object? sender, EventArgs e)
|
||||
{
|
||||
double factor = 1 / Math.Min(refForm.ZoomLevel.x, refForm.ZoomLevel.y);
|
||||
refForm.ZoomLevel = new(factor * refForm.ZoomLevel.x, factor * refForm.ZoomLevel.y);
|
||||
}
|
||||
private void ResetButton_Click(object? sender, EventArgs e)
|
||||
{
|
||||
refForm.ResetAllViewport();
|
||||
}
|
||||
private void ViewportLock_CheckedChanged(object? sender, EventArgs e)
|
||||
{
|
||||
refForm.ViewportLocked = ViewportLock.Checked;
|
||||
RedeclareValues();
|
||||
}
|
||||
|
||||
private void MinBoxX_Finish(object? sender, EventArgs e)
|
||||
{
|
||||
if (double.TryParse(MinBoxX.Text, out double minX))
|
||||
{
|
||||
Float2 min = refForm.MinVisibleGraph, max = refForm.MaxVisibleGraph;
|
||||
|
||||
if (minX > max.x)
|
||||
{
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
value = double.Parse(ZoomMaxValue.Text);
|
||||
if (value < 1e-2 || value > 1e3 || value < minZoomRange) throw new();
|
||||
MaxBoxX.Text = MinBoxX.Text;
|
||||
MaxBoxX_Finish(sender, e);
|
||||
minX = max.x;
|
||||
|
||||
// Redefine bounds.
|
||||
min = refForm.MinVisibleGraph;
|
||||
max = refForm.MaxVisibleGraph;
|
||||
}
|
||||
|
||||
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);
|
||||
double newCenterX = (minX + max.x) / 2,
|
||||
zoomFactorX = (max.x - minX) / (max.x - min.x);
|
||||
|
||||
zoomLevel = newZoom;
|
||||
if (newZoom != factor) Invalidate(true);
|
||||
refForm.ScreenCenter = new(newCenterX, refForm.ScreenCenter.y);
|
||||
refForm.ZoomLevel = new(refForm.ZoomLevel.x * zoomFactorX, refForm.ZoomLevel.y);
|
||||
}
|
||||
catch
|
||||
|
||||
refForm.Invalidate(false);
|
||||
}
|
||||
private void MaxBoxX_Finish(object? sender, EventArgs e)
|
||||
{
|
||||
if (double.TryParse(MaxBoxX.Text, out double maxX))
|
||||
{
|
||||
maxZoomRange = original;
|
||||
ZoomMaxValue.Text = maxZoomRange.ToString("0.00");
|
||||
Float2 min = refForm.MinVisibleGraph, max = refForm.MaxVisibleGraph;
|
||||
|
||||
if (maxX < min.x)
|
||||
{
|
||||
MinBoxX.Text = MaxBoxX.Text;
|
||||
MinBoxX_Finish(sender, e);
|
||||
maxX = min.x;
|
||||
|
||||
// Redefine bounds.
|
||||
min = refForm.MinVisibleGraph;
|
||||
max = refForm.MaxVisibleGraph;
|
||||
}
|
||||
|
||||
double newCenterX = (min.x + maxX) / 2,
|
||||
zoomFactorX = (maxX - min.x) / (max.x - min.x);
|
||||
|
||||
refForm.ScreenCenter = new(newCenterX, refForm.ScreenCenter.y);
|
||||
refForm.ZoomLevel = new(refForm.ZoomLevel.x * zoomFactorX, refForm.ZoomLevel.y);
|
||||
}
|
||||
|
||||
refForm.Invalidate(false);
|
||||
}
|
||||
private void MinBoxY_Finish(object? sender, EventArgs e)
|
||||
{
|
||||
if (double.TryParse(MinBoxY.Text, out double minY))
|
||||
{
|
||||
Float2 min = refForm.MinVisibleGraph, max = refForm.MaxVisibleGraph;
|
||||
|
||||
if (minY > max.y)
|
||||
{
|
||||
MaxBoxY.Text = MinBoxY.Text;
|
||||
MaxBoxY_Finish(sender, e);
|
||||
minY = max.y;
|
||||
|
||||
// Redefine bounds.
|
||||
min = refForm.MinVisibleGraph;
|
||||
max = refForm.MaxVisibleGraph;
|
||||
}
|
||||
|
||||
double newCenterY = -(minY + max.y) / 2, // Keeping it positive flips it for some reason ???
|
||||
zoomFactorY = (max.y - minY) / (max.y - min.y);
|
||||
|
||||
refForm.ScreenCenter = new(refForm.ScreenCenter.x, newCenterY);
|
||||
refForm.ZoomLevel = new(refForm.ZoomLevel.x, refForm.ZoomLevel.y * zoomFactorY);
|
||||
}
|
||||
|
||||
refForm.Invalidate(false);
|
||||
}
|
||||
private void MaxBoxY_Finish(object? sender, EventArgs e)
|
||||
{
|
||||
if (double.TryParse(MaxBoxY.Text, out double maxY))
|
||||
{
|
||||
Float2 min = refForm.MinVisibleGraph, max = refForm.MaxVisibleGraph;
|
||||
|
||||
if (maxY < min.y)
|
||||
{
|
||||
MinBoxY.Text = MaxBoxY.Text;
|
||||
MinBoxY_Finish(sender, e);
|
||||
maxY = min.y;
|
||||
|
||||
// Redefine bounds.
|
||||
min = refForm.MinVisibleGraph;
|
||||
max = refForm.MaxVisibleGraph;
|
||||
}
|
||||
|
||||
double newCenterY = -(min.y + maxY) / 2, // Keeping it positive flips it for some reason ???
|
||||
zoomFactorY = (maxY - min.y) / (max.y - min.y);
|
||||
|
||||
refForm.ScreenCenter = new(refForm.ScreenCenter.x, newCenterY);
|
||||
refForm.ZoomLevel = new(refForm.ZoomLevel.x, refForm.ZoomLevel.y * zoomFactorY);
|
||||
}
|
||||
|
||||
refForm.Invalidate(false);
|
||||
}
|
||||
|
||||
public void RedeclareValues()
|
||||
{
|
||||
bool enabled = !refForm.ViewportLocked;
|
||||
|
||||
Float2 minGraph = refForm.MinVisibleGraph,
|
||||
maxGraph = refForm.MaxVisibleGraph;
|
||||
|
||||
MinBoxX.Text = $"{minGraph.x:0.000}";
|
||||
MaxBoxX.Text = $"{maxGraph.x:0.000}";
|
||||
MinBoxY.Text = $"{minGraph.y:0.000}";
|
||||
MaxBoxY.Text = $"{maxGraph.y:0.000}";
|
||||
|
||||
ViewportLock.Checked = !enabled;
|
||||
EnableBoxSelect.Enabled = enabled;
|
||||
MatchAspectButton.Enabled = enabled;
|
||||
NormalizeButton.Enabled = enabled;
|
||||
ResetButton.Enabled = enabled;
|
||||
MinBoxX.Enabled = enabled;
|
||||
MaxBoxX.Enabled = enabled;
|
||||
MinBoxY.Enabled = enabled;
|
||||
MaxBoxY.Enabled = enabled;
|
||||
}
|
||||
|
||||
internal void CompleteBoxSelection()
|
||||
{
|
||||
if (boxSelectEnabled) EnableBoxSelect_Click(null, new());
|
||||
}
|
||||
}
|
||||
|
||||
147
Base/Forms/SlopeFieldDetailForm.Designer.cs
generated
Normal file
147
Base/Forms/SlopeFieldDetailForm.Designer.cs
generated
Normal file
@ -0,0 +1,147 @@
|
||||
namespace Graphing.Forms
|
||||
{
|
||||
partial class SlopeFieldDetailForm
|
||||
{
|
||||
/// <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()
|
||||
{
|
||||
Message = new System.Windows.Forms.Label();
|
||||
TrackSlopeDetail = new System.Windows.Forms.TrackBar();
|
||||
MinDetailBox = new System.Windows.Forms.TextBox();
|
||||
MaxDetailBox = new System.Windows.Forms.TextBox();
|
||||
CurrentDetailBox = new System.Windows.Forms.TextBox();
|
||||
IncrementButton = new System.Windows.Forms.Button();
|
||||
DecrementButton = new System.Windows.Forms.Button();
|
||||
((System.ComponentModel.ISupportInitialize)TrackSlopeDetail).BeginInit();
|
||||
SuspendLayout();
|
||||
//
|
||||
// Message
|
||||
//
|
||||
Message.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
|
||||
Message.Location = new System.Drawing.Point(119, 25);
|
||||
Message.Margin = new System.Windows.Forms.Padding(110);
|
||||
Message.Name = "Message";
|
||||
Message.Size = new System.Drawing.Size(516, 109);
|
||||
Message.TabIndex = 1;
|
||||
Message.Text = "Change the Detail of %name%\r\nA higher value means more lines per unit.";
|
||||
Message.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
|
||||
//
|
||||
// TrackSlopeDetail
|
||||
//
|
||||
TrackSlopeDetail.Anchor = System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
|
||||
TrackSlopeDetail.LargeChange = 250;
|
||||
TrackSlopeDetail.Location = new System.Drawing.Point(59, 158);
|
||||
TrackSlopeDetail.Margin = new System.Windows.Forms.Padding(50);
|
||||
TrackSlopeDetail.Maximum = 1000;
|
||||
TrackSlopeDetail.Name = "TrackSlopeDetail";
|
||||
TrackSlopeDetail.Size = new System.Drawing.Size(636, 90);
|
||||
TrackSlopeDetail.SmallChange = 0;
|
||||
TrackSlopeDetail.TabIndex = 0;
|
||||
TrackSlopeDetail.TickFrequency = 0;
|
||||
TrackSlopeDetail.TickStyle = System.Windows.Forms.TickStyle.Both;
|
||||
TrackSlopeDetail.Scroll += TrackSlopeDetail_Scroll;
|
||||
//
|
||||
// MinDetailBox
|
||||
//
|
||||
MinDetailBox.Anchor = System.Windows.Forms.AnchorStyles.Left;
|
||||
MinDetailBox.Location = new System.Drawing.Point(12, 228);
|
||||
MinDetailBox.Name = "MinDetailBox";
|
||||
MinDetailBox.Size = new System.Drawing.Size(100, 39);
|
||||
MinDetailBox.TabIndex = 2;
|
||||
//
|
||||
// MaxDetailBox
|
||||
//
|
||||
MaxDetailBox.Anchor = System.Windows.Forms.AnchorStyles.Right;
|
||||
MaxDetailBox.Location = new System.Drawing.Point(642, 228);
|
||||
MaxDetailBox.Name = "MaxDetailBox";
|
||||
MaxDetailBox.Size = new System.Drawing.Size(100, 39);
|
||||
MaxDetailBox.TabIndex = 3;
|
||||
MaxDetailBox.TextAlign = System.Windows.Forms.HorizontalAlignment.Right;
|
||||
//
|
||||
// CurrentDetailBox
|
||||
//
|
||||
CurrentDetailBox.Anchor = System.Windows.Forms.AnchorStyles.None;
|
||||
CurrentDetailBox.Location = new System.Drawing.Point(330, 228);
|
||||
CurrentDetailBox.Name = "CurrentDetailBox";
|
||||
CurrentDetailBox.Size = new System.Drawing.Size(100, 39);
|
||||
CurrentDetailBox.TabIndex = 4;
|
||||
CurrentDetailBox.TextAlign = System.Windows.Forms.HorizontalAlignment.Center;
|
||||
//
|
||||
// IncrementButton
|
||||
//
|
||||
IncrementButton.Anchor = System.Windows.Forms.AnchorStyles.None;
|
||||
IncrementButton.Font = new System.Drawing.Font("Segoe UI", 7.875F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, 0);
|
||||
IncrementButton.Location = new System.Drawing.Point(436, 228);
|
||||
IncrementButton.Name = "IncrementButton";
|
||||
IncrementButton.Size = new System.Drawing.Size(40, 40);
|
||||
IncrementButton.TabIndex = 5;
|
||||
IncrementButton.Text = "+";
|
||||
IncrementButton.UseVisualStyleBackColor = true;
|
||||
IncrementButton.Click += IncrementButton_Click;
|
||||
//
|
||||
// DecrementButton
|
||||
//
|
||||
DecrementButton.Anchor = System.Windows.Forms.AnchorStyles.None;
|
||||
DecrementButton.Font = new System.Drawing.Font("Segoe UI", 7.875F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, 0);
|
||||
DecrementButton.Location = new System.Drawing.Point(284, 228);
|
||||
DecrementButton.Name = "DecrementButton";
|
||||
DecrementButton.Size = new System.Drawing.Size(40, 40);
|
||||
DecrementButton.TabIndex = 6;
|
||||
DecrementButton.Text = "-";
|
||||
DecrementButton.UseVisualStyleBackColor = true;
|
||||
DecrementButton.Click += DecrementButton_Click;
|
||||
//
|
||||
// SlopeFieldDetailForm
|
||||
//
|
||||
AutoScaleDimensions = new System.Drawing.SizeF(13F, 32F);
|
||||
AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
ClientSize = new System.Drawing.Size(754, 282);
|
||||
Controls.Add(DecrementButton);
|
||||
Controls.Add(IncrementButton);
|
||||
Controls.Add(CurrentDetailBox);
|
||||
Controls.Add(MaxDetailBox);
|
||||
Controls.Add(MinDetailBox);
|
||||
Controls.Add(Message);
|
||||
Controls.Add(TrackSlopeDetail);
|
||||
FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow;
|
||||
Name = "SlopeFieldDetailForm";
|
||||
Text = "Change Slope Field Detail";
|
||||
((System.ComponentModel.ISupportInitialize)TrackSlopeDetail).EndInit();
|
||||
ResumeLayout(false);
|
||||
PerformLayout();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private System.Windows.Forms.Label Message;
|
||||
private System.Windows.Forms.TrackBar TrackSlopeDetail;
|
||||
private System.Windows.Forms.TextBox MinDetailBox;
|
||||
private System.Windows.Forms.TextBox MaxDetailBox;
|
||||
private System.Windows.Forms.TextBox CurrentDetailBox;
|
||||
private System.Windows.Forms.Button IncrementButton;
|
||||
private System.Windows.Forms.Button DecrementButton;
|
||||
}
|
||||
}
|
||||
130
Base/Forms/SlopeFieldDetailForm.cs
Normal file
130
Base/Forms/SlopeFieldDetailForm.cs
Normal file
@ -0,0 +1,130 @@
|
||||
using Graphing.Graphables;
|
||||
using System;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace Graphing.Forms;
|
||||
|
||||
public partial class SlopeFieldDetailForm : Form
|
||||
{
|
||||
private readonly GraphForm refForm;
|
||||
private readonly SlopeField slopeField;
|
||||
|
||||
private double minDetail, maxDetail;
|
||||
|
||||
public SlopeFieldDetailForm(GraphForm form, SlopeField sf)
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
refForm = form;
|
||||
slopeField = sf;
|
||||
|
||||
refForm.Paint += (o, e) => RedeclareValues();
|
||||
RedeclareValues();
|
||||
|
||||
TrackSlopeDetail.KeyDown += (o, e) =>
|
||||
{
|
||||
if (e.KeyCode == Keys.Right) IncrementButton_Click(o, e);
|
||||
else if (e.KeyCode == Keys.Left) DecrementButton_Click(o, e);
|
||||
};
|
||||
|
||||
MinDetailBox.Leave += MinDetailBox_Finish;
|
||||
MinDetailBox.KeyDown += (o, e) =>
|
||||
{
|
||||
if (e.KeyCode == Keys.Enter) MinDetailBox_Finish(o, e);
|
||||
};
|
||||
MaxDetailBox.Leave += MaxDetailBox_Finish;
|
||||
MaxDetailBox.KeyDown += (o, e) =>
|
||||
{
|
||||
if (e.KeyCode == Keys.Enter) MaxDetailBox_Finish(o, e);
|
||||
};
|
||||
CurrentDetailBox.Leave += CurrentDetailBox_Finish;
|
||||
CurrentDetailBox.KeyDown += (o, e) =>
|
||||
{
|
||||
if (e.KeyCode == Keys.Enter) CurrentDetailBox_Finish(o, e);
|
||||
};
|
||||
|
||||
minDetail = sf.Detail / 2;
|
||||
maxDetail = sf.Detail * 2;
|
||||
|
||||
Message.Text = Message.Text.Replace("%name%", sf.Name);
|
||||
}
|
||||
|
||||
// Exponential interpolations are better than simple lerps here since
|
||||
// we're scaling a multiple rather than an additive.
|
||||
private double Interp(double t)
|
||||
{
|
||||
// This is weird. I don't like the +1s and -1s, I don't think I wrote this right.
|
||||
// But it seems to get the job done.
|
||||
return minDetail + Math.Pow(2, t * Math.Log2(maxDetail - minDetail + 1)) - 1;
|
||||
}
|
||||
private double InverseInterp(double c)
|
||||
{
|
||||
return Math.Log2(c - minDetail + 1) / Math.Log2(maxDetail - minDetail + 1);
|
||||
}
|
||||
|
||||
private void RedeclareValues()
|
||||
{
|
||||
double detail = slopeField.Detail;
|
||||
if (detail < minDetail) minDetail = detail;
|
||||
else if (detail > maxDetail) maxDetail = detail;
|
||||
|
||||
double t = InverseInterp(detail);
|
||||
TrackSlopeDetail.Value = (int)(TrackSlopeDetail.Minimum + t * (TrackSlopeDetail.Maximum - TrackSlopeDetail.Minimum));
|
||||
|
||||
MinDetailBox.Text = $"{minDetail:0.00}";
|
||||
MaxDetailBox.Text = $"{maxDetail:0.00}";
|
||||
CurrentDetailBox.Text = $"{detail:0.00}";
|
||||
}
|
||||
|
||||
private void TrackSlopeDetail_Scroll(object? sender, EventArgs e)
|
||||
{
|
||||
double t = (double)(TrackSlopeDetail.Value - TrackSlopeDetail.Minimum) / (TrackSlopeDetail.Maximum - TrackSlopeDetail.Minimum);
|
||||
double newDetail = Interp(t);
|
||||
|
||||
slopeField.Detail = newDetail;
|
||||
refForm.Invalidate(false);
|
||||
}
|
||||
private void MinDetailBox_Finish(object? sender, EventArgs e)
|
||||
{
|
||||
if (double.TryParse(MinDetailBox.Text, out double newMinDetail))
|
||||
{
|
||||
minDetail = newMinDetail;
|
||||
if (minDetail > slopeField.Detail) slopeField.Detail = newMinDetail;
|
||||
}
|
||||
refForm.Invalidate(false);
|
||||
}
|
||||
private void MaxDetailBox_Finish(object? sender, EventArgs e)
|
||||
{
|
||||
if (double.TryParse(MaxDetailBox.Text, out double newMaxDetail))
|
||||
{
|
||||
maxDetail = newMaxDetail;
|
||||
if (maxDetail < slopeField.Detail) slopeField.Detail = newMaxDetail;
|
||||
}
|
||||
refForm.Invalidate(false);
|
||||
}
|
||||
private void CurrentDetailBox_Finish(object? sender, EventArgs e)
|
||||
{
|
||||
if (double.TryParse(CurrentDetailBox.Text, out double newDetail))
|
||||
{
|
||||
if (newDetail < minDetail) minDetail = newDetail;
|
||||
else if (newDetail > maxDetail) maxDetail = newDetail;
|
||||
slopeField.Detail = newDetail;
|
||||
}
|
||||
refForm.Invalidate(false);
|
||||
}
|
||||
|
||||
private void IncrementButton_Click(object? sender, EventArgs e)
|
||||
{
|
||||
double newDetail = slopeField.Detail * 1.0625f;
|
||||
if (newDetail > maxDetail) maxDetail = newDetail;
|
||||
slopeField.Detail = newDetail;
|
||||
refForm.Invalidate(false);
|
||||
}
|
||||
private void DecrementButton_Click(object? sender, EventArgs e)
|
||||
{
|
||||
double newDetail = slopeField.Detail / 1.0625f;
|
||||
if (newDetail < minDetail) minDetail = newDetail;
|
||||
slopeField.Detail = newDetail;
|
||||
refForm.Invalidate(false);
|
||||
}
|
||||
}
|
||||
120
Base/Forms/SlopeFieldDetailForm.resx
Normal file
120
Base/Forms/SlopeFieldDetailForm.resx
Normal 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>
|
||||
204
Base/Forms/TranslateForm.Designer.cs
generated
Normal file
204
Base/Forms/TranslateForm.Designer.cs
generated
Normal file
@ -0,0 +1,204 @@
|
||||
namespace Graphing.Forms
|
||||
{
|
||||
partial class TranslateForm
|
||||
{
|
||||
/// <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()
|
||||
{
|
||||
TrackX = new System.Windows.Forms.TrackBar();
|
||||
LabelX = new System.Windows.Forms.Label();
|
||||
MinBoxX = new System.Windows.Forms.TextBox();
|
||||
MaxBoxX = new System.Windows.Forms.TextBox();
|
||||
ThisValueX = new System.Windows.Forms.TextBox();
|
||||
ThisValueY = new System.Windows.Forms.TextBox();
|
||||
MaxBoxY = new System.Windows.Forms.TextBox();
|
||||
MinBoxY = new System.Windows.Forms.TextBox();
|
||||
LabelY = new System.Windows.Forms.Label();
|
||||
TrackY = new System.Windows.Forms.TrackBar();
|
||||
TitleLabel = new System.Windows.Forms.Label();
|
||||
((System.ComponentModel.ISupportInitialize)TrackX).BeginInit();
|
||||
((System.ComponentModel.ISupportInitialize)TrackY).BeginInit();
|
||||
SuspendLayout();
|
||||
//
|
||||
// TrackX
|
||||
//
|
||||
TrackX.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
|
||||
TrackX.LargeChange = 250;
|
||||
TrackX.Location = new System.Drawing.Point(15, 193);
|
||||
TrackX.Margin = new System.Windows.Forms.Padding(0);
|
||||
TrackX.Maximum = 1000;
|
||||
TrackX.Name = "TrackX";
|
||||
TrackX.Size = new System.Drawing.Size(644, 90);
|
||||
TrackX.SmallChange = 50;
|
||||
TrackX.TabIndex = 0;
|
||||
TrackX.TabStop = false;
|
||||
TrackX.TickFrequency = 50;
|
||||
TrackX.TickStyle = System.Windows.Forms.TickStyle.Both;
|
||||
TrackX.Value = 1;
|
||||
TrackX.Scroll += TrackX_Scroll;
|
||||
//
|
||||
// LabelX
|
||||
//
|
||||
LabelX.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
|
||||
LabelX.Location = new System.Drawing.Point(15, 157);
|
||||
LabelX.Name = "LabelX";
|
||||
LabelX.Size = new System.Drawing.Size(644, 36);
|
||||
LabelX.TabIndex = 1;
|
||||
LabelX.Text = "X Offset";
|
||||
LabelX.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
|
||||
//
|
||||
// MinBoxX
|
||||
//
|
||||
MinBoxX.Location = new System.Drawing.Point(15, 259);
|
||||
MinBoxX.Name = "MinBoxX";
|
||||
MinBoxX.Size = new System.Drawing.Size(100, 39);
|
||||
MinBoxX.TabIndex = 2;
|
||||
//
|
||||
// MaxBoxX
|
||||
//
|
||||
MaxBoxX.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right;
|
||||
MaxBoxX.Location = new System.Drawing.Point(556, 259);
|
||||
MaxBoxX.Name = "MaxBoxX";
|
||||
MaxBoxX.Size = new System.Drawing.Size(100, 39);
|
||||
MaxBoxX.TabIndex = 3;
|
||||
MaxBoxX.TextAlign = System.Windows.Forms.HorizontalAlignment.Right;
|
||||
//
|
||||
// ThisValueX
|
||||
//
|
||||
ThisValueX.Anchor = System.Windows.Forms.AnchorStyles.Top;
|
||||
ThisValueX.Location = new System.Drawing.Point(289, 259);
|
||||
ThisValueX.Name = "ThisValueX";
|
||||
ThisValueX.Size = new System.Drawing.Size(100, 39);
|
||||
ThisValueX.TabIndex = 4;
|
||||
ThisValueX.TextAlign = System.Windows.Forms.HorizontalAlignment.Center;
|
||||
//
|
||||
// ThisValueY
|
||||
//
|
||||
ThisValueY.Anchor = System.Windows.Forms.AnchorStyles.Top;
|
||||
ThisValueY.Location = new System.Drawing.Point(289, 449);
|
||||
ThisValueY.Name = "ThisValueY";
|
||||
ThisValueY.Size = new System.Drawing.Size(100, 39);
|
||||
ThisValueY.TabIndex = 9;
|
||||
ThisValueY.TextAlign = System.Windows.Forms.HorizontalAlignment.Center;
|
||||
//
|
||||
// MaxBoxY
|
||||
//
|
||||
MaxBoxY.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right;
|
||||
MaxBoxY.Location = new System.Drawing.Point(556, 449);
|
||||
MaxBoxY.Name = "MaxBoxY";
|
||||
MaxBoxY.Size = new System.Drawing.Size(100, 39);
|
||||
MaxBoxY.TabIndex = 8;
|
||||
MaxBoxY.TextAlign = System.Windows.Forms.HorizontalAlignment.Right;
|
||||
//
|
||||
// MinBoxY
|
||||
//
|
||||
MinBoxY.Location = new System.Drawing.Point(15, 449);
|
||||
MinBoxY.Name = "MinBoxY";
|
||||
MinBoxY.Size = new System.Drawing.Size(100, 39);
|
||||
MinBoxY.TabIndex = 7;
|
||||
//
|
||||
// LabelY
|
||||
//
|
||||
LabelY.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
|
||||
LabelY.Location = new System.Drawing.Point(15, 347);
|
||||
LabelY.Name = "LabelY";
|
||||
LabelY.Size = new System.Drawing.Size(644, 36);
|
||||
LabelY.TabIndex = 6;
|
||||
LabelY.Text = "Y Offset";
|
||||
LabelY.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
|
||||
//
|
||||
// TrackY
|
||||
//
|
||||
TrackY.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
|
||||
TrackY.LargeChange = 250;
|
||||
TrackY.Location = new System.Drawing.Point(15, 383);
|
||||
TrackY.Margin = new System.Windows.Forms.Padding(0);
|
||||
TrackY.Maximum = 1000;
|
||||
TrackY.Name = "TrackY";
|
||||
TrackY.Size = new System.Drawing.Size(644, 90);
|
||||
TrackY.SmallChange = 50;
|
||||
TrackY.TabIndex = 5;
|
||||
TrackY.TabStop = false;
|
||||
TrackY.TickFrequency = 50;
|
||||
TrackY.TickStyle = System.Windows.Forms.TickStyle.Both;
|
||||
TrackY.Value = 1;
|
||||
TrackY.Scroll += TrackY_Scroll;
|
||||
//
|
||||
// TitleLabel
|
||||
//
|
||||
TitleLabel.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
|
||||
TitleLabel.Location = new System.Drawing.Point(12, 39);
|
||||
TitleLabel.Name = "TitleLabel";
|
||||
TitleLabel.Padding = new System.Windows.Forms.Padding(0, 0, 0, 18);
|
||||
TitleLabel.Size = new System.Drawing.Size(644, 89);
|
||||
TitleLabel.TabIndex = 10;
|
||||
TitleLabel.Text = "Change the Location of %name%";
|
||||
TitleLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
|
||||
//
|
||||
// TranslateForm
|
||||
//
|
||||
AutoScaleDimensions = new System.Drawing.SizeF(13F, 32F);
|
||||
AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
AutoSize = true;
|
||||
AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink;
|
||||
ClientSize = new System.Drawing.Size(674, 531);
|
||||
Controls.Add(TitleLabel);
|
||||
Controls.Add(ThisValueY);
|
||||
Controls.Add(MaxBoxY);
|
||||
Controls.Add(MinBoxY);
|
||||
Controls.Add(LabelY);
|
||||
Controls.Add(TrackY);
|
||||
Controls.Add(ThisValueX);
|
||||
Controls.Add(MaxBoxX);
|
||||
Controls.Add(MinBoxX);
|
||||
Controls.Add(LabelX);
|
||||
Controls.Add(TrackX);
|
||||
FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow;
|
||||
Name = "TranslateForm";
|
||||
Padding = new System.Windows.Forms.Padding(15);
|
||||
Text = "Herm";
|
||||
TopMost = true;
|
||||
((System.ComponentModel.ISupportInitialize)TrackX).EndInit();
|
||||
((System.ComponentModel.ISupportInitialize)TrackY).EndInit();
|
||||
ResumeLayout(false);
|
||||
PerformLayout();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private System.Windows.Forms.TrackBar TrackX;
|
||||
private System.Windows.Forms.Label LabelX;
|
||||
private System.Windows.Forms.TextBox MinBoxX;
|
||||
private System.Windows.Forms.TextBox MaxBoxX;
|
||||
private System.Windows.Forms.TextBox ThisValueX;
|
||||
private System.Windows.Forms.TextBox ThisValueY;
|
||||
private System.Windows.Forms.TextBox MaxBoxY;
|
||||
private System.Windows.Forms.TextBox MinBoxY;
|
||||
private System.Windows.Forms.Label LabelY;
|
||||
private System.Windows.Forms.TrackBar TrackY;
|
||||
private System.Windows.Forms.Label TitleLabel;
|
||||
}
|
||||
}
|
||||
285
Base/Forms/TranslateForm.cs
Normal file
285
Base/Forms/TranslateForm.cs
Normal file
@ -0,0 +1,285 @@
|
||||
using Graphing.Abstract;
|
||||
using System;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace Graphing.Forms;
|
||||
|
||||
public partial class TranslateForm : Form
|
||||
{
|
||||
private readonly GraphForm refForm;
|
||||
|
||||
// These variables both represent the same graphable.
|
||||
private readonly ITranslatableX? ableTransX;
|
||||
private readonly ITranslatableY? ableTransY;
|
||||
|
||||
private readonly bool useX;
|
||||
private readonly bool useY;
|
||||
|
||||
private double minX, maxX, curX, minY, maxY, curY;
|
||||
|
||||
public TranslateForm(GraphForm graph, Graphable ableRaw, ITranslatable ableTrans)
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
Text = $"Translate {ableRaw.Name}";
|
||||
TitleLabel.Text = $"Adjust Location for {ableRaw.Name}";
|
||||
|
||||
MinBoxX.Leave += (o, e) => UpdateFromMinBoxX();
|
||||
MinBoxX.KeyDown += (o, e) =>
|
||||
{
|
||||
if (e.KeyCode == Keys.Enter) UpdateFromMinBoxX();
|
||||
};
|
||||
MaxBoxX.Leave += (o, e) => UpdateFromMaxBoxX();
|
||||
MaxBoxX.KeyDown += (o, e) =>
|
||||
{
|
||||
if (e.KeyCode == Keys.Enter) UpdateFromMaxBoxX();
|
||||
};
|
||||
ThisValueX.Leave += (o, e) => UpdateFromThisBoxX();
|
||||
ThisValueX.KeyDown += (o, e) =>
|
||||
{
|
||||
if (e.KeyCode == Keys.Enter) UpdateFromThisBoxX();
|
||||
};
|
||||
|
||||
MinBoxY.Leave += (o, e) => UpdateFromMinBoxY();
|
||||
MinBoxY.KeyDown += (o, e) =>
|
||||
{
|
||||
if (e.KeyCode == Keys.Enter) UpdateFromMinBoxY();
|
||||
};
|
||||
MaxBoxY.Leave += (o, e) => UpdateFromMaxBoxY();
|
||||
MaxBoxY.KeyDown += (o, e) =>
|
||||
{
|
||||
if (e.KeyCode == Keys.Enter) UpdateFromMaxBoxY();
|
||||
};
|
||||
ThisValueY.Leave += (o, e) => UpdateFromThisBoxY();
|
||||
ThisValueY.KeyDown += (o, e) =>
|
||||
{
|
||||
if (e.KeyCode == Keys.Enter) UpdateFromThisBoxY();
|
||||
};
|
||||
|
||||
refForm = graph;
|
||||
|
||||
double curX = 0, curY = 0;
|
||||
if (ableTrans is ITranslatableX transX)
|
||||
{
|
||||
useX = true;
|
||||
ableTransX = transX;
|
||||
curX = transX.OffsetX;
|
||||
}
|
||||
else
|
||||
{
|
||||
LabelY.Location = LabelX.Location;
|
||||
TrackY.Location = TrackX.Location;
|
||||
MinBoxY.Location = MinBoxX.Location;
|
||||
MaxBoxY.Location = MaxBoxX.Location;
|
||||
ThisValueY.Location = ThisValueX.Location;
|
||||
|
||||
LabelX.Dispose();
|
||||
TrackX.Dispose();
|
||||
MinBoxX.Dispose();
|
||||
MaxBoxX.Dispose();
|
||||
ThisValueX.Dispose();
|
||||
}
|
||||
|
||||
if (ableTrans is ITranslatableY transY)
|
||||
{
|
||||
useY = true;
|
||||
ableTransY = transY;
|
||||
curY = transY.OffsetY;
|
||||
}
|
||||
else
|
||||
{
|
||||
LabelY.Dispose();
|
||||
TrackY.Dispose();
|
||||
MinBoxY.Dispose();
|
||||
MaxBoxY.Dispose();
|
||||
ThisValueY.Dispose();
|
||||
}
|
||||
|
||||
if (!useX && !useY)
|
||||
{
|
||||
TitleLabel.Text = $"There doesn't seem to be anything you can translate for {ableRaw.Name}.";
|
||||
}
|
||||
|
||||
// TODO: Maybe replace these default limits with what's visible on screen?
|
||||
// Tried it and it got a bit confusing so maybe not.
|
||||
minX = -10;
|
||||
maxX = 10;
|
||||
minY = -10;
|
||||
maxY = 10;
|
||||
|
||||
UpdateFromCurX(curX, false);
|
||||
UpdateFromCurY(curY, false);
|
||||
}
|
||||
|
||||
private void UpdateFromCurX(double newCurX, bool invalidate)
|
||||
{
|
||||
curX = newCurX;
|
||||
if (curX < minX) minX = curX;
|
||||
else if (curX > maxX) maxX = curX;
|
||||
|
||||
int step = (int)(1000 * InverseLerp(minX, maxX, curX));
|
||||
TrackX.Value = step;
|
||||
MinBoxX.Text = $"{minX:0.00}";
|
||||
MaxBoxX.Text = $"{maxX:0.00}";
|
||||
ThisValueX.Text = $"{curX:0.00}";
|
||||
|
||||
if (invalidate) refForm.Invalidate(false);
|
||||
}
|
||||
private void UpdateFromSliderX(bool invalidate)
|
||||
{
|
||||
double t = InverseLerp(0, 1000, TrackX.Value);
|
||||
curX = Lerp(minX, maxX, t);
|
||||
|
||||
ThisValueX.Text = $"{curX:0.00}";
|
||||
ableTransX!.OffsetX = curX;
|
||||
|
||||
if (invalidate) refForm.Invalidate(false);
|
||||
}
|
||||
private void UpdateFromMinBoxX()
|
||||
{
|
||||
if (!double.TryParse(MinBoxX.Text, out double newMin))
|
||||
{
|
||||
MinBoxX.Text = $"{minX:0.00}";
|
||||
return;
|
||||
}
|
||||
minX = newMin;
|
||||
MinBoxX.Text = $"{minX:0.00}";
|
||||
|
||||
if (minX > curX)
|
||||
{
|
||||
curX = minX;
|
||||
ThisValueX.Text = $"{curX:0.00}";
|
||||
ableTransX!.OffsetX = curX;
|
||||
}
|
||||
|
||||
int step = (int)(1000 * InverseLerp(minX, maxX, curX));
|
||||
TrackX.Value = step;
|
||||
|
||||
refForm.Invalidate(false);
|
||||
}
|
||||
private void UpdateFromMaxBoxX()
|
||||
{
|
||||
if (!double.TryParse(MaxBoxX.Text, out double newMax))
|
||||
{
|
||||
MaxBoxX.Text = $"{maxX:0.00}";
|
||||
return;
|
||||
}
|
||||
|
||||
maxX = newMax;
|
||||
MaxBoxX.Text = $"{maxX:0.00}";
|
||||
|
||||
if (maxX < curX)
|
||||
{
|
||||
curX = maxX;
|
||||
ThisValueX.Text = $"{curX:0.00}";
|
||||
ableTransX!.OffsetX = curX;
|
||||
}
|
||||
|
||||
int step = (int)(1000 * InverseLerp(minX, maxX, curX));
|
||||
TrackX.Value = step;
|
||||
|
||||
refForm.Invalidate(false);
|
||||
}
|
||||
private void UpdateFromThisBoxX()
|
||||
{
|
||||
if (!double.TryParse(ThisValueX.Text, out double newCur))
|
||||
{
|
||||
ThisValueX.Text = $"{curX:0.00}";
|
||||
return;
|
||||
}
|
||||
ableTransX!.OffsetX = newCur;
|
||||
UpdateFromCurX(newCur, true);
|
||||
}
|
||||
|
||||
private void UpdateFromCurY(double newCurY, bool invalidate)
|
||||
{
|
||||
curY = newCurY;
|
||||
if (curY < minY) minY = curY;
|
||||
else if (curY > maxY) maxY = curY;
|
||||
|
||||
int step = (int)(1000 * InverseLerp(minY, maxY, curY));
|
||||
TrackY.Value = step;
|
||||
MinBoxY.Text = $"{minY:0.00}";
|
||||
MaxBoxY.Text = $"{maxY:0.00}";
|
||||
ThisValueY.Text = $"{curY:0.00}";
|
||||
|
||||
if (invalidate) refForm.Invalidate(false);
|
||||
}
|
||||
private void UpdateFromSliderY(bool invalidate)
|
||||
{
|
||||
double t = InverseLerp(0, 1000, TrackY.Value);
|
||||
curY = Lerp(minY, maxY, t);
|
||||
|
||||
ThisValueY.Text = $"{curY:0.00}";
|
||||
ableTransY!.OffsetY = curY;
|
||||
|
||||
if (invalidate) refForm.Invalidate(false);
|
||||
}
|
||||
private void UpdateFromMinBoxY()
|
||||
{
|
||||
if (!double.TryParse(MinBoxY.Text, out double newMin))
|
||||
{
|
||||
MinBoxY.Text = $"{minY:0.00}";
|
||||
return;
|
||||
}
|
||||
minY = newMin;
|
||||
MinBoxY.Text = $"{minY:0.00}";
|
||||
|
||||
if (minY > curY)
|
||||
{
|
||||
curY = minY;
|
||||
ThisValueY.Text = $"{curY:0.00}";
|
||||
ableTransY!.OffsetY = curY;
|
||||
}
|
||||
|
||||
int step = (int)(1000 * InverseLerp(minY, maxY, curY));
|
||||
TrackY.Value = step;
|
||||
|
||||
refForm.Invalidate(false);
|
||||
}
|
||||
private void UpdateFromMaxBoxY()
|
||||
{
|
||||
if (!double.TryParse(MaxBoxY.Text, out double newMax))
|
||||
{
|
||||
MaxBoxY.Text = $"{maxY:0.00}";
|
||||
return;
|
||||
}
|
||||
|
||||
maxY = newMax;
|
||||
MaxBoxY.Text = $"{maxY:0.00}";
|
||||
|
||||
if (maxY < curY)
|
||||
{
|
||||
curY = maxY;
|
||||
ThisValueY.Text = $"{curY:0.00}";
|
||||
ableTransY!.OffsetY = curY;
|
||||
}
|
||||
|
||||
int step = (int)(1000 * InverseLerp(minY, maxY, curY));
|
||||
TrackY.Value = step;
|
||||
|
||||
refForm.Invalidate(false);
|
||||
}
|
||||
private void UpdateFromThisBoxY()
|
||||
{
|
||||
if (!double.TryParse(ThisValueY.Text, out double newCur))
|
||||
{
|
||||
ThisValueY.Text = $"{curY:0.00}";
|
||||
return;
|
||||
}
|
||||
ableTransY!.OffsetY = newCur;
|
||||
UpdateFromCurY(newCur, true);
|
||||
}
|
||||
|
||||
private static double Lerp(double a, double b, double t) => a + t * (b - a);
|
||||
private static double InverseLerp(double a, double b, double c) => (c - a) / (b - a);
|
||||
|
||||
private void TrackX_Scroll(object sender, EventArgs e)
|
||||
{
|
||||
UpdateFromSliderX(true);
|
||||
}
|
||||
private void TrackY_Scroll(object sender, EventArgs e)
|
||||
{
|
||||
UpdateFromSliderY(true);
|
||||
}
|
||||
}
|
||||
120
Base/Forms/TranslateForm.resx
Normal file
120
Base/Forms/TranslateForm.resx
Normal 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>
|
||||
@ -33,6 +33,7 @@ public partial class ViewCacheForm : Form
|
||||
foreach (Graphable able in refForm.Graphables)
|
||||
{
|
||||
long thisBytes = able.GetCacheBytes();
|
||||
if (thisBytes == 0) continue;
|
||||
CachePie.Values.Add((able.Color, thisBytes));
|
||||
totalBytes += thisBytes;
|
||||
|
||||
|
||||
@ -30,12 +30,12 @@ public abstract class Graphable
|
||||
|
||||
public abstract IEnumerable<IGraphPart> GetItemsToRender(in GraphForm graph);
|
||||
|
||||
public abstract Graphable DeepCopy();
|
||||
public abstract Graphable ShallowCopy();
|
||||
|
||||
public virtual void EraseCache() { }
|
||||
public virtual long GetCacheBytes() => 0;
|
||||
public virtual void Preload(Float2 xRange, Float2 yRange, double step) { }
|
||||
|
||||
public virtual bool ShouldSelectGraphable(in GraphForm graph, Float2 graphMousePos, double factor) => false;
|
||||
public virtual Float2 GetSelectedPoint(in GraphForm graph, Float2 graphMousePos) => default;
|
||||
public virtual IEnumerable<IGraphPart> GetSelectionItemsToRender(in GraphForm graph, Float2 graphMousePos) => [];
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
using Graphing.Parts;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
|
||||
namespace Graphing.Graphables;
|
||||
@ -23,6 +24,7 @@ public class ColumnTable : Graphable
|
||||
}
|
||||
public ColumnTable(double step, Equation equation, double min, double max)
|
||||
{
|
||||
Color = equation.Color;
|
||||
Name = $"Column Table for {equation.Name}";
|
||||
|
||||
tableXY = [];
|
||||
@ -37,7 +39,7 @@ public class ColumnTable : Graphable
|
||||
|
||||
public override long GetCacheBytes() => 16 * tableXY.Count;
|
||||
|
||||
public override Graphable DeepCopy() => new ColumnTable(width / 0.75, tableXY.ToArray().ToDictionary());
|
||||
public override Graphable ShallowCopy() => new ColumnTable(width / 0.75, tableXY);
|
||||
|
||||
public override IEnumerable<IGraphPart> GetItemsToRender(in GraphForm graph)
|
||||
{
|
||||
@ -45,12 +47,87 @@ public class ColumnTable : Graphable
|
||||
foreach (KeyValuePair<double, double> col in tableXY)
|
||||
{
|
||||
items.Add(GraphRectangle.FromSize(new Float2(col.Key, col.Value / 2),
|
||||
new Float2(width, col.Value)));
|
||||
new Float2(width, col.Value), 0.625));
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
public override bool ShouldSelectGraphable(in GraphForm graph, Float2 graphMousePos, double factor)
|
||||
{
|
||||
// Get closest value to mouse pos.
|
||||
double closestDist = double.PositiveInfinity, closestX = 0, closestY = 0;
|
||||
foreach (KeyValuePair<double, double> points in tableXY)
|
||||
{
|
||||
double dist = Math.Abs(points.Key - graphMousePos.x);
|
||||
if (dist < closestDist)
|
||||
{
|
||||
closestDist = dist;
|
||||
closestX = points.Key;
|
||||
closestY = points.Value;
|
||||
}
|
||||
}
|
||||
|
||||
Int2 screenMousePos = graph.GraphSpaceToScreenSpace(graphMousePos);
|
||||
Int2 minBox = graph.GraphSpaceToScreenSpace(new(closestX - width / 2, 0)),
|
||||
maxBox = graph.GraphSpaceToScreenSpace(new(closestX + width / 2, closestY));
|
||||
|
||||
int distX, distY;
|
||||
if (screenMousePos.x < minBox.x) distX = minBox.x - screenMousePos.x; // On left side.
|
||||
else if (screenMousePos.x > maxBox.x) distX = screenMousePos.x - maxBox.x; // On right side.
|
||||
else distX = 0; // Inside.
|
||||
|
||||
if (closestY > 0)
|
||||
{
|
||||
if (screenMousePos.y > minBox.y) distY = screenMousePos.y - minBox.y; // Underneath.
|
||||
else if (screenMousePos.y < maxBox.y) distY = maxBox.y - screenMousePos.y; // Above.
|
||||
else distY = 0; // Inside.
|
||||
}
|
||||
else
|
||||
{
|
||||
if (screenMousePos.y < minBox.y) distY = minBox.y - screenMousePos.y; // Underneath.
|
||||
else if (screenMousePos.y > maxBox.y) distY = screenMousePos.y - maxBox.y; // Above.
|
||||
else distY = 0; // Inside.
|
||||
}
|
||||
|
||||
int totalDist = (int)Math.Sqrt(distX * distX + distY * distY);
|
||||
return totalDist < 50 * factor * graph.DpiFloat / 192;
|
||||
}
|
||||
public override IEnumerable<IGraphPart> GetSelectionItemsToRender(in GraphForm graph, Float2 graphMousePos)
|
||||
{
|
||||
// Get closest value to mouse pos.
|
||||
double closestDist = double.PositiveInfinity, closestX = 0, closestY = 0;
|
||||
foreach (KeyValuePair<double, double> points in tableXY)
|
||||
{
|
||||
double dist = Math.Abs(points.Key - graphMousePos.x);
|
||||
if (dist < closestDist)
|
||||
{
|
||||
closestDist = dist;
|
||||
closestX = points.Key;
|
||||
closestY = points.Value;
|
||||
}
|
||||
}
|
||||
|
||||
Float2 textPoint = new(closestX, closestY);
|
||||
Int2 offset;
|
||||
ContentAlignment alignment;
|
||||
if (textPoint.y >= 0)
|
||||
{
|
||||
offset = new(0, -5);
|
||||
alignment = ContentAlignment.BottomCenter;
|
||||
}
|
||||
else
|
||||
{
|
||||
offset = new(0, 5);
|
||||
alignment = ContentAlignment.TopCenter;
|
||||
}
|
||||
|
||||
return
|
||||
[
|
||||
new GraphUiText($"{closestY:0.00}", textPoint, alignment, offsetPix: offset)
|
||||
];
|
||||
}
|
||||
|
||||
// Nothing to preload, everything is already cached.
|
||||
public override void Preload(Float2 xRange, Float2 yRange, double step) { }
|
||||
}
|
||||
|
||||
@ -3,16 +3,26 @@ using Graphing.Forms;
|
||||
using Graphing.Parts;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
|
||||
namespace Graphing.Graphables;
|
||||
|
||||
public class Equation : Graphable, IIntegrable, IDerivable
|
||||
public class Equation : Graphable, IIntegrable, IDerivable, ITranslatableXY, IConvertSlopeField,
|
||||
IConvertColumnTable
|
||||
{
|
||||
private static int equationNum;
|
||||
|
||||
public bool UngraphWhenConvertedToColumnTable => false;
|
||||
public bool UngraphWhenConvertedToSlopeField => false;
|
||||
|
||||
public double OffsetX { get; set; }
|
||||
public double OffsetY { get; set; }
|
||||
|
||||
protected readonly EquationDelegate equ;
|
||||
protected readonly List<Float2> cache;
|
||||
|
||||
public event Action<GraphForm> OnInvalidate;
|
||||
|
||||
public Equation(EquationDelegate equ)
|
||||
{
|
||||
equationNum++;
|
||||
@ -20,6 +30,11 @@ public class Equation : Graphable, IIntegrable, IDerivable
|
||||
|
||||
this.equ = equ;
|
||||
cache = [];
|
||||
|
||||
OffsetX = 0;
|
||||
OffsetY = 0;
|
||||
|
||||
OnInvalidate = delegate { };
|
||||
}
|
||||
|
||||
public override IEnumerable<IGraphPart> GetItemsToRender(in GraphForm graph)
|
||||
@ -46,31 +61,44 @@ public class Equation : Graphable, IIntegrable, IDerivable
|
||||
previousX = currentX;
|
||||
previousY = currentY;
|
||||
}
|
||||
OnInvalidate.Invoke(graph);
|
||||
return lines;
|
||||
}
|
||||
|
||||
public Graphable Derive() => new Equation(x =>
|
||||
protected double DerivativeAtPoint(double x)
|
||||
{
|
||||
const double step = 1e-3;
|
||||
return (equ(x + step) - equ(x)) / step;
|
||||
});
|
||||
return (equ(x + step - OffsetX) - equ(x - OffsetX)) / step;
|
||||
}
|
||||
|
||||
public Graphable Derive() => new Equation(DerivativeAtPoint);
|
||||
public Graphable Integrate() => new IntegralEquation(this);
|
||||
|
||||
public EquationDelegate GetDelegate() => equ;
|
||||
|
||||
public SlopeField ToSlopeField(int detail) => new(detail, (x, y) => DerivativeAtPoint(x))
|
||||
{
|
||||
Color = Color,
|
||||
Name = $"Slope Field of {Name}"
|
||||
};
|
||||
public ColumnTable ToColumnTable(double start, double end, int detail)
|
||||
=> new(1.0 / detail, this, start, end);
|
||||
|
||||
public override void EraseCache() => cache.Clear();
|
||||
protected double GetFromCache(double x, double epsilon)
|
||||
{
|
||||
(double dist, double nearest, int index) = NearestCachedPoint(x);
|
||||
if (dist < epsilon) return nearest;
|
||||
(double dist, double nearest, int index) = NearestCachedPoint(x - OffsetX);
|
||||
if (dist < epsilon) return nearest + OffsetY;
|
||||
else
|
||||
{
|
||||
double result = equ(x);
|
||||
cache.Insert(index + 1, new(x, result));
|
||||
return result;
|
||||
double result = equ(x - OffsetX);
|
||||
cache.Insert(index + 1, new(x - OffsetX, result));
|
||||
return result + OffsetY;
|
||||
}
|
||||
}
|
||||
|
||||
public double GetValueAt(double x) => GetFromCache(x, 0);
|
||||
|
||||
protected (double dist, double y, int index) NearestCachedPoint(double x)
|
||||
{
|
||||
if (cache.Count == 0) return (double.PositiveInfinity, double.NaN, -1);
|
||||
@ -103,7 +131,7 @@ public class Equation : Graphable, IIntegrable, IDerivable
|
||||
}
|
||||
}
|
||||
|
||||
public override Graphable DeepCopy() => new Equation(equ);
|
||||
public override Graphable ShallowCopy() => new Equation(equ);
|
||||
|
||||
public override long GetCacheBytes() => cache.Count * 16;
|
||||
|
||||
@ -121,8 +149,15 @@ public class Equation : Graphable, IIntegrable, IDerivable
|
||||
double totalDist = Math.Sqrt(dist.x * dist.x + dist.y * dist.y);
|
||||
return totalDist <= allowedDist;
|
||||
}
|
||||
public override Float2 GetSelectedPoint(in GraphForm graph, Float2 graphMousePos) =>
|
||||
new(graphMousePos.x, GetFromCache(graphMousePos.x, 1e-3));
|
||||
public override IEnumerable<IGraphPart> GetSelectionItemsToRender(in GraphForm graph, Float2 graphMousePos)
|
||||
{
|
||||
Float2 point = new(graphMousePos.x, GetFromCache(graphMousePos.x, 1e-3));
|
||||
return
|
||||
[
|
||||
new GraphUiText($"({point.x:0.00}, {point.y:0.00})", point, ContentAlignment.BottomLeft),
|
||||
new GraphUiCircle(point),
|
||||
];
|
||||
}
|
||||
|
||||
public override void Preload(Float2 xRange, Float2 yRange, double step)
|
||||
{
|
||||
|
||||
96
Base/Graphables/EquationDifference.cs
Normal file
96
Base/Graphables/EquationDifference.cs
Normal file
@ -0,0 +1,96 @@
|
||||
using Graphing.Abstract;
|
||||
using Graphing.Forms;
|
||||
using Graphing.Parts;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
|
||||
namespace Graphing.Graphables;
|
||||
|
||||
public class EquationDifference : Graphable, ITranslatableX, IConvertEquation
|
||||
{
|
||||
public bool UngraphWhenConvertedToEquation => true;
|
||||
|
||||
public double Position
|
||||
{
|
||||
get => _position;
|
||||
set
|
||||
{
|
||||
_position = value;
|
||||
points = new Float2(equA.GetValueAt(value), equB.GetValueAt(value));
|
||||
}
|
||||
}
|
||||
private double _position;
|
||||
|
||||
public double OffsetX
|
||||
{
|
||||
get => Position;
|
||||
set => Position = value;
|
||||
}
|
||||
|
||||
protected readonly Equation equA, equB;
|
||||
protected Float2 points; // X represents equA.y, Y represents equB.y
|
||||
|
||||
public EquationDifference(double position, Equation equA, Equation equB)
|
||||
{
|
||||
this.equA = equA;
|
||||
this.equB = equB;
|
||||
|
||||
Name = $"Difference between {equA.Name} and {equB.Name}";
|
||||
|
||||
Position = position;
|
||||
}
|
||||
|
||||
public override IEnumerable<IGraphPart> GetItemsToRender(in GraphForm graph)
|
||||
{
|
||||
Float2 pA = new(Position, points.x),
|
||||
pB = new(Position, points.y);
|
||||
return
|
||||
[
|
||||
new GraphUiCircle(pA),
|
||||
new GraphUiCircle(pB),
|
||||
new GraphLine(pA, pB)
|
||||
];
|
||||
}
|
||||
|
||||
public double DistanceAtPoint(double x) => equA.GetValueAt(x) - equB.GetValueAt(x);
|
||||
|
||||
public override Graphable ShallowCopy() => new EquationDifference(Position, equA, equB);
|
||||
|
||||
public override bool ShouldSelectGraphable(in GraphForm graph, Float2 graphMousePos, double factor)
|
||||
{
|
||||
Float2 nearestPoint = new(Position, graphMousePos.y);
|
||||
double upper = double.Max(points.x, points.y),
|
||||
lower = double.Min(points.x, points.y);
|
||||
if (nearestPoint.y > upper) nearestPoint.y = upper;
|
||||
else if (nearestPoint.y < lower) nearestPoint.y = lower;
|
||||
|
||||
Int2 nearestPixelPoint = graph.GraphSpaceToScreenSpace(nearestPoint);
|
||||
Int2 screenMousePos = graph.GraphSpaceToScreenSpace(graphMousePos);
|
||||
|
||||
Int2 diff = new(screenMousePos.x - nearestPixelPoint.x,
|
||||
screenMousePos.y - nearestPixelPoint.y);
|
||||
int dist = (int)Math.Sqrt(diff.x * diff.x + diff.y * diff.y);
|
||||
return dist < 50 * factor * graph.DpiFloat / 192;
|
||||
}
|
||||
public override IEnumerable<IGraphPart> GetSelectionItemsToRender(in GraphForm graph, Float2 graphMousePos)
|
||||
{
|
||||
Float2 nearestPoint = new(Position, graphMousePos.y);
|
||||
double upper = double.Max(points.x, points.y),
|
||||
lower = double.Min(points.x, points.y);
|
||||
if (nearestPoint.y > upper) nearestPoint.y = upper;
|
||||
else if (nearestPoint.y < lower) nearestPoint.y = lower;
|
||||
|
||||
return
|
||||
[
|
||||
new GraphUiText($"Δ = {points.x - points.y:0.000}", nearestPoint, ContentAlignment.MiddleLeft, offsetPix: new Int2(15, 0)),
|
||||
new GraphUiCircle(nearestPoint)
|
||||
];
|
||||
}
|
||||
|
||||
public Equation ToEquation() => new(DistanceAtPoint)
|
||||
{
|
||||
Color = Color,
|
||||
Name = Name
|
||||
};
|
||||
}
|
||||
@ -47,7 +47,7 @@ public class IntegralEquation : Graphable, IIntegrable, IDerivable
|
||||
usingAlt = true;
|
||||
}
|
||||
|
||||
public override Graphable DeepCopy() => new IntegralEquation(this);
|
||||
public override Graphable ShallowCopy() => new IntegralEquation(this);
|
||||
|
||||
public override IEnumerable<IGraphPart> GetItemsToRender(in GraphForm graph)
|
||||
{
|
||||
@ -164,7 +164,7 @@ public class IntegralEquation : Graphable, IIntegrable, IDerivable
|
||||
}
|
||||
else
|
||||
{
|
||||
stepY += baseEquDel!(stepX) * dX;
|
||||
stepY += (baseEquDel!(stepX - baseEqu!.OffsetX) + baseEqu.OffsetY) * dX;
|
||||
}
|
||||
}
|
||||
|
||||
@ -178,8 +178,8 @@ public class IntegralEquation : Graphable, IIntegrable, IDerivable
|
||||
|
||||
public Graphable Derive()
|
||||
{
|
||||
if (usingAlt) return altBaseEqu!.DeepCopy();
|
||||
else return (Equation)baseEqu!.DeepCopy();
|
||||
if (usingAlt) return altBaseEqu!.ShallowCopy();
|
||||
else return (Equation)baseEqu!.ShallowCopy();
|
||||
}
|
||||
public Graphable Integrate() => new IntegralEquation(this);
|
||||
|
||||
@ -234,6 +234,6 @@ public class IntegralEquation : Graphable, IIntegrable, IDerivable
|
||||
double totalDist = Math.Sqrt(dist.x * dist.x + dist.y * dist.y);
|
||||
return totalDist <= allowedDist;
|
||||
}
|
||||
public override Float2 GetSelectedPoint(in GraphForm graph, Float2 graphMousePos) =>
|
||||
new(graphMousePos.x, IntegralAtPoint(graphMousePos.x));
|
||||
public override IEnumerable<IGraphPart> GetSelectionItemsToRender(in GraphForm graph, Float2 graphMousePos) =>
|
||||
[new GraphUiCircle(new(graphMousePos.x, IntegralAtPoint(graphMousePos.x)))];
|
||||
}
|
||||
|
||||
134
Base/Graphables/ParametricEquation.cs
Normal file
134
Base/Graphables/ParametricEquation.cs
Normal file
@ -0,0 +1,134 @@
|
||||
using Graphing.Abstract;
|
||||
using Graphing.Forms;
|
||||
using Graphing.Parts;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Graphing.Graphables;
|
||||
|
||||
public class ParametricEquation : Graphable, IDerivable, ITranslatableXY
|
||||
{
|
||||
private static int equationNum;
|
||||
|
||||
public double OffsetX { get; set; }
|
||||
public double OffsetY { get; set; }
|
||||
|
||||
public double InitialT { get; set; }
|
||||
public double FinalT { get; set; }
|
||||
|
||||
protected readonly ParametricDelegate equX, equY;
|
||||
protected readonly List<(double t, Float2 point)> cache;
|
||||
|
||||
public ParametricEquation(double initialT, double finalT,
|
||||
ParametricDelegate equX, ParametricDelegate equY)
|
||||
{
|
||||
equationNum++;
|
||||
Name = $"Parametric Equation {equationNum}";
|
||||
|
||||
InitialT = initialT;
|
||||
FinalT = finalT;
|
||||
|
||||
this.equX = equX;
|
||||
this.equY = equY;
|
||||
cache = [];
|
||||
}
|
||||
|
||||
public override Graphable ShallowCopy() => new ParametricEquation(InitialT, FinalT, equX, equY);
|
||||
|
||||
public override IEnumerable<IGraphPart> GetItemsToRender(in GraphForm graph)
|
||||
{
|
||||
const int step = 10;
|
||||
|
||||
double epsilon = Math.Abs(graph.ScreenSpaceToGraphSpace(new Int2(0, 0)).x
|
||||
- graph.ScreenSpaceToGraphSpace(new Int2(step, 0)).x);
|
||||
|
||||
List<IGraphPart> lines = [];
|
||||
|
||||
Float2 previousPoint = GetFromCache(InitialT, epsilon);
|
||||
for (double t = InitialT; t <= FinalT; t += epsilon)
|
||||
{
|
||||
Float2 currentPoint = GetFromCache(t, epsilon);
|
||||
if (graph.IsGraphPointVisible(currentPoint) ||
|
||||
graph.IsGraphPointVisible(previousPoint))
|
||||
lines.Add(new GraphLine(previousPoint, currentPoint));
|
||||
previousPoint = currentPoint;
|
||||
}
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
public Graphable Derive() =>
|
||||
new ParametricEquation(InitialT, FinalT, GetDerivativeAtPointX, GetDerivativeAtPointY);
|
||||
|
||||
public ParametricDelegate GetXDelegate() => equX;
|
||||
public ParametricDelegate GetYDelegate() => equY;
|
||||
|
||||
public double GetDerivativeAtPointX(double t)
|
||||
{
|
||||
const double step = 1e-3;
|
||||
return (equX(t + step) - equX(t)) / step;
|
||||
}
|
||||
public double GetDerivativeAtPointY(double t)
|
||||
{
|
||||
const double step = 1e-3;
|
||||
return (equY(t + step) - equY(t)) / step;
|
||||
}
|
||||
public Float2 GetDerivativeAtPoint(double t) =>
|
||||
new(GetDerivativeAtPointX(t), GetDerivativeAtPointY(t));
|
||||
|
||||
public Float2 GetPointAt(double t) => GetFromCache(t, 0);
|
||||
|
||||
public override void EraseCache() => cache.Clear();
|
||||
protected Float2 GetFromCache(double t, double epsilon)
|
||||
{
|
||||
(double dist, Float2 nearest, int index) = NearestCachedPoint(t);
|
||||
if (dist < epsilon) return new(nearest.x + OffsetX, nearest.y + OffsetY);
|
||||
else
|
||||
{
|
||||
Float2 result = new(equX(t), equY(t));
|
||||
cache.Insert(index + 1, (t, result));
|
||||
return new(result.x + OffsetX, result.y + OffsetY);
|
||||
}
|
||||
}
|
||||
public override long GetCacheBytes() => cache.Count * 24;
|
||||
|
||||
protected (double dist, Float2 point, int index) NearestCachedPoint(double t)
|
||||
{
|
||||
if (cache.Count <= 1) return (double.PositiveInfinity, new(double.NaN, double.NaN), -1);
|
||||
else if (cache.Count == 1)
|
||||
{
|
||||
(double resultT, Float2 resultPoint) = cache[0];
|
||||
return (Math.Abs(resultT - t), resultPoint, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
int boundA = 0, boundB = cache.Count;
|
||||
do
|
||||
{
|
||||
int boundC = (boundA + boundB) / 2;
|
||||
|
||||
(double thisT, Float2 thisPoint) = cache[boundC];
|
||||
if (thisT == t) return (0, thisPoint, boundC);
|
||||
else if (thisT > t)
|
||||
{
|
||||
boundA = boundC;
|
||||
}
|
||||
else // thisT < t
|
||||
{
|
||||
boundB = boundC;
|
||||
}
|
||||
|
||||
} while (boundB - boundA > 1);
|
||||
|
||||
(double resultT, Float2 resultPoint) = cache[boundA];
|
||||
return (Math.Abs(resultT - t), resultPoint, boundA);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Preload(Float2 xRange, Float2 yRange, double step)
|
||||
{
|
||||
for (double t = InitialT; t <= FinalT; t += step) GetFromCache(t, step);
|
||||
}
|
||||
}
|
||||
|
||||
public delegate double ParametricDelegate(double t);
|
||||
@ -2,6 +2,7 @@
|
||||
using Graphing.Parts;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
|
||||
namespace Graphing.Graphables;
|
||||
|
||||
@ -9,29 +10,50 @@ public class SlopeField : Graphable
|
||||
{
|
||||
private static int slopeFieldNum;
|
||||
|
||||
protected readonly SlopeFieldsDelegate equ;
|
||||
protected readonly int detail;
|
||||
public double Detail
|
||||
{
|
||||
get => _detail;
|
||||
set
|
||||
{
|
||||
if (Math.Abs(value - Detail) >= 1e-4)
|
||||
{
|
||||
// When changing detail, we need to regenerate all
|
||||
// the lines. Inefficient, I know. Might be optimized
|
||||
// in a future update.
|
||||
EraseCache();
|
||||
}
|
||||
_detail = value;
|
||||
}
|
||||
}
|
||||
private double _detail;
|
||||
|
||||
protected readonly SlopeFieldsDelegate equ;
|
||||
protected readonly List<(Float2, GraphLine)> cache;
|
||||
|
||||
public SlopeField(int detail, SlopeFieldsDelegate equ)
|
||||
public SlopeField(double detail, SlopeFieldsDelegate equ)
|
||||
{
|
||||
slopeFieldNum++;
|
||||
Name = $"Slope Field {slopeFieldNum}";
|
||||
|
||||
this.equ = equ;
|
||||
this.detail = detail;
|
||||
_detail = detail;
|
||||
cache = [];
|
||||
}
|
||||
|
||||
public override IEnumerable<IGraphPart> GetItemsToRender(in GraphForm graph)
|
||||
{
|
||||
double epsilon = 1 / (detail * 2.0);
|
||||
double step = 1 / _detail;
|
||||
double epsilon = step * 0.5;
|
||||
List<IGraphPart> lines = [];
|
||||
|
||||
for (double x = Math.Ceiling(graph.MinVisibleGraph.x - 1); x < graph.MaxVisibleGraph.x + 1; x += 1.0 / detail)
|
||||
double minX = Math.Round((graph.MinVisibleGraph.x - 1) / step) * step,
|
||||
maxX = Math.Round((graph.MaxVisibleGraph.x + 1) / step) * step,
|
||||
minY = Math.Round((graph.MinVisibleGraph.y - 1) / step) * step,
|
||||
maxY = Math.Round((graph.MaxVisibleGraph.y + 1) / step) * step;
|
||||
|
||||
for (double x = minX; x < maxX; x += step)
|
||||
{
|
||||
for (double y = Math.Ceiling(graph.MinVisibleGraph.y - 1); y < graph.MaxVisibleGraph.y + 1; y += 1.0 / detail)
|
||||
for (double y = minY; y < maxY; y += step)
|
||||
{
|
||||
lines.Add(GetFromCache(epsilon, x, y));
|
||||
}
|
||||
@ -42,7 +64,7 @@ public class SlopeField : Graphable
|
||||
|
||||
protected GraphLine MakeSlopeLine(Float2 position, double slope)
|
||||
{
|
||||
double size = detail;
|
||||
double size = _detail;
|
||||
|
||||
double dirX = size, dirY = slope * size;
|
||||
double magnitude = Math.Sqrt(dirX * dirX + dirY * dirY);
|
||||
@ -72,17 +94,17 @@ public class SlopeField : Graphable
|
||||
return result;
|
||||
}
|
||||
|
||||
public override Graphable DeepCopy() => new SlopeField(detail, equ);
|
||||
public override Graphable ShallowCopy() => new SlopeField(_detail, equ);
|
||||
|
||||
public override void EraseCache() => cache.Clear();
|
||||
public override long GetCacheBytes() => cache.Count * 48;
|
||||
|
||||
public override bool ShouldSelectGraphable(in GraphForm graph, Float2 graphMousePos, double factor)
|
||||
{
|
||||
Float2 nearestPos = new(Math.Round(graphMousePos.x * detail) / detail,
|
||||
Math.Round(graphMousePos.y * detail) / detail);
|
||||
Float2 nearestPos = new(Math.Round(graphMousePos.x * _detail) / _detail,
|
||||
Math.Round(graphMousePos.y * _detail) / _detail);
|
||||
|
||||
double epsilon = 1 / (detail * 2.0);
|
||||
double epsilon = 1 / (_detail * 2.0);
|
||||
GraphLine line = GetFromCache(epsilon, nearestPos.x, nearestPos.y);
|
||||
double slope = (line.b.y - line.a.y) / (line.b.x - line.a.x);
|
||||
|
||||
@ -101,12 +123,12 @@ public class SlopeField : Graphable
|
||||
double totalDist = Math.Sqrt(dist.x * dist.x + dist.y * dist.y);
|
||||
return totalDist <= allowedDist;
|
||||
}
|
||||
public override Float2 GetSelectedPoint(in GraphForm graph, Float2 graphMousePos)
|
||||
public override IEnumerable<IGraphPart> GetSelectionItemsToRender(in GraphForm graph, Float2 graphMousePos)
|
||||
{
|
||||
Float2 nearestPos = new(Math.Round(graphMousePos.x * detail) / detail,
|
||||
Math.Round(graphMousePos.y * detail) / detail);
|
||||
Float2 nearestPos = new(Math.Round(graphMousePos.x * _detail) / _detail,
|
||||
Math.Round(graphMousePos.y * _detail) / _detail);
|
||||
|
||||
double epsilon = 1 / (detail * 2.0);
|
||||
double epsilon = 1 / (_detail * 2.0);
|
||||
GraphLine line = GetFromCache(epsilon, nearestPos.x, nearestPos.y);
|
||||
double slope = (line.b.y - line.a.y) / (line.b.x - line.a.x);
|
||||
|
||||
@ -114,14 +136,18 @@ public class SlopeField : Graphable
|
||||
lineY = slope * (lineX - nearestPos.x) + nearestPos.y;
|
||||
Float2 point = new(lineX, lineY);
|
||||
|
||||
return point;
|
||||
return
|
||||
[
|
||||
new GraphUiText($"M = {slope:0.000}", point, ContentAlignment.BottomLeft),
|
||||
new GraphUiCircle(point)
|
||||
];
|
||||
}
|
||||
|
||||
public override void Preload(Float2 xRange, Float2 yRange, double step)
|
||||
{
|
||||
for (double x = Math.Ceiling(xRange.x - 1); x < xRange.y + 1; x += 1.0 / detail)
|
||||
for (double x = Math.Ceiling(xRange.x - 1); x < xRange.y + 1; x += 1.0 / _detail)
|
||||
{
|
||||
for (double y = Math.Ceiling(yRange.x - 1); y < yRange.y + 1; y += 1.0 / detail)
|
||||
for (double y = Math.Ceiling(yRange.x - 1); y < yRange.y + 1; y += 1.0 / _detail)
|
||||
{
|
||||
GetFromCache(step, x, y);
|
||||
}
|
||||
|
||||
@ -1,12 +1,16 @@
|
||||
using Graphing.Forms;
|
||||
using Graphing.Abstract;
|
||||
using Graphing.Forms;
|
||||
using Graphing.Parts;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
|
||||
namespace Graphing.Graphables;
|
||||
|
||||
public class TangentLine : Graphable
|
||||
public class TangentLine : Graphable, IConvertEquation, ITranslatableX
|
||||
{
|
||||
public bool UngraphWhenConvertedToEquation => true;
|
||||
|
||||
public double Position
|
||||
{
|
||||
get => _position;
|
||||
@ -18,8 +22,13 @@ public class TangentLine : Graphable
|
||||
}
|
||||
private double _position; // Private because it has exactly the same functionality as `Position`.
|
||||
|
||||
public double OffsetX
|
||||
{
|
||||
get => Position;
|
||||
set => Position = value;
|
||||
}
|
||||
|
||||
protected readonly Equation parent;
|
||||
protected readonly EquationDelegate parentEqu;
|
||||
|
||||
protected readonly double length;
|
||||
|
||||
@ -35,16 +44,26 @@ public class TangentLine : Graphable
|
||||
Name = $"Tangent Line of {parent.Name}";
|
||||
|
||||
slopeCache = [];
|
||||
parentEqu = parent.GetDelegate();
|
||||
Position = position;
|
||||
this.length = length;
|
||||
this.parent = parent;
|
||||
Position = position;
|
||||
|
||||
parent.OnInvalidate += (graph) =>
|
||||
{
|
||||
// I don't love this but it works.
|
||||
EraseCache();
|
||||
Position = _position; // Done for side effects.
|
||||
};
|
||||
}
|
||||
|
||||
public override IEnumerable<IGraphPart> GetItemsToRender(in GraphForm graph)
|
||||
{
|
||||
Float2 point = new(Position, currentSlope.y);
|
||||
return [MakeSlopeLine(), new GraphUiCircle(point, 8)];
|
||||
return
|
||||
[
|
||||
MakeSlopeLine(),
|
||||
new GraphUiCircle(point)
|
||||
];
|
||||
}
|
||||
protected GraphLine MakeSlopeLine()
|
||||
{
|
||||
@ -63,13 +82,13 @@ public class TangentLine : Graphable
|
||||
|
||||
const double step = 1e-3;
|
||||
|
||||
double initial = parentEqu(x);
|
||||
Float2 result = new((parentEqu(x + step) - initial) / step, initial);
|
||||
double initial = parent.GetValueAt(x);
|
||||
Float2 result = new((parent.GetValueAt(x + step) - initial) / step, initial);
|
||||
slopeCache.Add(x, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
public override Graphable DeepCopy() => new TangentLine(length, Position, parent);
|
||||
public override Graphable ShallowCopy() => new TangentLine(length, Position, parent);
|
||||
|
||||
public override void EraseCache() => slopeCache.Clear();
|
||||
public override long GetCacheBytes() => slopeCache.Count * 24;
|
||||
@ -93,7 +112,7 @@ public class TangentLine : Graphable
|
||||
double totalDist = Math.Sqrt(dist.x * dist.x + dist.y * dist.y);
|
||||
return totalDist <= allowedDist;
|
||||
}
|
||||
public override Float2 GetSelectedPoint(in GraphForm graph, Float2 graphMousePos)
|
||||
public override IEnumerable<IGraphPart> GetSelectionItemsToRender(in GraphForm graph, Float2 graphMousePos)
|
||||
{
|
||||
GraphLine line = MakeSlopeLine();
|
||||
|
||||
@ -101,7 +120,15 @@ public class TangentLine : Graphable
|
||||
Math.Min(line.a.x, line.b.x),
|
||||
Math.Max(line.a.x, line.b.x)),
|
||||
lineY = currentSlope.x * (lineX - Position) + currentSlope.y;
|
||||
return new Float2(lineX, lineY);
|
||||
|
||||
double slope = currentSlope.x;
|
||||
Float2 point = new(lineX, lineY);
|
||||
|
||||
return
|
||||
[
|
||||
new GraphUiText($"M = {slope:0.000}", point, ContentAlignment.BottomLeft),
|
||||
new GraphUiCircle(new(lineX, lineY))
|
||||
];
|
||||
}
|
||||
|
||||
public override void Preload(Float2 xRange, Float2 yRange, double step)
|
||||
@ -112,4 +139,14 @@ public class TangentLine : Graphable
|
||||
// that can be changed.
|
||||
for (double x = xRange.x; x <= xRange.y; x += step) DerivativeAtPoint(x);
|
||||
}
|
||||
|
||||
public Equation ToEquation()
|
||||
{
|
||||
double slope = currentSlope.x, x1 = Position, y1 = currentSlope.y;
|
||||
return new(x => slope * (x - x1) + y1)
|
||||
{
|
||||
Name = Name,
|
||||
Color = Color
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,24 +6,28 @@ namespace Graphing.Parts;
|
||||
public record struct GraphRectangle : IGraphPart
|
||||
{
|
||||
public Float2 min, max;
|
||||
public double opacity;
|
||||
|
||||
public GraphRectangle()
|
||||
{
|
||||
min = new();
|
||||
max = new();
|
||||
opacity = 1;
|
||||
}
|
||||
|
||||
public static GraphRectangle FromSize(Float2 center, Float2 size) => new()
|
||||
public static GraphRectangle FromSize(Float2 center, Float2 size, double opacity = 1) => new()
|
||||
{
|
||||
min = new(center.x - size.x / 2,
|
||||
center.y - size.y / 2),
|
||||
max = new(center.x + size.x / 2,
|
||||
center.y + size.y / 2)
|
||||
center.y + size.y / 2),
|
||||
opacity = opacity,
|
||||
};
|
||||
public static GraphRectangle FromRange(Float2 min, Float2 max) => new()
|
||||
public static GraphRectangle FromRange(Float2 min, Float2 max, double opacity = 1) => new()
|
||||
{
|
||||
min = min,
|
||||
max = max
|
||||
max = max,
|
||||
opacity = opacity,
|
||||
};
|
||||
|
||||
public void Render(in GraphForm form, in Graphics g, in Pen pen)
|
||||
@ -41,6 +45,9 @@ public record struct GraphRectangle : IGraphPart
|
||||
start.y - end.y);
|
||||
|
||||
if (size.x == 0 || size.y == 0) return;
|
||||
Color initialColor = pen.Color;
|
||||
pen.Color = Color.FromArgb((int)(opacity * 255), pen.Color);
|
||||
g.FillRectangle(pen.Brush, new Rectangle(start.x, end.y, size.x, size.y));
|
||||
pen.Color = initialColor;
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,7 +13,7 @@ public record struct GraphUiCircle : IGraphPart
|
||||
center = new();
|
||||
radius = 1;
|
||||
}
|
||||
public GraphUiCircle(Float2 center, int radius)
|
||||
public GraphUiCircle(Float2 center, int radius = 8)
|
||||
{
|
||||
this.center = center;
|
||||
this.radius = radius;
|
||||
|
||||
87
Base/Parts/GraphUiText.cs
Normal file
87
Base/Parts/GraphUiText.cs
Normal file
@ -0,0 +1,87 @@
|
||||
using Graphing.Forms;
|
||||
using System.Drawing;
|
||||
|
||||
namespace Graphing.Parts;
|
||||
|
||||
public record struct GraphUiText : IGraphPart
|
||||
{
|
||||
public string text;
|
||||
public Float2 position;
|
||||
public bool background;
|
||||
|
||||
public ContentAlignment alignment;
|
||||
public Int2 offsetPix;
|
||||
|
||||
private readonly Font font;
|
||||
private readonly Brush? backgroundBrush;
|
||||
|
||||
public GraphUiText(string text, Float2 position, ContentAlignment alignment,
|
||||
bool background = true, Int2? offsetPix = null)
|
||||
{
|
||||
font = new Font("Segoe UI", 8, FontStyle.Bold);
|
||||
|
||||
this.text = text;
|
||||
this.position = position;
|
||||
this.background = background;
|
||||
this.alignment = alignment;
|
||||
this.offsetPix = offsetPix ?? new();
|
||||
|
||||
if (background) backgroundBrush = new SolidBrush(GraphForm.BackgroundColor);
|
||||
}
|
||||
|
||||
public readonly void Render(in GraphForm form, in Graphics g, in Pen p)
|
||||
{
|
||||
Int2 posScreen = form.GraphSpaceToScreenSpace(position);
|
||||
SizeF size = g.MeasureString(text, font);
|
||||
|
||||
// Adjust X position based on alignment.
|
||||
switch (alignment)
|
||||
{
|
||||
case ContentAlignment.TopLeft or
|
||||
ContentAlignment.MiddleLeft or
|
||||
ContentAlignment.BottomLeft: break; // Nothing to offset.
|
||||
|
||||
case ContentAlignment.TopCenter or
|
||||
ContentAlignment.MiddleCenter or
|
||||
ContentAlignment.BottomCenter:
|
||||
posScreen.x -= (int)(size.Width / 2);
|
||||
break;
|
||||
|
||||
case ContentAlignment.TopRight or
|
||||
ContentAlignment.MiddleRight or
|
||||
ContentAlignment.BottomRight:
|
||||
posScreen.x -= (int)size.Width;
|
||||
break;
|
||||
}
|
||||
|
||||
// Adjust Y position based on alignment.
|
||||
switch (alignment)
|
||||
{
|
||||
case ContentAlignment.TopLeft or
|
||||
ContentAlignment.TopCenter or
|
||||
ContentAlignment.TopRight: break; // Nothing to offset.
|
||||
|
||||
case ContentAlignment.MiddleLeft or
|
||||
ContentAlignment.MiddleCenter or
|
||||
ContentAlignment.MiddleRight:
|
||||
posScreen.y -= (int)(size.Height / 2);
|
||||
break;
|
||||
|
||||
case ContentAlignment.BottomLeft or
|
||||
ContentAlignment.BottomCenter or
|
||||
ContentAlignment.BottomRight:
|
||||
posScreen.y -= (int)size.Height;
|
||||
break;
|
||||
}
|
||||
|
||||
posScreen.x += (int)(offsetPix.x * form.DpiFloat / 192);
|
||||
posScreen.y += (int)(offsetPix.y * form.DpiFloat / 192);
|
||||
|
||||
if (background)
|
||||
{
|
||||
g.FillRectangle(backgroundBrush!, new Rectangle(posScreen.x, posScreen.y,
|
||||
(int)size.Width, (int)size.Height));
|
||||
}
|
||||
g.DrawString(text, font, p.Brush, new Point(posScreen.x, posScreen.y));
|
||||
}
|
||||
}
|
||||
@ -4,7 +4,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
|
||||
-->
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<History>True|2024-03-20T12:39:01.6402921Z;True|2024-03-13T10:31:43.4569441-04:00;False|2024-03-13T10:30:01.4347009-04:00;False|2024-03-13T10:27:31.9554551-04:00;</History>
|
||||
<History>True|2024-03-20T12:48:45.8740885Z;True|2024-03-20T08:48:35.6948867-04:00;True|2024-03-20T08:39:01.6402921-04:00;True|2024-03-13T10:31:43.4569441-04:00;False|2024-03-13T10:30:01.4347009-04:00;False|2024-03-13T10:27:31.9554551-04:00;</History>
|
||||
<LastFailureDetails />
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@ -3,8 +3,9 @@
|
||||
This is a graphing calculator I made initially for a Calculus project in a day or so. I've written a basic rendering system in Windows Forms that runs on .NET 8.0.
|
||||
|
||||
Currently, it doesn't have a whole lot of features, but I'll be adding more in the future. Here's currently what it can do:
|
||||
- Graph an equation (duh).
|
||||
- Graph standard equations (duh).
|
||||
- There are currently some rendering issues with asymptotes which will be focused on at some point.
|
||||
- Graph parametric equations.
|
||||
- Integrate and derive equations.
|
||||
- Graph a slope field of a `dy/dx =` style equation.
|
||||
- View a tangent line of an equation.
|
||||
|
||||
@ -13,18 +13,15 @@ internal static class Program
|
||||
Application.EnableVisualStyles();
|
||||
Application.SetCompatibleTextRenderingDefault(false);
|
||||
Application.SetHighDpiMode(HighDpiMode.PerMonitorV2);
|
||||
|
||||
|
||||
GraphForm graph = new("One Of The Graphing Calculators Of All Time");
|
||||
|
||||
Equation equ = new(Math.Sin);
|
||||
SlopeField sf = new(2, (x, y) => Math.Cos(x));
|
||||
TangentLine tl = new(2, 2, equ);
|
||||
graph.Graph(equ, sf, tl);
|
||||
|
||||
// Now, when integrating equations, the result is much less jagged
|
||||
// and much faster. Try it out! You can also select points along
|
||||
// equations and such as well. Click on an equation to see for
|
||||
// yourself!
|
||||
Equation equA = new(Math.Sin),
|
||||
equB = new(Math.Cos);
|
||||
EquationDifference diff = new(2, equA, equB);
|
||||
ParametricEquation equC = new(0, 20, t => 0.0375 * t * Math.Cos(t), t => 0.0625 * t * Math.Sin(t) + 3);
|
||||
TangentLine tanA = new(2, 2, equA);
|
||||
graph.Graph(equA, equB, diff, equC, equB.ToColumnTable(-3, 3, 2), tanA);
|
||||
|
||||
Application.Run(graph);
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user