Compare commits
46 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ea608656db | |||
| a780e58378 | |||
| 25c660b1b7 | |||
| 861c7c5a03 | |||
| eea0bba358 | |||
| 8b63bf011e | |||
| e0a2713585 | |||
| ea9d2cd3b6 | |||
| 789a3b448e | |||
| aba32f8b58 | |||
| ed4ce2bbeb | |||
| a6f7279b97 | |||
| 933fcdf082 | |||
| 3ab55b509a | |||
| 41732d01e7 | |||
| 470a70a97a | |||
| 3b6ebc7b99 | |||
| e5c985c060 | |||
| d19c7a3fc3 | |||
| 4fc2425797 | |||
| 3665b0547a | |||
| 66146355c6 | |||
| 30529673d0 | |||
| 24828e9922 | |||
| a4e9ae3aa5 | |||
| f8c1788502 | |||
| 9b4905233c | |||
| f5107b7238 | |||
| 855a90b452 | |||
| 762a5f5a32 | |||
| c30ced7578 | |||
| 55cb7af2ac | |||
| 8618442bba | |||
| 7ff3720b70 | |||
| 5c3cf9cafe | |||
| 255a7d3774 | |||
| f87ef52a7d | |||
| 16f9cb794d | |||
| cd05a6829c | |||
| a93b5855d0 | |||
| bd70c17bf6 | |||
| 4989de2ed2 | |||
| 6d8787cac7 | |||
| fc829d6a6b | |||
| e31e6bfdb6 | |||
| 21c498f445 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,4 +1,5 @@
|
||||
.vs/
|
||||
.github/
|
||||
|
||||
Base/obj/
|
||||
Base/bin/
|
||||
|
||||
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);
|
||||
}
|
||||
8
Base/Abstract/IDerivable.cs
Normal file
8
Base/Abstract/IDerivable.cs
Normal file
@ -0,0 +1,8 @@
|
||||
using Graphing.Graphables;
|
||||
|
||||
namespace Graphing.Abstract;
|
||||
|
||||
public interface IDerivable
|
||||
{
|
||||
public Graphable Derive();
|
||||
}
|
||||
8
Base/Abstract/IIntegrable.cs
Normal file
8
Base/Abstract/IIntegrable.cs
Normal file
@ -0,0 +1,8 @@
|
||||
using Graphing.Graphables;
|
||||
|
||||
namespace Graphing.Abstract;
|
||||
|
||||
public interface IIntegrable
|
||||
{
|
||||
public Graphable Integrate();
|
||||
}
|
||||
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; }
|
||||
}
|
||||
@ -5,23 +5,25 @@
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<ImplicitUsings>disable</ImplicitUsings>
|
||||
<RootNamespace>Graphing</RootNamespace>
|
||||
<AssemblyName>ThatOneNerd.Graphing</AssemblyName>
|
||||
<ProduceReferenceAssembly>True</ProduceReferenceAssembly>
|
||||
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
||||
<PackageId>ThatOneNerd.Graphing</PackageId>
|
||||
<Title>ThatOneNerd.Graphing</Title>
|
||||
<Version>1.0.0</Version>
|
||||
<Version>1.3.0</Version>
|
||||
<Authors>That_One_Nerd</Authors>
|
||||
<Description>A fairly adept graphing calculator made in Windows Forms. </Description>
|
||||
<Description>A fairly adept graphing calculator made in Windows Forms.</Description>
|
||||
<Copyright>MIT</Copyright>
|
||||
<RepositoryUrl>https://github.com/That-One-Nerd/Graphing</RepositoryUrl>
|
||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||
<PackageTags>graphing;graph;plot;math;calculus;visual;desmos</PackageTags>
|
||||
<PackageTags>graphing;graph;plot;math;calculus;visual;desmos;slope field;slopefield;equation;visualizer;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.3.0</PackageReleaseNotes>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
|
||||
@ -1,6 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<_LastSelectedProfileId>C:\Users\kyley\Desktop\Coding\C#\Graphing\Base\Properties\PublishProfiles\FolderProfile.pubxml</_LastSelectedProfileId>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Update="Forms\Controls\PieChart.cs">
|
||||
<SubType>UserControl</SubType>
|
||||
</Compile>
|
||||
<Compile Update="Forms\GraphColorPickerForm.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
@ -10,5 +16,19 @@
|
||||
<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>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Update="Forms\ViewCacheForm.resx">
|
||||
<SubType>Designer</SubType>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
32
Base/Extensions/FormattingExtensions.cs
Normal file
32
Base/Extensions/FormattingExtensions.cs
Normal file
@ -0,0 +1,32 @@
|
||||
namespace Graphing.Extensions;
|
||||
|
||||
public static class FormattingExtensions
|
||||
{
|
||||
private static readonly string[] sizeUnits =
|
||||
[
|
||||
" bytes",
|
||||
" KB",
|
||||
" MB",
|
||||
" GB",
|
||||
" TB",
|
||||
" PB",
|
||||
];
|
||||
|
||||
public static string FormatAsBytes(this long bytes)
|
||||
{
|
||||
double val = bytes;
|
||||
int unitIndex = 0;
|
||||
|
||||
while (val > 1024)
|
||||
{
|
||||
unitIndex++;
|
||||
val /= 1024;
|
||||
}
|
||||
|
||||
string result;
|
||||
if (unitIndex == 0) result = val.ToString("0");
|
||||
else result = val.ToString("0.00");
|
||||
|
||||
return result + sizeUnits[unitIndex];
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,6 @@
|
||||
namespace Graphing;
|
||||
using System.Drawing;
|
||||
|
||||
namespace Graphing;
|
||||
|
||||
public record struct Float2
|
||||
{
|
||||
|
||||
46
Base/Forms/Controls/PieChart.Designer.cs
generated
Normal file
46
Base/Forms/Controls/PieChart.Designer.cs
generated
Normal file
@ -0,0 +1,46 @@
|
||||
using System.Drawing;
|
||||
|
||||
namespace Graphing.Forms.Controls
|
||||
{
|
||||
partial class PieChart
|
||||
{
|
||||
/// <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 Component 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()
|
||||
{
|
||||
SuspendLayout();
|
||||
//
|
||||
// PieChart
|
||||
//
|
||||
AutoScaleDimensions = new SizeF(13F, 32F);
|
||||
AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
Name = "PieChart";
|
||||
Size = new Size(500, 500);
|
||||
ResumeLayout(false);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
74
Base/Forms/Controls/PieChart.cs
Normal file
74
Base/Forms/Controls/PieChart.cs
Normal file
@ -0,0 +1,74 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Drawing2D;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace Graphing.Forms.Controls;
|
||||
|
||||
public partial class PieChart : UserControl
|
||||
{
|
||||
public List<(Color, double)> Values { get; set; }
|
||||
|
||||
public float DpiFloat { get; private set; }
|
||||
|
||||
public PieChart()
|
||||
{
|
||||
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
|
||||
SetStyle(ControlStyles.AllPaintingInWmPaint, true);
|
||||
SetStyle(ControlStyles.UserPaint, true);
|
||||
|
||||
Graphics tempG = CreateGraphics();
|
||||
DpiFloat = (tempG.DpiX + tempG.DpiY) / 2;
|
||||
tempG.Dispose();
|
||||
|
||||
Values = [];
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
protected override void OnPaint(PaintEventArgs e)
|
||||
{
|
||||
Graphics g = e.Graphics;
|
||||
g.SmoothingMode = SmoothingMode.HighQuality;
|
||||
int size = Math.Min(Width, Height);
|
||||
Rectangle rect = new(5, 5, size - 10, size - 10);
|
||||
|
||||
double sum = 0;
|
||||
foreach ((Color, double v) item in Values)
|
||||
sum += item.v;
|
||||
|
||||
// Draw them.
|
||||
double current = 0;
|
||||
foreach ((Color color, double value) item in Values)
|
||||
{
|
||||
double start = 360 * current / sum,
|
||||
end = 360 * (current + item.value) / sum;
|
||||
|
||||
Brush filler = new SolidBrush(item.color);
|
||||
g.FillPie(filler, rect, (float)start, (float)(end - start));
|
||||
|
||||
current += item.value;
|
||||
}
|
||||
|
||||
// Draw the outline of each slice.
|
||||
// Only done if there is more than one slice.
|
||||
if (Values.Count > 1)
|
||||
{
|
||||
Pen outlinePartsPen = new(Color.FromArgb(unchecked((int)0xFF_202020)), DpiFloat * 3 / 192);
|
||||
current = 0;
|
||||
foreach ((Color, double value) item in Values)
|
||||
{
|
||||
double start = 360 * current / sum,
|
||||
end = 360 * (current + item.value) / sum;
|
||||
if (item.value > 0)
|
||||
g.DrawPie(outlinePartsPen, rect, (float)start, (float)(end - start));
|
||||
|
||||
current += item.value;
|
||||
}
|
||||
}
|
||||
|
||||
// Outline
|
||||
Pen outlinePen = new(Color.FromArgb(unchecked((int)0xFF_202020)), DpiFloat * 5 / 192);
|
||||
g.DrawEllipse(outlinePen, rect);
|
||||
}
|
||||
}
|
||||
120
Base/Forms/Controls/PieChart.resx
Normal file
120
Base/Forms/Controls/PieChart.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>
|
||||
29
Base/Forms/GraphColorPickerForm.Designer.cs
generated
29
Base/Forms/GraphColorPickerForm.Designer.cs
generated
@ -1,4 +1,7 @@
|
||||
namespace Graphing.Forms
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace Graphing.Forms
|
||||
{
|
||||
partial class GraphColorPickerForm
|
||||
{
|
||||
@ -40,7 +43,7 @@
|
||||
ResultView = new Panel();
|
||||
BottomPanel = new Panel();
|
||||
OkButton = new Button();
|
||||
CancelButton = new Button();
|
||||
CancellingButton = new Button();
|
||||
RgbSliders.SuspendLayout();
|
||||
((System.ComponentModel.ISupportInitialize)BlueTrackBar).BeginInit();
|
||||
((System.ComponentModel.ISupportInitialize)RedTrackBar).BeginInit();
|
||||
@ -169,7 +172,7 @@
|
||||
//
|
||||
BottomPanel.BackColor = SystemColors.Window;
|
||||
BottomPanel.Controls.Add(OkButton);
|
||||
BottomPanel.Controls.Add(CancelButton);
|
||||
BottomPanel.Controls.Add(CancellingButton);
|
||||
BottomPanel.Dock = DockStyle.Bottom;
|
||||
BottomPanel.Location = new Point(0, 517);
|
||||
BottomPanel.Margin = new Padding(0);
|
||||
@ -191,15 +194,15 @@
|
||||
//
|
||||
// CancelButton
|
||||
//
|
||||
CancelButton.Anchor = AnchorStyles.Right;
|
||||
CancelButton.Location = new Point(384, 9);
|
||||
CancelButton.Margin = new Padding(0);
|
||||
CancelButton.Name = "CancelButton";
|
||||
CancelButton.Size = new Size(150, 46);
|
||||
CancelButton.TabIndex = 0;
|
||||
CancelButton.Text = "Cancel";
|
||||
CancelButton.UseVisualStyleBackColor = true;
|
||||
CancelButton.Click += CancelButton_Click;
|
||||
CancellingButton.Anchor = AnchorStyles.Right;
|
||||
CancellingButton.Location = new Point(384, 9);
|
||||
CancellingButton.Margin = new Padding(0);
|
||||
CancellingButton.Name = "CancelButton";
|
||||
CancellingButton.Size = new Size(150, 46);
|
||||
CancellingButton.TabIndex = 0;
|
||||
CancellingButton.Text = "Cancel";
|
||||
CancellingButton.UseVisualStyleBackColor = true;
|
||||
CancellingButton.Click += CancelButton_Click;
|
||||
//
|
||||
// GraphColorPickerForm
|
||||
//
|
||||
@ -234,7 +237,7 @@
|
||||
private TrackBar BlueTrackBar;
|
||||
private TrackBar RedTrackBar;
|
||||
private Panel BottomPanel;
|
||||
private Button CancelButton;
|
||||
private Button CancellingButton;
|
||||
private Button OkButton;
|
||||
private TextBox RedValueBox;
|
||||
private TextBox BlueValueBox;
|
||||
|
||||
@ -1,4 +1,8 @@
|
||||
namespace Graphing.Forms;
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace Graphing.Forms;
|
||||
|
||||
public partial class GraphColorPickerForm : Form
|
||||
{
|
||||
@ -37,7 +41,7 @@ public partial class GraphColorPickerForm : Form
|
||||
MessageLabel.Text = $"Pick a color for {able.Name}.";
|
||||
|
||||
// Add preset buttons.
|
||||
const int size = 48;
|
||||
int size = (int)(graph.DpiFloat * 48 / 192);
|
||||
int position = 0;
|
||||
foreach (uint cId in Graphable.DefaultColors)
|
||||
{
|
||||
|
||||
221
Base/Forms/GraphForm.Designer.cs
generated
221
Base/Forms/GraphForm.Designer.cs
generated
@ -1,4 +1,7 @@
|
||||
namespace Graphing.Forms
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace Graphing.Forms
|
||||
{
|
||||
partial class GraphForm
|
||||
{
|
||||
@ -35,30 +38,45 @@
|
||||
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 });
|
||||
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);
|
||||
@ -75,68 +93,185 @@
|
||||
// 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
|
||||
//
|
||||
MenuMisc.DropDownItems.AddRange(new ToolStripItem[] { MenuMiscCaches, MiscMenuPreload });
|
||||
MenuMisc.Name = "MenuMisc";
|
||||
MenuMisc.Size = new Size(83, 38);
|
||||
MenuMisc.Text = "Misc";
|
||||
//
|
||||
// MenuMiscCaches
|
||||
//
|
||||
MenuMiscCaches.Name = "MenuMiscCaches";
|
||||
MenuMiscCaches.Size = new Size(299, 44);
|
||||
MenuMiscCaches.Text = "View Caches";
|
||||
MenuMiscCaches.Click += MenuMiscCaches_Click;
|
||||
//
|
||||
// MiscMenuPreload
|
||||
//
|
||||
MiscMenuPreload.Name = "MiscMenuPreload";
|
||||
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();
|
||||
}
|
||||
@ -145,14 +280,28 @@
|
||||
|
||||
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,32 +1,71 @@
|
||||
using Graphing.Graphables;
|
||||
using Graphing.Abstract;
|
||||
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 Float2 ScreenCenter { get; private set; }
|
||||
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)); // 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 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 double ZoomLevel
|
||||
public float DpiFloat { get; private set; }
|
||||
|
||||
public Float2 ZoomLevel
|
||||
{
|
||||
get => _zoomLevel;
|
||||
set
|
||||
{
|
||||
double oldZoom = ZoomLevel;
|
||||
|
||||
_zoomLevel = Math.Clamp(value, 1e-2, 1e3);
|
||||
|
||||
int totalSegments = 0;
|
||||
foreach (Graphable able in ables) totalSegments += able.GetItemsToRender(this).Count();
|
||||
|
||||
if (totalSegments > 10_000)
|
||||
{
|
||||
_zoomLevel = oldZoom;
|
||||
return; // Too many segments, stop.
|
||||
}
|
||||
_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;
|
||||
@ -38,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);
|
||||
@ -50,10 +91,15 @@ public partial class GraphForm : Form
|
||||
Graphics tempG = CreateGraphics();
|
||||
Dpi = new(tempG.DpiX, tempG.DpiY);
|
||||
tempG.Dispose();
|
||||
|
||||
DpiFloat = (float)((Dpi.x + Dpi.y) / 2);
|
||||
|
||||
ables = [];
|
||||
ZoomLevel = 1;
|
||||
ZoomLevel = new(1, 1);
|
||||
initialWindowPos = Location;
|
||||
initialWindowSize = Size;
|
||||
|
||||
RunUpdateChecker();
|
||||
}
|
||||
|
||||
public Int2 GraphSpaceToScreenSpace(Float2 graphPoint)
|
||||
@ -63,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;
|
||||
@ -78,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;
|
||||
@ -91,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(Color.FromArgb(unchecked((int)0xFF_E0E0E0)));
|
||||
Pen quarterPen = new(quarterBrush, 2);
|
||||
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));
|
||||
@ -111,24 +158,24 @@ public partial class GraphForm : Form
|
||||
}
|
||||
|
||||
// Draw horizontal/vertical semi-axis.
|
||||
Brush semiBrush = new SolidBrush(Color.FromArgb(unchecked((int)0xFF_999999)));
|
||||
Pen semiPen = new(semiBrush, 2);
|
||||
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));
|
||||
g.DrawLine(semiPen, startPos, endPos);
|
||||
}
|
||||
|
||||
Brush mainLineBrush = new SolidBrush(Color.Black);
|
||||
Pen mainLinePen = new(mainLineBrush, 3);
|
||||
Brush mainLineBrush = new SolidBrush(MainAxisColor);
|
||||
Pen mainLinePen = new(mainLineBrush, DpiFloat * 3 / 192);
|
||||
|
||||
// Draw the main axis (on top of the semi axis).
|
||||
Int2 startCenterY = GraphSpaceToScreenSpace(new Float2(0, MinVisibleGraph.y)),
|
||||
@ -139,33 +186,99 @@ public partial class GraphForm : Form
|
||||
g.DrawLine(mainLinePen, startCenterX, endCenterX);
|
||||
g.DrawLine(mainLinePen, startCenterY, endCenterY);
|
||||
}
|
||||
protected virtual void PaintUnits(Graphics g)
|
||||
{
|
||||
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 / axisScaleX) * axisScaleX; x <= MaxVisibleGraph.x; x += axisScaleX)
|
||||
{
|
||||
if (x == 0) x = 0; // Fixes -0
|
||||
|
||||
Int2 screenPos = GraphSpaceToScreenSpace(new Float2(x, 0));
|
||||
|
||||
if (screenPos.y < minX) screenPos.y = minX;
|
||||
else if (screenPos.y > maxX) screenPos.y = maxX;
|
||||
|
||||
g.DrawString($"{x}", textFont, textBrush, screenPos.x, screenPos.y);
|
||||
}
|
||||
|
||||
// Y-axis
|
||||
int minY = (int)(DpiFloat * 10 / 192);
|
||||
for (double y = Math.Ceiling(MinVisibleGraph.y / axisScaleY) * axisScaleY; y <= MaxVisibleGraph.y; y += axisScaleY)
|
||||
{
|
||||
if (y == 0) continue;
|
||||
|
||||
Int2 screenPos = GraphSpaceToScreenSpace(new Float2(0, y));
|
||||
|
||||
string result = y.ToString();
|
||||
int maxY = ClientRectangle.Width - (int)(DpiFloat * (textFont.Height * result.Length * 0.40 + 15) / 192);
|
||||
|
||||
if (screenPos.x < minY) screenPos.x = minY;
|
||||
else if (screenPos.x > maxY) screenPos.x = maxY;
|
||||
|
||||
g.DrawString($"{y}", textFont, textBrush, screenPos.x, screenPos.y);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnPaint(PaintEventArgs e)
|
||||
{
|
||||
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);
|
||||
PaintUnits(g);
|
||||
|
||||
Point clientMousePos = PointToClient(Cursor.Position);
|
||||
Float2 graphMousePos = ScreenSpaceToGraphSpace(new(clientMousePos.X,
|
||||
clientMousePos.Y));
|
||||
|
||||
// Draw the actual graphs.
|
||||
Pen[] graphPens = new Pen[ables.Count];
|
||||
for (int i = 0; i < ables.Count; i++)
|
||||
{
|
||||
IEnumerable<Line2d> lines = ables[i].GetItemsToRender(this);
|
||||
IEnumerable<IGraphPart> lines = ables[i].GetItemsToRender(this);
|
||||
Brush graphBrush = new SolidBrush(ables[i].Color);
|
||||
Pen penBrush = new(graphBrush, 3);
|
||||
Pen graphPen = new(graphBrush, DpiFloat * 3 / 192);
|
||||
graphPens[i] = graphPen;
|
||||
foreach (IGraphPart gp in lines) gp.Render(this, g, graphPen);
|
||||
}
|
||||
|
||||
foreach (Line2d l in lines)
|
||||
// Equation selection detection.
|
||||
// This system lets you select multiple graphs, and that's cool by me.
|
||||
if (selectState == SelectionState.GraphSelect)
|
||||
{
|
||||
for (int i = 0; i < ables.Count; i++)
|
||||
{
|
||||
if (!double.IsNormal(l.a.x) || !double.IsNormal(l.a.y) ||
|
||||
!double.IsNormal(l.b.x) || !double.IsNormal(l.b.y)) continue;
|
||||
|
||||
Int2 start = GraphSpaceToScreenSpace(l.a),
|
||||
end = GraphSpaceToScreenSpace(l.b);
|
||||
g.DrawLine(penBrush, start, end);
|
||||
if (ables[i].ShouldSelectGraphable(this, graphMousePos, 2.5))
|
||||
{
|
||||
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);
|
||||
}
|
||||
@ -175,60 +288,153 @@ public partial class GraphForm : Form
|
||||
Invalidate(false);
|
||||
}
|
||||
|
||||
public void Graph(Graphable able)
|
||||
public void Graph(params Graphable[] newAbles)
|
||||
{
|
||||
ables.Add(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 Float2 boxSelectA, boxSelectB;
|
||||
|
||||
protected override void OnMouseDown(MouseEventArgs e)
|
||||
{
|
||||
mouseDrag = true;
|
||||
initialMouseLocation = new Int2(Cursor.Position.X, Cursor.Position.Y);
|
||||
initialScreenCenter = ScreenCenter;
|
||||
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))
|
||||
selectState = SelectionState.GraphSelect;
|
||||
}
|
||||
if (selectState == SelectionState.GraphSelect) Invalidate(false);
|
||||
}
|
||||
|
||||
if (selectState == SelectionState.None && !ViewportLocked)
|
||||
{
|
||||
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);
|
||||
Invalidate(false);
|
||||
}
|
||||
mouseDrag = 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 (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)
|
||||
{
|
||||
GraphColorPickerForm picker = new(this, able)
|
||||
@ -237,15 +443,57 @@ public partial class GraphForm : Form
|
||||
};
|
||||
picker.Location = new Point(Location.X + ClientRectangle.Width + 10,
|
||||
Location.Y + (ClientRectangle.Height - picker.ClientRectangle.Height) / 2);
|
||||
|
||||
if (picker.Location.X + picker.Width > Screen.FromControl(this).WorkingArea.Width)
|
||||
{
|
||||
picker.StartPosition = FormStartPosition.WindowsDefaultLocation;
|
||||
}
|
||||
|
||||
picker.TopMost = true;
|
||||
picker.ShowDialog();
|
||||
RegenerateMenuItems();
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
@ -255,52 +503,126 @@ public partial class GraphForm : Form
|
||||
Text = able.Name
|
||||
};
|
||||
colorItem.Click += (o, e) => GraphColorPickerButton_Click(able);
|
||||
MenuColors.DropDownItems.Add(colorItem);
|
||||
MenuElementsColors.DropDownItems.Add(colorItem);
|
||||
|
||||
if (able is Equation)
|
||||
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)
|
||||
{
|
||||
ToolStripMenuItem derivativeItem = new()
|
||||
{
|
||||
ForeColor = able.Color,
|
||||
Text = able.Name
|
||||
};
|
||||
derivativeItem.Click += (o, e) => EquationComputeDerivative_Click((able as Equation)!);
|
||||
MenuEquationsDerivative.DropDownItems.Add(derivativeItem);
|
||||
|
||||
derivativeItem.Click += (o, e) => Graph(derivable.Derive());
|
||||
MenuOperationsDerivative.DropDownItems.Add(derivativeItem);
|
||||
}
|
||||
if (able is IIntegrable integrable)
|
||||
{
|
||||
ToolStripMenuItem integralItem = new()
|
||||
{
|
||||
ForeColor = able.Color,
|
||||
Text = able.Name
|
||||
};
|
||||
integralItem.Click += (o, e) => EquationComputeIntegral_Click((able as Equation)!);
|
||||
MenuEquationsIntegral.DropDownItems.Add(integralItem);
|
||||
integralItem.Click += (o, e) => Graph(integrable.Integrate());
|
||||
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)
|
||||
{
|
||||
MessageBox.Show("TODO", "Set Center Position", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
}
|
||||
|
||||
private void ButtonViewportReset_Click(object? sender, EventArgs e)
|
||||
{
|
||||
ScreenCenter = new Float2(0, 0);
|
||||
ZoomLevel = 1;
|
||||
ZoomLevel = new(1, 1);
|
||||
Invalidate(false);
|
||||
}
|
||||
|
||||
private void ButtonViewportResetWindow_Click(object? sender, EventArgs e)
|
||||
{
|
||||
Location = initialWindowPos;
|
||||
@ -308,60 +630,147 @@ public partial class GraphForm : Form
|
||||
WindowState = FormWindowState.Normal;
|
||||
}
|
||||
|
||||
private void EquationComputeDerivative_Click(Equation equation)
|
||||
public void ResetAllViewport()
|
||||
{
|
||||
EquationDelegate equ = equation.GetDelegate();
|
||||
string oldName = equation.Name, newName;
|
||||
if (oldName.StartsWith("Derivative of ")) newName = "Second Derivative of " + oldName[14..];
|
||||
else if (oldName.StartsWith("Second Derivative of ")) newName = "Third Derivative of " + oldName[21..];
|
||||
else newName = "Derivative of " + oldName;
|
||||
// TODO: anti-integrate (maybe).
|
||||
ScreenCenter = new Float2(0, 0);
|
||||
ZoomLevel = new(1, 1);
|
||||
Location = initialWindowPos;
|
||||
Size = initialWindowSize;
|
||||
WindowState = FormWindowState.Normal;
|
||||
Invalidate(false);
|
||||
}
|
||||
|
||||
Graph(new Equation(DerivativeAtPoint(equ))
|
||||
private ViewCacheForm? cacheForm;
|
||||
private void MenuMiscCaches_Click(object? sender, EventArgs e)
|
||||
{
|
||||
if (this.cacheForm is not null)
|
||||
{
|
||||
Name = newName
|
||||
});
|
||||
this.cacheForm.Focus();
|
||||
return;
|
||||
}
|
||||
|
||||
static EquationDelegate DerivativeAtPoint(EquationDelegate e)
|
||||
ViewCacheForm cacheForm = new(this)
|
||||
{
|
||||
const double step = 1e-3;
|
||||
return x => (e(x + step) - e(x)) / step;
|
||||
StartPosition = FormStartPosition.Manual
|
||||
};
|
||||
this.cacheForm = cacheForm;
|
||||
|
||||
cacheForm.Location = new Point(Location.X + ClientRectangle.Width + 10,
|
||||
Location.Y + (ClientRectangle.Height - cacheForm.ClientRectangle.Height) / 2);
|
||||
|
||||
if (cacheForm.Location.X + cacheForm.Width > Screen.FromControl(this).WorkingArea.Width)
|
||||
{
|
||||
cacheForm.StartPosition = FormStartPosition.WindowsDefaultLocation;
|
||||
}
|
||||
cacheForm.TopMost = true;
|
||||
cacheForm.Show();
|
||||
}
|
||||
private void MiscMenuPreload_Click(object? sender, EventArgs e)
|
||||
{
|
||||
Float2 min = MinVisibleGraph, max = MaxVisibleGraph;
|
||||
Float2 add = new(max.x - min.x, max.y - min.y);
|
||||
add.x *= 0.75; // Expansion
|
||||
add.y *= 0.75; // Screen + 75%
|
||||
|
||||
Float2 xRange = new(min.x - add.x, max.x + add.x),
|
||||
yRange = new(min.y - add.y, max.y + add.y);
|
||||
|
||||
double step = ScreenSpaceToGraphSpace(new Int2(1, 0)).x
|
||||
- ScreenSpaceToGraphSpace(new Int2(0, 0)).x;
|
||||
step /= 10;
|
||||
|
||||
foreach (Graphable able in Graphables) able.Preload(xRange, yRange, step);
|
||||
Invalidate(false);
|
||||
}
|
||||
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 void EquationComputeIntegral_Click(Equation equation)
|
||||
private enum SelectionState
|
||||
{
|
||||
EquationDelegate equ = equation.GetDelegate();
|
||||
string oldName = equation.Name, newName;
|
||||
if (oldName.StartsWith("Integral of ")) newName = "Second Integral of " + oldName[12..];
|
||||
else if (oldName.StartsWith("Second Integral of ")) newName = "Third Integral of " + oldName[19..];
|
||||
else newName = "Integral of " + oldName;
|
||||
// TODO: anti-derive (maybe)
|
||||
|
||||
Graph(new Equation(x => Integrate(equ, 0, x))
|
||||
{
|
||||
Name = newName
|
||||
});
|
||||
|
||||
static double Integrate(EquationDelegate e, double lower, double upper)
|
||||
{
|
||||
// TODO: a better rendering method could make this much faster.
|
||||
const double step = 1e-1;
|
||||
|
||||
double factor = 1;
|
||||
if (upper < lower)
|
||||
{
|
||||
factor = -1;
|
||||
(lower, upper) = (upper, lower);
|
||||
}
|
||||
|
||||
double sum = 0;
|
||||
for (double x = lower; x <= upper; x += step)
|
||||
{
|
||||
sum += e(x) * step;
|
||||
}
|
||||
|
||||
return sum * factor;
|
||||
}
|
||||
None = 0,
|
||||
ViewportDrag,
|
||||
GraphSelect,
|
||||
ZoomBox,
|
||||
}
|
||||
}
|
||||
|
||||
195
Base/Forms/SetZoomForm.Designer.cs
generated
195
Base/Forms/SetZoomForm.Designer.cs
generated
@ -28,90 +28,157 @@
|
||||
/// </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;
|
||||
}
|
||||
}
|
||||
@ -1,131 +1,227 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Data;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace Graphing.Forms
|
||||
namespace Graphing.Forms;
|
||||
|
||||
public partial class SetZoomForm : Form
|
||||
{
|
||||
public partial class SetZoomForm : Form
|
||||
private readonly GraphForm refForm;
|
||||
|
||||
private bool boxSelectEnabled;
|
||||
|
||||
public SetZoomForm(GraphForm refForm)
|
||||
{
|
||||
private double minZoomRange;
|
||||
private double maxZoomRange;
|
||||
InitializeComponent();
|
||||
this.refForm = refForm;
|
||||
|
||||
private double zoomLevel;
|
||||
refForm.Paint += (o, e) => RedeclareValues();
|
||||
RedeclareValues();
|
||||
|
||||
private readonly GraphForm form;
|
||||
|
||||
public SetZoomForm(GraphForm form)
|
||||
MinBoxX.Leave += MinBoxX_Finish;
|
||||
MinBoxX.KeyDown += (o, e) =>
|
||||
{
|
||||
InitializeComponent();
|
||||
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 = 1 / (form.ZoomLevel * 2);
|
||||
maxZoomRange = 2 / form.ZoomLevel;
|
||||
zoomLevel = 1 / form.ZoomLevel;
|
||||
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);
|
||||
};
|
||||
}
|
||||
|
||||
ZoomTrackBar.Value = (int)(ZoomToFactor(zoomLevel) * (ZoomTrackBar.Maximum - ZoomTrackBar.Minimum) + ZoomTrackBar.Minimum);
|
||||
private void EnableBoxSelect_Click(object? sender, EventArgs e)
|
||||
{
|
||||
boxSelectEnabled = !boxSelectEnabled;
|
||||
refForm.canBoxSelect = boxSelectEnabled;
|
||||
|
||||
this.form = form;
|
||||
if (boxSelectEnabled)
|
||||
{
|
||||
EnableBoxSelect.Text = $"Cancel ...";
|
||||
refForm.Focus();
|
||||
}
|
||||
|
||||
protected override void OnPaint(PaintEventArgs e)
|
||||
else
|
||||
{
|
||||
ZoomMaxValue.Text = maxZoomRange.ToString("0.00");
|
||||
ZoomMinValue.Text = minZoomRange.ToString("0.00");
|
||||
|
||||
ValueLabel.Text = $"{zoomLevel:0.00}x";
|
||||
|
||||
base.OnPaint(e);
|
||||
|
||||
form.ZoomLevel = 1 / zoomLevel;
|
||||
form.Invalidate(false);
|
||||
}
|
||||
|
||||
private double FactorToZoom(double factor)
|
||||
{
|
||||
return minZoomRange + (factor * factor) * (maxZoomRange - minZoomRange);
|
||||
}
|
||||
private double ZoomToFactor(double zoom)
|
||||
{
|
||||
double sqrValue = (zoom - minZoomRange) / (maxZoomRange - minZoomRange);
|
||||
return Math.Sign(sqrValue) * Math.Sqrt(Math.Abs(sqrValue));
|
||||
}
|
||||
|
||||
private void ZoomTrackBar_Scroll(object? sender, EventArgs e)
|
||||
{
|
||||
double factor = (ZoomTrackBar.Value - ZoomTrackBar.Minimum) / (double)(ZoomTrackBar.Maximum - ZoomTrackBar.Minimum);
|
||||
zoomLevel = FactorToZoom(factor);
|
||||
|
||||
Invalidate(true);
|
||||
}
|
||||
|
||||
private void ZoomMinValue_TextChanged(object? sender, EventArgs e)
|
||||
{
|
||||
double original = minZoomRange;
|
||||
try
|
||||
{
|
||||
double value;
|
||||
if (string.IsNullOrWhiteSpace(ZoomMinValue.Text) ||
|
||||
ZoomMinValue.Text.EndsWith('.'))
|
||||
{
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
value = double.Parse(ZoomMinValue.Text);
|
||||
if (value < 1e-2 || value > 1e3 || value > maxZoomRange) throw new();
|
||||
}
|
||||
|
||||
minZoomRange = value;
|
||||
ZoomTrackBar.Value = (int)Math.Clamp(ZoomToFactor(zoomLevel) * (ZoomTrackBar.Maximum - ZoomTrackBar.Minimum) + ZoomTrackBar.Minimum, ZoomTrackBar.Minimum, ZoomTrackBar.Maximum);
|
||||
double factor = (ZoomTrackBar.Value - ZoomTrackBar.Minimum) / (double)(ZoomTrackBar.Maximum - ZoomTrackBar.Minimum);
|
||||
double newZoom = FactorToZoom(factor);
|
||||
|
||||
zoomLevel = newZoom;
|
||||
if (newZoom != factor) Invalidate(true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
minZoomRange = original;
|
||||
ZoomMinValue.Text = minZoomRange.ToString("0.00");
|
||||
}
|
||||
}
|
||||
|
||||
private void ZoomMaxValue_TextChanged(object sender, EventArgs e)
|
||||
{
|
||||
double original = maxZoomRange;
|
||||
try
|
||||
{
|
||||
double value;
|
||||
if (string.IsNullOrWhiteSpace(ZoomMaxValue.Text) ||
|
||||
ZoomMaxValue.Text.EndsWith('.'))
|
||||
{
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
value = double.Parse(ZoomMaxValue.Text);
|
||||
if (value < 1e-2 || value > 1e3 || value < minZoomRange) throw new();
|
||||
}
|
||||
|
||||
maxZoomRange = value;
|
||||
ZoomTrackBar.Value = (int)Math.Clamp(ZoomToFactor(zoomLevel) * (ZoomTrackBar.Maximum - ZoomTrackBar.Minimum) + ZoomTrackBar.Minimum, ZoomTrackBar.Minimum, ZoomTrackBar.Maximum);
|
||||
double factor = (ZoomTrackBar.Value - ZoomTrackBar.Minimum) / (double)(ZoomTrackBar.Maximum - ZoomTrackBar.Minimum);
|
||||
double newZoom = FactorToZoom(factor);
|
||||
|
||||
zoomLevel = newZoom;
|
||||
if (newZoom != factor) Invalidate(true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
maxZoomRange = original;
|
||||
ZoomMaxValue.Text = maxZoomRange.ToString("0.00");
|
||||
}
|
||||
EnableBoxSelect.Text = "Box Select";
|
||||
}
|
||||
}
|
||||
private void MatchAspectButton_Click(object? sender, EventArgs e)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
MaxBoxX.Text = MinBoxX.Text;
|
||||
MaxBoxX_Finish(sender, e);
|
||||
minX = max.x;
|
||||
|
||||
// Redefine bounds.
|
||||
min = refForm.MinVisibleGraph;
|
||||
max = refForm.MaxVisibleGraph;
|
||||
}
|
||||
|
||||
double newCenterX = (minX + max.x) / 2,
|
||||
zoomFactorX = (max.x - minX) / (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 MaxBoxX_Finish(object? sender, EventArgs e)
|
||||
{
|
||||
if (double.TryParse(MaxBoxX.Text, out double maxX))
|
||||
{
|
||||
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>
|
||||
100
Base/Forms/ViewCacheForm.Designer.cs
generated
Normal file
100
Base/Forms/ViewCacheForm.Designer.cs
generated
Normal file
@ -0,0 +1,100 @@
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace Graphing.Forms
|
||||
{
|
||||
partial class ViewCacheForm
|
||||
{
|
||||
/// <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()
|
||||
{
|
||||
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ViewCacheForm));
|
||||
CachePie = new Controls.PieChart();
|
||||
TotalCacheText = new Label();
|
||||
EraseAllCacheButton = new Button();
|
||||
SpecificCachePanel = new Panel();
|
||||
SuspendLayout();
|
||||
//
|
||||
// CachePie
|
||||
//
|
||||
CachePie.Location = new Point(50, 50);
|
||||
CachePie.Name = "CachePie";
|
||||
CachePie.Size = new Size(450, 450);
|
||||
CachePie.TabIndex = 0;
|
||||
//
|
||||
// TotalCacheText
|
||||
//
|
||||
TotalCacheText.Font = new Font("Segoe UI Semibold", 10.125F, FontStyle.Bold, GraphicsUnit.Point, 0);
|
||||
TotalCacheText.Location = new Point(62, 540);
|
||||
TotalCacheText.Name = "TotalCacheText";
|
||||
TotalCacheText.Size = new Size(425, 45);
|
||||
TotalCacheText.TabIndex = 1;
|
||||
TotalCacheText.Text = "Total Cache: Something";
|
||||
TotalCacheText.TextAlign = ContentAlignment.TopCenter;
|
||||
//
|
||||
// EraseAllCacheButton
|
||||
//
|
||||
EraseAllCacheButton.Location = new Point(200, 580);
|
||||
EraseAllCacheButton.Name = "EraseAllCacheButton";
|
||||
EraseAllCacheButton.Size = new Size(150, 46);
|
||||
EraseAllCacheButton.TabIndex = 2;
|
||||
EraseAllCacheButton.Text = "Erase All";
|
||||
EraseAllCacheButton.UseVisualStyleBackColor = true;
|
||||
EraseAllCacheButton.Click += EraseAllCacheButton_Click;
|
||||
//
|
||||
// SpecificCachePanel
|
||||
//
|
||||
SpecificCachePanel.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
|
||||
SpecificCachePanel.AutoScroll = true;
|
||||
SpecificCachePanel.Location = new Point(520, 12);
|
||||
SpecificCachePanel.Name = "SpecificCachePanel";
|
||||
SpecificCachePanel.Size = new Size(542, 657);
|
||||
SpecificCachePanel.TabIndex = 3;
|
||||
//
|
||||
// ViewCacheForm
|
||||
//
|
||||
AutoScaleDimensions = new SizeF(13F, 32F);
|
||||
AutoScaleMode = AutoScaleMode.Font;
|
||||
ClientSize = new Size(1074, 679);
|
||||
Controls.Add(SpecificCachePanel);
|
||||
Controls.Add(EraseAllCacheButton);
|
||||
Controls.Add(TotalCacheText);
|
||||
Controls.Add(CachePie);
|
||||
FormBorderStyle = FormBorderStyle.SizableToolWindow;
|
||||
MinimumSize = new Size(885, 750);
|
||||
Name = "ViewCacheForm";
|
||||
Text = "Graph Caches";
|
||||
ResumeLayout(false);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private Controls.PieChart CachePie;
|
||||
private Label TotalCacheText;
|
||||
private Button EraseAllCacheButton;
|
||||
private Panel SpecificCachePanel;
|
||||
}
|
||||
}
|
||||
98
Base/Forms/ViewCacheForm.cs
Normal file
98
Base/Forms/ViewCacheForm.cs
Normal file
@ -0,0 +1,98 @@
|
||||
using Graphing.Extensions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace Graphing.Forms;
|
||||
|
||||
public partial class ViewCacheForm : Form
|
||||
{
|
||||
private readonly GraphForm refForm;
|
||||
|
||||
private readonly List<Label> labelCache;
|
||||
private readonly List<Button> buttonCache;
|
||||
|
||||
public ViewCacheForm(GraphForm thisForm)
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
refForm = thisForm;
|
||||
refForm.Paint += (o, e) => UpdatePieChart();
|
||||
labelCache = [];
|
||||
buttonCache = [];
|
||||
UpdatePieChart();
|
||||
}
|
||||
|
||||
private void UpdatePieChart()
|
||||
{
|
||||
CachePie.Values.Clear();
|
||||
|
||||
long totalBytes = 0;
|
||||
int index = 0;
|
||||
foreach (Graphable able in refForm.Graphables)
|
||||
{
|
||||
long thisBytes = able.GetCacheBytes();
|
||||
if (thisBytes == 0) continue;
|
||||
CachePie.Values.Add((able.Color, thisBytes));
|
||||
totalBytes += thisBytes;
|
||||
|
||||
int buttonHeight = (int)(refForm.DpiFloat * 46 / 192),
|
||||
buttonWidth = (int)(refForm.DpiFloat * 92 / 192),
|
||||
buttonSpaced = (int)(refForm.DpiFloat * 98 / 192);
|
||||
|
||||
if (index < labelCache.Count)
|
||||
{
|
||||
Label reuseLabel = labelCache[index];
|
||||
reuseLabel.ForeColor = able.Color;
|
||||
reuseLabel.Text = $"{able.Name}: {thisBytes.FormatAsBytes()}";
|
||||
}
|
||||
else
|
||||
{
|
||||
Label newText = new()
|
||||
{
|
||||
Anchor = AnchorStyles.Left | AnchorStyles.Top | AnchorStyles.Right,
|
||||
AutoEllipsis = true,
|
||||
ForeColor = able.Color,
|
||||
Location = new Point(0, labelCache.Count * buttonHeight),
|
||||
Parent = SpecificCachePanel,
|
||||
Size = new Size(SpecificCachePanel.Width - buttonSpaced, buttonHeight),
|
||||
Text = $"{able.Name}: {thisBytes.FormatAsBytes()}",
|
||||
TextAlign = ContentAlignment.MiddleLeft,
|
||||
};
|
||||
labelCache.Add(newText);
|
||||
}
|
||||
|
||||
if (index >= buttonCache.Count)
|
||||
{
|
||||
Button newButton = new()
|
||||
{
|
||||
Anchor = AnchorStyles.Top | AnchorStyles.Right,
|
||||
Location = new Point(SpecificCachePanel.Width - buttonWidth, buttonCache.Count * buttonHeight),
|
||||
Parent = SpecificCachePanel,
|
||||
Size = new Size(buttonWidth, buttonHeight),
|
||||
Text = "Clear"
|
||||
};
|
||||
newButton.Click += (o, e) => EraseSpecificGraphable_Click(able);
|
||||
buttonCache.Add(newButton);
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
TotalCacheText.Text = $"Total Cache: {totalBytes.FormatAsBytes()}";
|
||||
|
||||
Invalidate(true);
|
||||
}
|
||||
|
||||
private void EraseAllCacheButton_Click(object? sender, EventArgs e)
|
||||
{
|
||||
foreach (Graphable able in refForm.Graphables) able.EraseCache();
|
||||
refForm.Invalidate(false);
|
||||
}
|
||||
private void EraseSpecificGraphable_Click(Graphable able)
|
||||
{
|
||||
able.EraseCache();
|
||||
refForm.Invalidate(false);
|
||||
}
|
||||
}
|
||||
139
Base/Forms/ViewCacheForm.resx
Normal file
139
Base/Forms/ViewCacheForm.resx
Normal file
@ -0,0 +1,139 @@
|
||||
<?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>
|
||||
<data name="CachePie.Values" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>
|
||||
AAEAAAD/////AQAAAAAAAAAEAQAAAM0CU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuTGlzdGAxW1tT
|
||||
eXN0ZW0uVmFsdWVUdXBsZWAyW1tTeXN0ZW0uRHJhd2luZy5Db2xvciwgU3lzdGVtLkRyYXdpbmcsIFZl
|
||||
cnNpb249NC4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj1iMDNmNWY3ZjExZDUw
|
||||
YTNhXSxbU3lzdGVtLkRvdWJsZSwgbXNjb3JsaWIsIFZlcnNpb249NC4wLjAuMCwgQ3VsdHVyZT1uZXV0
|
||||
cmFsLCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5XV0sIG1zY29ybGliLCBWZXJzaW9uPTQu
|
||||
MC4wLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49Yjc3YTVjNTYxOTM0ZTA4OV1dAwAA
|
||||
AAZfaXRlbXMFX3NpemUIX3ZlcnNpb24DAADdAVN5c3RlbS5WYWx1ZVR1cGxlYDJbW1N5c3RlbS5EcmF3
|
||||
aW5nLkNvbG9yLCBTeXN0ZW0uRHJhd2luZywgVmVyc2lvbj00LjAuMC4wLCBDdWx0dXJlPW5ldXRyYWws
|
||||
IFB1YmxpY0tleVRva2VuPWIwM2Y1ZjdmMTFkNTBhM2FdLFtTeXN0ZW0uRG91YmxlLCBtc2NvcmxpYiwg
|
||||
VmVyc2lvbj00LjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkz
|
||||
NGUwODldXVtdCAgJAgAAAAAAAAAAAAAABwIAAAAAAQAAAAAAAAAD2wFTeXN0ZW0uVmFsdWVUdXBsZWAy
|
||||
W1tTeXN0ZW0uRHJhd2luZy5Db2xvciwgU3lzdGVtLkRyYXdpbmcsIFZlcnNpb249NC4wLjAuMCwgQ3Vs
|
||||
dHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj1iMDNmNWY3ZjExZDUwYTNhXSxbU3lzdGVtLkRvdWJs
|
||||
ZSwgbXNjb3JsaWIsIFZlcnNpb249NC4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tl
|
||||
bj1iNzdhNWM1NjE5MzRlMDg5XV0L
|
||||
</value>
|
||||
</data>
|
||||
</root>
|
||||
@ -1,4 +1,6 @@
|
||||
using Graphing.Forms;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
|
||||
namespace Graphing;
|
||||
|
||||
@ -7,12 +9,12 @@ public abstract class Graphable
|
||||
private static int defaultColorsUsed;
|
||||
public static readonly uint[] DefaultColors =
|
||||
[
|
||||
0xEF_B34D47, // Red
|
||||
0xEF_4769B3, // Blue
|
||||
0xEF_50B347, // Green
|
||||
0xEF_7047B3, // Purple
|
||||
0xEF_B38B47, // Orange
|
||||
0xEF_5B5B5B // Black
|
||||
0xFF_B34D47, // Red
|
||||
0xFF_4769B3, // Blue
|
||||
0xFF_50B347, // Green
|
||||
0xFF_7047B3, // Purple
|
||||
0xFF_B38B47, // Orange
|
||||
0xFF_5B5B5B // Black
|
||||
];
|
||||
|
||||
public Color Color { get; set; }
|
||||
@ -26,5 +28,14 @@ public abstract class Graphable
|
||||
Name = "Unnamed Graphable.";
|
||||
}
|
||||
|
||||
public abstract IEnumerable<Line2d> GetItemsToRender(in GraphForm graph);
|
||||
public abstract IEnumerable<IGraphPart> GetItemsToRender(in GraphForm graph);
|
||||
|
||||
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 IEnumerable<IGraphPart> GetSelectionItemsToRender(in GraphForm graph, Float2 graphMousePos) => [];
|
||||
}
|
||||
|
||||
133
Base/Graphables/ColumnTable.cs
Normal file
133
Base/Graphables/ColumnTable.cs
Normal file
@ -0,0 +1,133 @@
|
||||
using Graphing.Forms;
|
||||
using Graphing.Parts;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
|
||||
namespace Graphing.Graphables;
|
||||
|
||||
public class ColumnTable : Graphable
|
||||
{
|
||||
private static int tableNum;
|
||||
|
||||
protected readonly Dictionary<double, double> tableXY;
|
||||
protected readonly double width;
|
||||
|
||||
public ColumnTable(double width, Dictionary<double, double> tableXY)
|
||||
{
|
||||
tableNum++;
|
||||
Name = $"Column Table {tableNum}";
|
||||
|
||||
this.tableXY = tableXY;
|
||||
this.width = width;
|
||||
}
|
||||
public ColumnTable(double step, Equation equation, double min, double max)
|
||||
{
|
||||
Color = equation.Color;
|
||||
Name = $"Column Table for {equation.Name}";
|
||||
|
||||
tableXY = [];
|
||||
EquationDelegate equ = equation.GetDelegate();
|
||||
width = 0.75 * step;
|
||||
|
||||
double minRounded = Math.Round(min / step) * step,
|
||||
maxRounded = Math.Round(max / step) * step;
|
||||
for (double x = minRounded; x <= maxRounded; x += step)
|
||||
tableXY.Add(x, equ(x));
|
||||
}
|
||||
|
||||
public override long GetCacheBytes() => 16 * tableXY.Count;
|
||||
|
||||
public override Graphable ShallowCopy() => new ColumnTable(width / 0.75, tableXY);
|
||||
|
||||
public override IEnumerable<IGraphPart> GetItemsToRender(in GraphForm graph)
|
||||
{
|
||||
List<IGraphPart> items = [];
|
||||
foreach (KeyValuePair<double, double> col in tableXY)
|
||||
{
|
||||
items.Add(GraphRectangle.FromSize(new Float2(col.Key, col.Value / 2),
|
||||
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) { }
|
||||
}
|
||||
@ -1,12 +1,27 @@
|
||||
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 Equation : Graphable
|
||||
public class Equation : Graphable, IIntegrable, IDerivable, ITranslatableXY, IConvertSlopeField,
|
||||
IConvertColumnTable
|
||||
{
|
||||
private static int equationNum;
|
||||
|
||||
private readonly EquationDelegate equ;
|
||||
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)
|
||||
{
|
||||
@ -14,28 +29,140 @@ public class Equation : Graphable
|
||||
Name = $"Equation {equationNum}";
|
||||
|
||||
this.equ = equ;
|
||||
cache = [];
|
||||
|
||||
OffsetX = 0;
|
||||
OffsetY = 0;
|
||||
|
||||
OnInvalidate = delegate { };
|
||||
}
|
||||
|
||||
public override IEnumerable<Line2d> GetItemsToRender(in GraphForm graph)
|
||||
public override IEnumerable<IGraphPart> GetItemsToRender(in GraphForm graph)
|
||||
{
|
||||
List<Line2d> lines = [];
|
||||
const int step = 10;
|
||||
|
||||
double epsilon = Math.Abs(graph.ScreenSpaceToGraphSpace(new Int2(0, 0)).x
|
||||
- graph.ScreenSpaceToGraphSpace(new Int2(step / 2, 0)).x) / 5;
|
||||
epsilon *= graph.DpiFloat / 192;
|
||||
|
||||
List<IGraphPart> lines = [];
|
||||
|
||||
double previousX = graph.MinVisibleGraph.x;
|
||||
double previousY = equ(previousX);
|
||||
for (int i = 1; i < graph.ClientRectangle.Width; i += 10)
|
||||
double previousY = GetFromCache(previousX, epsilon);
|
||||
|
||||
for (int i = 0; i < graph.ClientRectangle.Width + step; i += step)
|
||||
{
|
||||
double currentX = graph.ScreenSpaceToGraphSpace(new Int2(i, 0)).x;
|
||||
double currentY = equ(currentX);
|
||||
double currentY = GetFromCache(currentX, epsilon);
|
||||
if (Math.Abs(currentY - previousY) <= 10)
|
||||
{
|
||||
lines.Add(new Line2d(new Float2(previousX, previousY), new Float2(currentX, currentY)));
|
||||
lines.Add(new GraphLine(new Float2(previousX, previousY), new Float2(currentX, currentY)));
|
||||
}
|
||||
previousX = currentX;
|
||||
previousY = currentY;
|
||||
}
|
||||
OnInvalidate.Invoke(graph);
|
||||
return lines;
|
||||
}
|
||||
|
||||
protected double DerivativeAtPoint(double x)
|
||||
{
|
||||
const double step = 1e-3;
|
||||
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 - OffsetX);
|
||||
if (dist < epsilon) return nearest + OffsetY;
|
||||
else
|
||||
{
|
||||
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);
|
||||
else if (cache.Count == 1)
|
||||
{
|
||||
Float2 single = cache[0];
|
||||
return (Math.Abs(single.x - x), single.y, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
int boundA = 0, boundB = cache.Count;
|
||||
do
|
||||
{
|
||||
int boundC = (boundA + boundB) / 2;
|
||||
Float2 pointC = cache[boundC];
|
||||
|
||||
if (pointC.x == x) return (0, pointC.y, boundC);
|
||||
else if (pointC.x > x)
|
||||
{
|
||||
boundA = boundC;
|
||||
}
|
||||
else // pointC.x < x
|
||||
{
|
||||
boundB = boundC;
|
||||
}
|
||||
|
||||
} while (boundB - boundA > 1);
|
||||
|
||||
return (Math.Abs(cache[boundA].x - x), cache[boundA].y, boundA);
|
||||
}
|
||||
}
|
||||
|
||||
public override Graphable ShallowCopy() => new Equation(equ);
|
||||
|
||||
public override long GetCacheBytes() => cache.Count * 16;
|
||||
|
||||
public override bool ShouldSelectGraphable(in GraphForm graph, Float2 graphMousePos, double factor)
|
||||
{
|
||||
Int2 screenMousePos = graph.GraphSpaceToScreenSpace(graphMousePos);
|
||||
|
||||
(_, _, int index) = NearestCachedPoint(graphMousePos.x);
|
||||
Int2 screenCachePos = graph.GraphSpaceToScreenSpace(cache[index]);
|
||||
|
||||
double allowedDist = factor * graph.DpiFloat * 80 / 192;
|
||||
|
||||
Int2 dist = new(screenCachePos.x - screenMousePos.x,
|
||||
screenCachePos.y - screenMousePos.y);
|
||||
double totalDist = Math.Sqrt(dist.x * dist.x + dist.y * dist.y);
|
||||
return totalDist <= allowedDist;
|
||||
}
|
||||
public override 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)
|
||||
{
|
||||
for (double x = xRange.x; x <= xRange.y; x += step) GetFromCache(x, step);
|
||||
}
|
||||
}
|
||||
|
||||
public delegate double EquationDelegate(double x);
|
||||
|
||||
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
|
||||
};
|
||||
}
|
||||
239
Base/Graphables/IntegralEquation.cs
Normal file
239
Base/Graphables/IntegralEquation.cs
Normal file
@ -0,0 +1,239 @@
|
||||
using Graphing.Abstract;
|
||||
using Graphing.Forms;
|
||||
using Graphing.Parts;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Design;
|
||||
|
||||
namespace Graphing.Graphables;
|
||||
|
||||
public class IntegralEquation : Graphable, IIntegrable, IDerivable
|
||||
{
|
||||
protected readonly Equation? baseEqu;
|
||||
protected readonly EquationDelegate? baseEquDel;
|
||||
|
||||
protected readonly IntegralEquation? altBaseEqu;
|
||||
|
||||
protected readonly bool usingAlt;
|
||||
|
||||
public IntegralEquation(Equation baseEquation)
|
||||
{
|
||||
string oldName = baseEquation.Name, newName;
|
||||
if (oldName.StartsWith("Integral of ")) newName = "Second Integral of " + oldName[12..];
|
||||
else if (oldName.StartsWith("Second Integral of ")) newName = "Third Integral of " + oldName[19..];
|
||||
else newName = "Integral of " + oldName;
|
||||
|
||||
Name = newName;
|
||||
|
||||
baseEqu = baseEquation;
|
||||
baseEquDel = baseEquation.GetDelegate();
|
||||
|
||||
altBaseEqu = null;
|
||||
usingAlt = false;
|
||||
}
|
||||
public IntegralEquation(IntegralEquation baseEquation)
|
||||
{
|
||||
string oldName = baseEquation.Name, newName;
|
||||
if (oldName.StartsWith("Integral of ")) newName = "Second Integral of " + oldName[12..];
|
||||
else if (oldName.StartsWith("Second Integral of ")) newName = "Third Integral of " + oldName[19..];
|
||||
else newName = "Integral of " + oldName;
|
||||
|
||||
Name = newName;
|
||||
|
||||
baseEqu = null;
|
||||
baseEquDel = null;
|
||||
|
||||
altBaseEqu = baseEquation;
|
||||
usingAlt = true;
|
||||
}
|
||||
|
||||
public override Graphable ShallowCopy() => new IntegralEquation(this);
|
||||
|
||||
public override IEnumerable<IGraphPart> GetItemsToRender(in GraphForm graph)
|
||||
{
|
||||
const int step = 10;
|
||||
double epsilon = Math.Abs(graph.ScreenSpaceToGraphSpace(new Int2(0, 0)).x
|
||||
- graph.ScreenSpaceToGraphSpace(new Int2(step / 2, 0)).x) / 5;
|
||||
epsilon *= graph.DpiFloat / 192;
|
||||
List<IGraphPart> lines = [];
|
||||
|
||||
Int2 originLocation = graph.GraphSpaceToScreenSpace(new Float2(0, 0));
|
||||
if (originLocation.x < 0)
|
||||
{
|
||||
// Origin is off the left side of the screen.
|
||||
// Get to the left side from the origin.
|
||||
double start = graph.MinVisibleGraph.x, end = graph.MaxVisibleGraph.x;
|
||||
SetInternalStepper(start, epsilon);
|
||||
|
||||
// Now we can start.
|
||||
double previousX = stepX;
|
||||
double previousY = stepY;
|
||||
for (double x = start; x <= end; x += epsilon)
|
||||
{
|
||||
MoveInternalStepper(epsilon);
|
||||
lines.Add(new GraphLine(new Float2(previousX, previousY),
|
||||
new Float2(stepX, stepY)));
|
||||
previousX = stepX;
|
||||
previousY = stepY;
|
||||
}
|
||||
}
|
||||
else if (originLocation.x > graph.ClientRectangle.Width)
|
||||
{
|
||||
// Origin is off the right side of the screen.
|
||||
// Get to the right side of the origin.
|
||||
double start = graph.MaxVisibleGraph.x, end = graph.MinVisibleGraph.x;
|
||||
SetInternalStepper(start, epsilon);
|
||||
|
||||
// Now we can start.
|
||||
double previousX = stepX;
|
||||
double previousY = stepY;
|
||||
for (double x = start; x >= end; x -= epsilon)
|
||||
{
|
||||
MoveInternalStepper(-epsilon);
|
||||
lines.Add(new GraphLine(new Float2(previousX, previousY),
|
||||
new Float2(stepX, stepY)));
|
||||
previousX = stepX;
|
||||
previousY = stepY;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Origin is on-screen.
|
||||
// We need to do two cycles.
|
||||
|
||||
// Start with right.
|
||||
double start = 0, end = graph.MaxVisibleGraph.x;
|
||||
SetInternalStepper(start, epsilon);
|
||||
|
||||
double previousX = stepX;
|
||||
double previousY = stepY;
|
||||
for (double x = start; x <= end; x += epsilon)
|
||||
{
|
||||
MoveInternalStepper(epsilon);
|
||||
lines.Add(new GraphLine(new Float2(previousX, previousY),
|
||||
new Float2(stepX, stepY)));
|
||||
previousX = stepX;
|
||||
previousY = stepY;
|
||||
}
|
||||
|
||||
// Now do left.
|
||||
start = 0;
|
||||
end = graph.MinVisibleGraph.x;
|
||||
SetInternalStepper(start, epsilon);
|
||||
|
||||
previousX = stepX;
|
||||
previousY = stepY;
|
||||
|
||||
for (double x = start; x >= end; x -= epsilon)
|
||||
{
|
||||
MoveInternalStepper(-epsilon);
|
||||
lines.Add(new GraphLine(new Float2(previousX, previousY),
|
||||
new Float2(stepX, stepY)));
|
||||
previousX = stepX;
|
||||
previousY = stepY;
|
||||
}
|
||||
}
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
private double stepX = 0;
|
||||
private double stepY = 0;
|
||||
private void SetInternalStepper(double x, double dX)
|
||||
{
|
||||
stepX = 0;
|
||||
stepY = 0;
|
||||
if (usingAlt) altBaseEqu!.SetInternalStepper(0, dX);
|
||||
|
||||
if (x > 0)
|
||||
{
|
||||
while (stepX < x) MoveInternalStepper(dX);
|
||||
}
|
||||
else if (x < 0)
|
||||
{
|
||||
while (x < stepX) MoveInternalStepper(-dX);
|
||||
}
|
||||
}
|
||||
private void MoveInternalStepper(double dX)
|
||||
{
|
||||
stepX += dX;
|
||||
if (usingAlt)
|
||||
{
|
||||
altBaseEqu!.MoveInternalStepper(dX);
|
||||
stepY += altBaseEqu!.stepY * dX;
|
||||
}
|
||||
else
|
||||
{
|
||||
stepY += (baseEquDel!(stepX - baseEqu!.OffsetX) + baseEqu.OffsetY) * dX;
|
||||
}
|
||||
}
|
||||
|
||||
// Try to avoid using this, as it converts the integral into a
|
||||
// far less efficient format (uses the `IntegralAtPoint` method).
|
||||
public Equation AsEquation() => new(IntegralAtPoint)
|
||||
{
|
||||
Name = Name,
|
||||
Color = Color
|
||||
};
|
||||
|
||||
public Graphable Derive()
|
||||
{
|
||||
if (usingAlt) return altBaseEqu!.ShallowCopy();
|
||||
else return (Equation)baseEqu!.ShallowCopy();
|
||||
}
|
||||
public Graphable Integrate() => new IntegralEquation(this);
|
||||
|
||||
// Standard integral method.
|
||||
// Inefficient for successive calls.
|
||||
public double IntegralAtPoint(double x)
|
||||
{
|
||||
if (x > 0)
|
||||
{
|
||||
double start = Math.Min(0, x), end = Math.Max(0, x);
|
||||
const double step = 1e-3;
|
||||
double sum = 0;
|
||||
|
||||
SetInternalStepper(start, step);
|
||||
for (double t = start; t <= end; t += step)
|
||||
{
|
||||
MoveInternalStepper(step);
|
||||
sum += stepY * step;
|
||||
}
|
||||
|
||||
return sum;
|
||||
}
|
||||
else if (x < 0)
|
||||
{
|
||||
double start = Math.Max(0, x), end = Math.Min(0, x);
|
||||
const double step = 1e-3;
|
||||
double sum = 0;
|
||||
|
||||
SetInternalStepper(start, step);
|
||||
for (double t = start; t >= end; t -= step)
|
||||
{
|
||||
MoveInternalStepper(-step);
|
||||
sum -= stepY * step;
|
||||
}
|
||||
|
||||
return sum;
|
||||
}
|
||||
else return 0;
|
||||
}
|
||||
|
||||
public override bool ShouldSelectGraphable(in GraphForm graph, Float2 graphMousePos, double factor)
|
||||
{
|
||||
Int2 screenMousePos = graph.GraphSpaceToScreenSpace(graphMousePos);
|
||||
|
||||
Int2 screenPos = graph.GraphSpaceToScreenSpace(new Float2(graphMousePos.x,
|
||||
IntegralAtPoint(graphMousePos.x)));
|
||||
|
||||
double allowedDist = factor * graph.DpiFloat * 80 / 192;
|
||||
|
||||
Int2 dist = new(screenPos.x - screenMousePos.x,
|
||||
screenPos.y - screenMousePos.y);
|
||||
double totalDist = Math.Sqrt(dist.x * dist.x + dist.y * dist.y);
|
||||
return totalDist <= allowedDist;
|
||||
}
|
||||
public override 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);
|
||||
@ -1,4 +1,8 @@
|
||||
using Graphing.Forms;
|
||||
using Graphing.Parts;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
|
||||
namespace Graphing.Graphables;
|
||||
|
||||
@ -6,37 +10,61 @@ public class SlopeField : Graphable
|
||||
{
|
||||
private static int slopeFieldNum;
|
||||
|
||||
private readonly SlopeFieldsDelegate equ;
|
||||
private readonly double 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;
|
||||
|
||||
public SlopeField(int detail, SlopeFieldsDelegate equ)
|
||||
protected readonly SlopeFieldsDelegate equ;
|
||||
protected readonly List<(Float2, GraphLine)> cache;
|
||||
|
||||
public SlopeField(double detail, SlopeFieldsDelegate equ)
|
||||
{
|
||||
slopeFieldNum++;
|
||||
Name = $"Slope Field {slopeFieldNum}";
|
||||
|
||||
this.equ = equ;
|
||||
this.detail = detail;
|
||||
_detail = detail;
|
||||
cache = [];
|
||||
}
|
||||
|
||||
public override IEnumerable<Line2d> GetItemsToRender(in GraphForm graph)
|
||||
public override IEnumerable<IGraphPart> GetItemsToRender(in GraphForm graph)
|
||||
{
|
||||
List<Line2d> lines = [];
|
||||
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 / 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 / detail)
|
||||
for (double y = minY; y < maxY; y += step)
|
||||
{
|
||||
double slope = equ(x, y);
|
||||
lines.Add(MakeSlopeLine(new Float2(x, y), slope));
|
||||
lines.Add(GetFromCache(epsilon, x, y));
|
||||
}
|
||||
}
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
private Line2d MakeSlopeLine(Float2 position, double slope)
|
||||
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);
|
||||
@ -46,6 +74,85 @@ public class SlopeField : Graphable
|
||||
|
||||
return new(new(position.x + dirX, position.y + dirY), new(position.x - dirX, position.y - dirY));
|
||||
}
|
||||
protected GraphLine GetFromCache(double epsilon, double x, double y)
|
||||
{
|
||||
// Probably no binary search here, though maybe it could be done
|
||||
// in terms of just one axis.
|
||||
|
||||
foreach ((Float2 p, GraphLine l) in cache)
|
||||
{
|
||||
double diffX = Math.Abs(p.x - x),
|
||||
diffY = Math.Abs(p.y - y);
|
||||
|
||||
if (diffX < epsilon && diffY < epsilon) return l;
|
||||
}
|
||||
|
||||
// Create a new value.
|
||||
double slope = equ(x, y);
|
||||
GraphLine result = MakeSlopeLine(new Float2(x, y), slope);
|
||||
cache.Add((new Float2(x, y), result));
|
||||
return result;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
double epsilon = 1 / (_detail * 2.0);
|
||||
GraphLine line = GetFromCache(epsilon, nearestPos.x, nearestPos.y);
|
||||
double slope = (line.b.y - line.a.y) / (line.b.x - line.a.x);
|
||||
|
||||
if (graphMousePos.x < Math.Min(line.a.x, line.b.x) ||
|
||||
graphMousePos.x > Math.Max(line.a.x, line.b.x)) return false;
|
||||
|
||||
double allowedDist = factor * graph.DpiFloat * 10 / 192;
|
||||
|
||||
double lineX = graphMousePos.x,
|
||||
lineY = slope * (lineX - nearestPos.x) + nearestPos.y;
|
||||
|
||||
Int2 pointScreen = graph.GraphSpaceToScreenSpace(new Float2(lineX, lineY));
|
||||
Int2 mouseScreen = graph.GraphSpaceToScreenSpace(graphMousePos);
|
||||
Int2 dist = new(pointScreen.x - mouseScreen.x,
|
||||
pointScreen.y - mouseScreen.y);
|
||||
double totalDist = Math.Sqrt(dist.x * dist.x + dist.y * dist.y);
|
||||
return totalDist <= allowedDist;
|
||||
}
|
||||
public override IEnumerable<IGraphPart> GetSelectionItemsToRender(in GraphForm graph, Float2 graphMousePos)
|
||||
{
|
||||
Float2 nearestPos = new(Math.Round(graphMousePos.x * _detail) / _detail,
|
||||
Math.Round(graphMousePos.y * _detail) / _detail);
|
||||
|
||||
double epsilon = 1 / (_detail * 2.0);
|
||||
GraphLine line = GetFromCache(epsilon, nearestPos.x, nearestPos.y);
|
||||
double slope = (line.b.y - line.a.y) / (line.b.x - line.a.x);
|
||||
|
||||
double lineX = graphMousePos.x,
|
||||
lineY = slope * (lineX - nearestPos.x) + nearestPos.y;
|
||||
Float2 point = new(lineX, lineY);
|
||||
|
||||
return
|
||||
[
|
||||
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 y = Math.Ceiling(yRange.x - 1); y < yRange.y + 1; y += 1.0 / _detail)
|
||||
{
|
||||
GetFromCache(step, x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public delegate double SlopeFieldsDelegate(double x, double y);
|
||||
|
||||
152
Base/Graphables/TangentLine.cs
Normal file
152
Base/Graphables/TangentLine.cs
Normal file
@ -0,0 +1,152 @@
|
||||
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, IConvertEquation, ITranslatableX
|
||||
{
|
||||
public bool UngraphWhenConvertedToEquation => true;
|
||||
|
||||
public double Position
|
||||
{
|
||||
get => _position;
|
||||
set
|
||||
{
|
||||
currentSlope = DerivativeAtPoint(value);
|
||||
_position = value;
|
||||
}
|
||||
}
|
||||
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 double length;
|
||||
|
||||
// X is slope, Y is height.
|
||||
protected Float2 currentSlope;
|
||||
|
||||
// No binary search for this, I want it to be exact.
|
||||
// Value: X is slope, Y is height.
|
||||
protected Dictionary<double, Float2> slopeCache;
|
||||
|
||||
public TangentLine(double length, double position, Equation parent)
|
||||
{
|
||||
Name = $"Tangent Line of {parent.Name}";
|
||||
|
||||
slopeCache = [];
|
||||
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)
|
||||
];
|
||||
}
|
||||
protected GraphLine MakeSlopeLine()
|
||||
{
|
||||
double dirX = length, dirY = currentSlope.x * length;
|
||||
double magnitude = Math.Sqrt(dirX * dirX + dirY * dirY);
|
||||
|
||||
dirX /= magnitude * 2 / length;
|
||||
dirY /= magnitude * 2 / length;
|
||||
|
||||
return new(new(Position + dirX, currentSlope.y + dirY), new(Position - dirX, currentSlope.y - dirY));
|
||||
}
|
||||
protected Float2 DerivativeAtPoint(double x)
|
||||
{
|
||||
// If value is already computed, return it.
|
||||
if (slopeCache.TryGetValue(x, out Float2 val)) return val;
|
||||
|
||||
const double step = 1e-3;
|
||||
|
||||
double initial = parent.GetValueAt(x);
|
||||
Float2 result = new((parent.GetValueAt(x + step) - initial) / step, initial);
|
||||
slopeCache.Add(x, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
public override Graphable ShallowCopy() => new TangentLine(length, Position, parent);
|
||||
|
||||
public override void EraseCache() => slopeCache.Clear();
|
||||
public override long GetCacheBytes() => slopeCache.Count * 24;
|
||||
|
||||
public override bool ShouldSelectGraphable(in GraphForm graph, Float2 graphMousePos, double factor)
|
||||
{
|
||||
GraphLine line = MakeSlopeLine();
|
||||
|
||||
if (graphMousePos.x < Math.Min(line.a.x - 0.25, line.b.x - 0.25) ||
|
||||
graphMousePos.x > Math.Max(line.a.x + 0.25, line.b.x + 0.25)) return false;
|
||||
|
||||
double allowedDist = factor * graph.DpiFloat * 80 / 192;
|
||||
|
||||
double lineX = graphMousePos.x,
|
||||
lineY = currentSlope.x * (lineX - Position) + currentSlope.y;
|
||||
|
||||
Int2 pointScreen = graph.GraphSpaceToScreenSpace(new Float2(lineX, lineY));
|
||||
Int2 mouseScreen = graph.GraphSpaceToScreenSpace(graphMousePos);
|
||||
Int2 dist = new(pointScreen.x - mouseScreen.x,
|
||||
pointScreen.y - mouseScreen.y);
|
||||
double totalDist = Math.Sqrt(dist.x * dist.x + dist.y * dist.y);
|
||||
return totalDist <= allowedDist;
|
||||
}
|
||||
public override IEnumerable<IGraphPart> GetSelectionItemsToRender(in GraphForm graph, Float2 graphMousePos)
|
||||
{
|
||||
GraphLine line = MakeSlopeLine();
|
||||
|
||||
double lineX = Math.Clamp(graphMousePos.x,
|
||||
Math.Min(line.a.x, line.b.x),
|
||||
Math.Max(line.a.x, line.b.x)),
|
||||
lineY = currentSlope.x * (lineX - Position) + currentSlope.y;
|
||||
|
||||
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)
|
||||
{
|
||||
// Despite the tangent line barely using any data, when preloaded it
|
||||
// will always take as much memory as an equation. Seems like a bit much,
|
||||
// but may be used when the tangent line is moved. Not sure there's much
|
||||
// that can be changed.
|
||||
for (double x = xRange.x; x <= xRange.y; x += step) DerivativeAtPoint(x);
|
||||
}
|
||||
|
||||
public Equation ToEquation()
|
||||
{
|
||||
double slope = currentSlope.x, x1 = Position, y1 = currentSlope.y;
|
||||
return new(x => slope * (x - x1) + y1)
|
||||
{
|
||||
Name = Name,
|
||||
Color = Color
|
||||
};
|
||||
}
|
||||
}
|
||||
9
Base/IGraphPart.cs
Normal file
9
Base/IGraphPart.cs
Normal file
@ -0,0 +1,9 @@
|
||||
using Graphing.Forms;
|
||||
using System.Drawing;
|
||||
|
||||
namespace Graphing;
|
||||
|
||||
public interface IGraphPart
|
||||
{
|
||||
public void Render(in GraphForm form, in Graphics g, in Pen pen);
|
||||
}
|
||||
@ -1,4 +1,6 @@
|
||||
namespace Graphing;
|
||||
using System.Drawing;
|
||||
|
||||
namespace Graphing;
|
||||
|
||||
public record struct Int2
|
||||
{
|
||||
|
||||
@ -1,18 +0,0 @@
|
||||
namespace Graphing;
|
||||
|
||||
public record struct Line2d
|
||||
{
|
||||
public Float2 a;
|
||||
public Float2 b;
|
||||
|
||||
public Line2d()
|
||||
{
|
||||
a = new();
|
||||
b = new();
|
||||
}
|
||||
public Line2d(Float2 a, Float2 b)
|
||||
{
|
||||
this.a = a;
|
||||
this.b = b;
|
||||
}
|
||||
}
|
||||
31
Base/Parts/GraphLine.cs
Normal file
31
Base/Parts/GraphLine.cs
Normal file
@ -0,0 +1,31 @@
|
||||
using Graphing.Forms;
|
||||
using System.Drawing;
|
||||
|
||||
namespace Graphing.Parts;
|
||||
|
||||
public record struct GraphLine : IGraphPart
|
||||
{
|
||||
public Float2 a;
|
||||
public Float2 b;
|
||||
|
||||
public GraphLine()
|
||||
{
|
||||
a = new();
|
||||
b = new();
|
||||
}
|
||||
public GraphLine(Float2 a, Float2 b)
|
||||
{
|
||||
this.a = a;
|
||||
this.b = b;
|
||||
}
|
||||
|
||||
public readonly void Render(in GraphForm form, in Graphics g, in Pen pen)
|
||||
{
|
||||
if (!double.IsFinite(a.x) || !double.IsFinite(a.y) ||
|
||||
!double.IsFinite(b.x) || !double.IsFinite(b.y)) return;
|
||||
|
||||
Int2 start = form.GraphSpaceToScreenSpace(a),
|
||||
end = form.GraphSpaceToScreenSpace(b);
|
||||
g.DrawLine(pen, start, end);
|
||||
}
|
||||
}
|
||||
53
Base/Parts/GraphRectangle.cs
Normal file
53
Base/Parts/GraphRectangle.cs
Normal file
@ -0,0 +1,53 @@
|
||||
using Graphing.Forms;
|
||||
using System.Drawing;
|
||||
|
||||
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, 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),
|
||||
opacity = opacity,
|
||||
};
|
||||
public static GraphRectangle FromRange(Float2 min, Float2 max, double opacity = 1) => new()
|
||||
{
|
||||
min = min,
|
||||
max = max,
|
||||
opacity = opacity,
|
||||
};
|
||||
|
||||
public void Render(in GraphForm form, in Graphics g, in Pen pen)
|
||||
{
|
||||
if (!double.IsFinite(max.x) || !double.IsFinite(max.y) ||
|
||||
!double.IsFinite(min.x) || !double.IsFinite(min.y)) return;
|
||||
|
||||
if (min.x > max.x) (min.x, max.x) = (max.x, min.x);
|
||||
if (min.y > max.y) (min.y, max.y) = (max.y, min.y);
|
||||
|
||||
Int2 start = form.GraphSpaceToScreenSpace(min),
|
||||
end = form.GraphSpaceToScreenSpace(max);
|
||||
|
||||
Int2 size = new(end.x - start.x + 1,
|
||||
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;
|
||||
}
|
||||
}
|
||||
34
Base/Parts/GraphUiCircle.cs
Normal file
34
Base/Parts/GraphUiCircle.cs
Normal file
@ -0,0 +1,34 @@
|
||||
using Graphing.Forms;
|
||||
using System.Drawing;
|
||||
|
||||
namespace Graphing.Parts;
|
||||
|
||||
public record struct GraphUiCircle : IGraphPart
|
||||
{
|
||||
public Float2 center;
|
||||
public int radius;
|
||||
|
||||
public GraphUiCircle()
|
||||
{
|
||||
center = new();
|
||||
radius = 1;
|
||||
}
|
||||
public GraphUiCircle(Float2 center, int radius = 8)
|
||||
{
|
||||
this.center = center;
|
||||
this.radius = radius;
|
||||
}
|
||||
|
||||
public readonly void Render(in GraphForm form, in Graphics g, in Pen pen)
|
||||
{
|
||||
if (!double.IsFinite(center.x) || !double.IsFinite(center.y) ||
|
||||
!double.IsFinite(radius) || radius == 0) return;
|
||||
|
||||
int rad = (int)(form.DpiFloat * radius / 192);
|
||||
|
||||
Int2 centerPix = form.GraphSpaceToScreenSpace(center);
|
||||
g.FillEllipse(pen.Brush, new Rectangle(new Point(centerPix.x - rad,
|
||||
centerPix.y - rad),
|
||||
new Size(rad * 2, rad * 2)));
|
||||
}
|
||||
}
|
||||
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));
|
||||
}
|
||||
}
|
||||
13
Base/Properties/PublishProfiles/FolderProfile.pubxml
Normal file
13
Base/Properties/PublishProfiles/FolderProfile.pubxml
Normal file
@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
https://go.microsoft.com/fwlink/?LinkID=208121.
|
||||
-->
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Any CPU</Platform>
|
||||
<PublishDir>bin\Release\net8.0-windows\publish\</PublishDir>
|
||||
<PublishProtocol>FileSystem</PublishProtocol>
|
||||
<_TargetId>Folder</_TargetId>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
10
Base/Properties/PublishProfiles/FolderProfile.pubxml.user
Normal file
10
Base/Properties/PublishProfiles/FolderProfile.pubxml.user
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
https://go.microsoft.com/fwlink/?LinkID=208121.
|
||||
-->
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<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>
|
||||
@ -1,27 +0,0 @@
|
||||
namespace Graphing;
|
||||
|
||||
public record struct Range2d
|
||||
{
|
||||
public double minX;
|
||||
public double minY;
|
||||
public double maxX;
|
||||
public double maxY;
|
||||
|
||||
public Range2d()
|
||||
{
|
||||
minX = 0;
|
||||
minY = 0;
|
||||
maxX = 0;
|
||||
maxY = 0;
|
||||
}
|
||||
public Range2d(double minX, double minY, double maxX, double maxY)
|
||||
{
|
||||
this.minX = minX;
|
||||
this.minY = minY;
|
||||
this.maxX = maxX;
|
||||
this.maxY = maxY;
|
||||
}
|
||||
|
||||
public readonly bool Contains(Float2 p) =>
|
||||
p.x >= minX && p.x <= maxX && p.y >= minY && p.y <= maxY;
|
||||
}
|
||||
10
README.md
10
README.md
@ -3,9 +3,15 @@
|
||||
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.
|
||||
- Display a vertical bar graph.
|
||||
|
||||
However, you can develop your own features as well.
|
||||
|
||||
The system does not and likely will not (at least for a while) support text-to-equation parsing. You must import this project as a library and add graphs that way.
|
||||
|
||||
@ -70,7 +76,7 @@ An equation requires a delegate such as the one you see. Alternatively, you can
|
||||
graph.Graph(new Equation(x => Math.Pow(2, x))
|
||||
{
|
||||
Color = Color.Green,
|
||||
Name = "2^x"
|
||||
Name = "Exponential Base 2"
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
using Graphing.Forms;
|
||||
using Graphing.Graphables;
|
||||
using System;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace Graphing.Testing;
|
||||
|
||||
@ -11,9 +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");
|
||||
graph.Graph(new Equation(Math.Cos));
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@ -1,14 +1,21 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<ImplicitUsings>disable</ImplicitUsings>
|
||||
<RootNamespace>Graphing.Testing</RootNamespace>
|
||||
<AssemblyName>ThatOneNerd.Graphing.Testing</AssemblyName>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<OutputType>Exe</OutputType>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<OutputType>WinExe</OutputType>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Base\Base.csproj" />
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user