diff --git a/ComputerAlgebra b/ComputerAlgebra
index 64c51e89..20634f1c 160000
--- a/ComputerAlgebra
+++ b/ComputerAlgebra
@@ -1 +1 @@
-Subproject commit 64c51e893d6c2dc497eb4dc4f87d278529d93009
+Subproject commit 20634f1c2465471133abb166a26b321e0003fe5b
diff --git a/LiveSPICE.sln b/LiveSPICE.sln
index 0d7267d0..6bf97481 100644
--- a/LiveSPICE.sln
+++ b/LiveSPICE.sln
@@ -27,8 +27,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MockVst", "MockVst\MockVst.
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "LiveSPICEVst", "LiveSPICEVst", "{6DD7DA6B-5277-45C3-ACBD-8306D27528D0}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ComputerAlgebra.Plotting", "ComputerAlgebra\ComputerAlgebra.Plotting\ComputerAlgebra.Plotting.csproj", "{51F8BCEC-5C29-46BA-8448-06B516715840}"
-EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Benchmarks", "Benchmarks\Benchmarks.csproj", "{ECCE5E4E-730F-4A2D-A772-3AC922D9ACD2}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{04388F7E-6B22-489C-A791-6B10372D8862}"
@@ -88,10 +86,6 @@ Global
{AD8DCC49-44C5-4E36-8F06-F100C5A7BAD4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AD8DCC49-44C5-4E36-8F06-F100C5A7BAD4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AD8DCC49-44C5-4E36-8F06-F100C5A7BAD4}.Release|Any CPU.Build.0 = Release|Any CPU
- {51F8BCEC-5C29-46BA-8448-06B516715840}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {51F8BCEC-5C29-46BA-8448-06B516715840}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {51F8BCEC-5C29-46BA-8448-06B516715840}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {51F8BCEC-5C29-46BA-8448-06B516715840}.Release|Any CPU.Build.0 = Release|Any CPU
{ECCE5E4E-730F-4A2D-A772-3AC922D9ACD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{ECCE5E4E-730F-4A2D-A772-3AC922D9ACD2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{ECCE5E4E-730F-4A2D-A772-3AC922D9ACD2}.Release|Any CPU.ActiveCfg = Release|Any CPU
diff --git a/Tests/Plotting/Plot.cs b/Tests/Plotting/Plot.cs
new file mode 100644
index 00000000..24b633b8
--- /dev/null
+++ b/Tests/Plotting/Plot.cs
@@ -0,0 +1,270 @@
+using System;
+using System.Drawing;
+using System.Drawing.Drawing2D;
+using System.Threading;
+using System.Windows.Forms;
+using Matrix2D = System.Drawing.Drawing2D.Matrix;
+
+namespace Plotting
+{
+ ///
+ /// A single plot window.
+ ///
+ public class Plot
+ {
+ protected SeriesCollection series;
+ ///
+ /// The series displayed in this plot.
+ ///
+ public SeriesCollection Series { get { return series; } }
+
+ ///
+ /// Width of the plot window.
+ ///
+ public int Width { get { return form.Width; } set { Invoke(() => form.Width = value); } }
+ ///
+ /// Height of the plot window.
+ ///
+ public int Height { get { return form.Height; } set { Invoke(() => form.Height = value); } }
+
+ private double _x0 = -10.0, _x1 = 10.0;
+ private double _y0 = double.NaN, _y1 = double.NaN;
+ ///
+ /// Plot area bounds.
+ ///
+ public double x0 { get { return _x0; } set { Invoke(() => { _x0 = value; Invalidate(); }); } }
+ public double y0 { get { return _y0; } set { Invoke(() => { _y0 = value; Invalidate(); }); } }
+ public double x1 { get { return _x1; } set { Invoke(() => { _x1 = value; Invalidate(); }); } }
+ public double y1 { get { return _y1; } set { Invoke(() => { _y1 = value; Invalidate(); }); } }
+
+ private string xlabel = null, ylabel = null;
+ ///
+ /// Axis labels.
+ ///
+ public string xLabel { get { return xlabel; } set { Invoke(() => { xlabel = value; Invalidate(); }); } }
+ public string yLabel { get { return ylabel; } set { Invoke(() => { ylabel = value; Invalidate(); }); } }
+
+ string title = null;
+ ///
+ /// Title of the plot window.
+ ///
+ public string Title { get { return title; } set { Invoke(() => { form.Text = title = value; Invalidate(); }); } }
+
+ private bool showLegend = true;
+ ///
+ /// Show or hide the legend.
+ ///
+ public bool ShowLegend { get { return showLegend; } set { Invoke(() => { showLegend = value; Invalidate(); }); } }
+
+ protected Thread thread;
+ protected Form form = new Form();
+ protected bool shown = false;
+ private void Invoke(Action action)
+ {
+ while (!shown)
+ Thread.Sleep(0);
+ form.Invoke((Delegate)action);
+ }
+
+ public Plot()
+ {
+ series = new SeriesCollection();
+ series.ItemAdded += (o, e) => Invalidate();
+ series.ItemRemoved += (o, e) => Invalidate();
+
+ form = new Form()
+ {
+ Text = "Plot",
+ Width = 300,
+ Height = 300,
+ };
+ form.Paint += Plot_Paint;
+ form.SizeChanged += Plot_SizeChanged;
+ form.Shown += (o, e) => shown = true;
+ form.KeyDown += (o, e) => { if (e.KeyCode == Keys.Escape) form.Close(); };
+
+ thread = new Thread(() => Application.Run(form));
+ thread.Start();
+ }
+
+ public void Save(string Filename)
+ {
+ Rectangle bounds = new Rectangle(0, 0, Width, Height);
+ Bitmap bitmap = new Bitmap(Width, Height);
+ Invoke(() => form.DrawToBitmap(bitmap, bounds));
+ bitmap.Save(Filename);
+ }
+
+ private RectangleF PaintTitle(Graphics G, RectangleF Area)
+ {
+ if (title == null)
+ return Area;
+
+ Font font = new Font(form.Font.FontFamily, form.Font.Size * 1.5f, FontStyle.Bold);
+
+ // Draw title.
+ SizeF sz = G.MeasureString(title, font);
+ G.DrawString(title, font, Brushes.Black, new PointF(Area.Left + (Area.Width - sz.Width) / 2, Area.Top));
+ Area.Y += sz.Height + 10.0f;
+ Area.Height -= sz.Height + 10.0f;
+
+ return Area;
+ }
+
+ private RectangleF PaintAxisLabels(Graphics G, RectangleF Area)
+ {
+ Font font = new Font(form.Font.FontFamily, form.Font.Size * 1.25f);
+
+ // Draw axis labels.
+ if (xlabel != null)
+ {
+ SizeF sz = G.MeasureString(xlabel, font);
+ G.DrawString(xlabel, font, Brushes.Black, new PointF(Area.Left + (Area.Width - sz.Width) / 2, Area.Bottom - sz.Height - 5.0f));
+ Area.Height -= sz.Height + 10.0f;
+ }
+ if (ylabel != null)
+ {
+ SizeF sz = G.MeasureString(ylabel, font);
+ G.TranslateTransform(Area.Left + 5.0f, Area.Top + (Area.Height + sz.Width) / 2.0f);
+ G.RotateTransform(-90.0f);
+ G.DrawString(ylabel, font, Brushes.Black, new PointF(0.0f, 0.0f));
+ G.ResetTransform();
+
+ Area.X += sz.Height + 10.0f;
+ Area.Width -= sz.Height + 10.0f;
+ }
+
+ Area.X += 30.0f;
+ Area.Width -= 30.0f;
+ Area.Height -= 20.0f;
+
+ return Area;
+ }
+
+ private RectangleF PaintLegend(Graphics G, RectangleF Area)
+ {
+ if (!showLegend)
+ return Area;
+
+ Font font = form.Font;
+
+ // Draw legend.
+ float legendWidth = 0.0f;
+ series.ForEach(i =>
+ {
+ SizeF sz = G.MeasureString(i.Name, font);
+ legendWidth = Math.Max(legendWidth, sz.Width);
+ });
+
+ float legendY = Area.Top;
+ series.ForEach(i =>
+ {
+ PointF lx = new PointF(Area.Right - legendWidth, legendY);
+ SizeF sz = G.MeasureString(i.Name, font);
+ G.DrawString(i.Name, font, Brushes.Black, lx);
+
+ PointF[] points = new PointF[]
+ {
+ new PointF(lx.X - 25.0f, legendY + sz.Height / 2.0f),
+ new PointF(lx.X - 5.0f, legendY + sz.Height / 2.0f),
+ };
+ G.DrawLines(i.Pen, points);
+ legendY += sz.Height;
+ });
+
+ Area.Width -= legendWidth + 30.0f;
+
+ return Area;
+ }
+
+ private void Plot_Paint(object sender, PaintEventArgs e)
+ {
+ if (series.Count == 0)
+ return;
+
+ Graphics G = e.Graphics;
+ G.SmoothingMode = SmoothingMode.AntiAlias;
+ G.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
+
+ Pen axis = Pens.Black;
+ Pen grid = Pens.LightGray;
+ Font font = form.Font;
+
+ // Compute plot area.
+ RectangleF area = e.ClipRectangle;
+ area.Inflate(-10.0f, -10.0f);
+
+ area = PaintTitle(G, area);
+ area = PaintAxisLabels(G, area);
+ area = PaintLegend(G, area);
+
+ // Draw background.
+ G.FillRectangle(Brushes.White, area);
+ G.DrawRectangle(Pens.Gray, area.Left, area.Top, area.Width, area.Height);
+
+ // Compute plot bounds.
+ PointF x0, x1;
+ double y0 = _y0, y1 = _y1;
+ if (double.IsNaN(y0) || double.IsNaN(y1))
+ series.AutoBounds(_x0, _x1, out y0, out y1);
+ x0 = new PointF((float)_x0, (float)y0);
+ x1 = new PointF((float)_x1, (float)y1);
+
+ Matrix2D T = new Matrix2D(
+ area,
+ new PointF[] { new PointF(x0.X, x1.Y), new PointF(x1.X, x1.Y), new PointF(x0.X, x0.Y) });
+ T.Invert();
+
+ // Draw axes.
+ double dx = Partition((x1.X - x0.X) / (Width / 80));
+ for (double x = x0.X - x0.X % dx; x <= x1.X; x += dx)
+ {
+ PointF tx = Tx(T, new PointF((float)x, 0.0f));
+ string s = x.ToString("G3");
+ SizeF sz = G.MeasureString(s, font);
+ G.DrawString(s, font, Brushes.Black, new PointF(tx.X - sz.Width / 2, area.Bottom + 3.0f));
+ G.DrawLine(grid, new PointF(tx.X, area.Top), new PointF(tx.X, area.Bottom));
+ }
+
+ double dy = Partition((x1.Y - x0.Y) / (Height / 50));
+ for (double y = x0.Y - x0.Y % dy; y <= x1.Y; y += dy)
+ {
+ PointF tx = Tx(T, new PointF(0.0f, (float)y));
+ string s = y.ToString("G3");
+ SizeF sz = G.MeasureString(s, font);
+ G.DrawString(s, font, Brushes.Black, new PointF(area.Left - sz.Width, tx.Y - sz.Height / 2));
+ G.DrawLine(grid, new PointF(area.Left, tx.Y), new PointF(area.Right, tx.Y));
+ }
+
+ G.DrawLine(axis, Tx(T, new PointF(x0.X, 0.0f)), Tx(T, new PointF(x1.X, 0.0f)));
+ G.DrawLine(axis, Tx(T, new PointF(0.0f, x0.Y)), Tx(T, new PointF(0.0f, x1.Y)));
+ G.DrawRectangle(Pens.Gray, area.Left, area.Top, area.Width, area.Height);
+ G.SetClip(area);
+
+ // Draw series.
+ series.ForEach(i => i.Paint(T, x0.X, x1.X, G));
+ }
+
+ private static PointF Tx(Matrix2D T, PointF x)
+ {
+ PointF[] xs = new[] { x };
+ T.TransformPoints(xs);
+ return xs[0];
+ }
+
+ private void Invalidate() { form.Invalidate(); }
+
+ private void Plot_SizeChanged(object sender, EventArgs e) { form.Invalidate(); }
+
+ private static double Partition(double P)
+ {
+ double[] Partitions = { 10.0, 4.0, 2.0 };
+
+ double p = Math.Pow(10.0, Math.Ceiling(Math.Log10(P)));
+ foreach (double i in Partitions)
+ if (p / i > P)
+ return p / i;
+ return p;
+ }
+ }
+}
diff --git a/Tests/Plotting/Series.cs b/Tests/Plotting/Series.cs
new file mode 100644
index 00000000..563c37c5
--- /dev/null
+++ b/Tests/Plotting/Series.cs
@@ -0,0 +1,109 @@
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Linq;
+using Matrix2D = System.Drawing.Drawing2D.Matrix;
+
+namespace Plotting
+{
+ public enum PointStyle
+ {
+ None,
+ Square,
+ Circle,
+ Cross,
+ }
+
+ ///
+ /// A data series.
+ ///
+ public abstract class Series
+ {
+ private string name = "y[x]";
+ public string Name { get { return name; } set { name = value; } }
+
+ private Pen pen = null;
+ public Pen Pen { get { return pen != null ? pen : Pens.Transparent; } set { pen = value; } }
+
+ protected PointStyle pointStyle = PointStyle.Square;
+ public PointStyle PointStyle { get { return pointStyle; } set { pointStyle = value; } }
+
+ public abstract List Evaluate(double x0, double x1);
+
+ public void Paint(Matrix2D T, double x0, double x1, Graphics G)
+ {
+ Pen pen = Pen;
+ List points = Evaluate(x0, x1);
+ foreach (PointF[] i in points)
+ {
+ T.TransformPoints(i);
+ G.DrawLines(pen, i);
+ }
+ }
+
+ protected static float ToFloat(double x)
+ {
+ if (x > 1e6)
+ return 1e6f;
+ else if (x < -1e6)
+ return -1e6f;
+ else
+ return (float)x;
+ }
+ }
+
+ ///
+ /// Data series derived from a lambda function.
+ ///
+ public class Function : Series
+ {
+ protected Func f;
+ public Function(Func f) { this.f = f; }
+
+ public override List Evaluate(double x0, double x1)
+ {
+ int N = 2048;
+
+ List points = new List();
+
+ List run = new List();
+ for (int i = 0; i <= N; ++i)
+ {
+ double x = ((x1 - x0) * i) / N + x0;
+ float fx = ToFloat(f(x));
+
+ if (double.IsNaN(fx) || float.IsInfinity(fx))
+ {
+ if (run.Count > 1)
+ points.Add(run.ToArray());
+ run.Clear();
+ }
+ else
+ {
+ run.Add(new PointF((float)x, fx));
+ }
+ }
+ if (run.Count > 1)
+ points.Add(run.ToArray());
+
+ return points;
+ }
+ }
+
+ ///
+ /// Explicit point list.
+ ///
+ public class Scatter : Series
+ {
+ protected PointF[] points;
+ public Scatter(KeyValuePair[] Points)
+ {
+ points = Points.Select(i => new PointF((float)i.Key, ToFloat(i.Value))).ToArray();
+ }
+
+ public override List Evaluate(double x0, double x1)
+ {
+ return new List() { points.ToArray() };
+ }
+ }
+}
diff --git a/Tests/Plotting/SeriesCollection.cs b/Tests/Plotting/SeriesCollection.cs
new file mode 100644
index 00000000..625875da
--- /dev/null
+++ b/Tests/Plotting/SeriesCollection.cs
@@ -0,0 +1,168 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Linq;
+
+namespace Plotting
+{
+ public class SeriesEventArgs : EventArgs
+ {
+ private Series s;
+ public Series Series { get { return s; } }
+
+ public SeriesEventArgs(Series S) { s = S; }
+ }
+
+ ///
+ /// Collection of Series.
+ ///
+ public class SeriesCollection : ICollection, IEnumerable
+ {
+ protected List x = new List();
+ protected List colors = new List()
+ {
+ Color.Red,
+ Color.Blue,
+ Color.Green,
+ Color.DarkRed,
+ Color.DarkGreen,
+ Color.DarkBlue,
+ };
+
+ ///
+ /// Default colors used for series if none is specified.
+ ///
+ public IList Colors { get { return colors; } }
+
+ ///
+ /// Get the series at the specified index.
+ ///
+ ///
+ ///
+ public Series this[int i] { get { return x[i]; } }
+
+ ///
+ /// Estimate useful bounds for displaying the signals in this collection.
+ ///
+ ///
+ ///
+ ///
+ ///
+ public void AutoBounds(double x0, double x1, out double y0, out double y1)
+ {
+ lock (x)
+ {
+ int N = 0;
+
+ // Compute the mean.
+ float mean = 0.0f;
+ x.ForEach(i =>
+ {
+ List xy = i.Evaluate(x0, x1);
+ mean += xy.Sum(j => j.Sum(k => k.Y));
+ N += xy.Sum(j => j.Count());
+ });
+ mean /= N;
+
+ // Compute standard deviation.
+ float stddev = 0.0f;
+ float max = 0.0f;
+ //series.ForEach(i => stddev = Math.Max(stddev, i.Evaluate(_x0, _x1).Max(j => j.Max(k => Math.Abs(mean - k.Y))) * 1.25 + 1e-6));
+ x.ForEach(i =>
+ {
+ List xy = i.Evaluate(x0, x1);
+ stddev += xy.Sum(j => j.Sum(k => (mean - k.Y) * (mean - k.Y)));
+ max = xy.Max(j => j.Max(k => Math.Abs(mean - k.Y)), max);
+ });
+ stddev = (float)Math.Sqrt(stddev / N) * 4.0f;
+ float y = Math.Min(stddev, max) * 1.25f + 1e-6f;
+
+ y0 = mean - y;
+ y1 = mean + y;
+ }
+ }
+
+ public delegate void SeriesEventHandler(object sender, SeriesEventArgs e);
+
+ private List itemAdded = new List();
+ protected void OnItemAdded(SeriesEventArgs e) { foreach (SeriesEventHandler i in itemAdded) i(this, e); }
+ ///
+ /// Called when a series is added to the collection.
+ ///
+ public event SeriesEventHandler ItemAdded
+ {
+ add { itemAdded.Add(value); }
+ remove { itemAdded.Remove(value); }
+ }
+
+ private List itemRemoved = new List();
+ protected void OnItemRemoved(SeriesEventArgs e) { foreach (SeriesEventHandler i in itemRemoved) i(this, e); }
+ ///
+ /// Called when a series is removed from the collection.
+ ///
+ public event SeriesEventHandler ItemRemoved
+ {
+ add { itemRemoved.Add(value); }
+ remove { itemRemoved.Remove(value); }
+ }
+
+ // ICollection
+ public int Count { get { lock (x) return x.Count; } }
+ public bool IsReadOnly { get { return false; } }
+ public void Add(Series item)
+ {
+ lock (x)
+ {
+
+ if (item.Pen == Pens.Transparent)
+ item.Pen = new Pen(colors.ArgMin(j => x.Count(k => k.Pen != null && k.Pen.Color == j)), 0.5f);
+ x.Add(item);
+ }
+ OnItemAdded(new SeriesEventArgs(item));
+ }
+ public void AddRange(IEnumerable items)
+ {
+ lock (x) foreach (Series i in items)
+ Add(i);
+ }
+ public void Clear()
+ {
+ Series[] removed;
+ lock (x)
+ {
+ removed = x.ToArray();
+ x.Clear();
+ }
+
+ foreach (Series i in removed)
+ OnItemRemoved(new SeriesEventArgs(i));
+ }
+ public bool Contains(Series item) { lock (x) return x.Contains(item); }
+ public void CopyTo(Series[] array, int arrayIndex) { lock (x) x.CopyTo(array, arrayIndex); }
+ public bool Remove(Series item)
+ {
+ bool ret;
+ lock (x) ret = x.Remove(item);
+ if (ret)
+ OnItemRemoved(new SeriesEventArgs(item));
+ return ret;
+ }
+ public void RemoveRange(IEnumerable items)
+ {
+ foreach (Series i in items)
+ Remove(i);
+ }
+
+ public void ForEach(Action f)
+ {
+ lock (x) foreach (Series i in x)
+ f(i);
+ }
+
+ // IEnumerable
+ public IEnumerator GetEnumerator() { return x.GetEnumerator(); }
+
+ IEnumerator IEnumerable.GetEnumerator() { return this.GetEnumerator(); }
+ }
+}
diff --git a/Tests/Test.cs b/Tests/Test.cs
index 254a159d..c2244990 100644
--- a/Tests/Test.cs
+++ b/Tests/Test.cs
@@ -1,6 +1,6 @@
using Circuit;
using ComputerAlgebra;
-using ComputerAlgebra.Plotting;
+using Plotting;
using System;
using System.Collections.Generic;
using System.IO;
diff --git a/Tests/Tests.csproj b/Tests/Tests.csproj
index 12e7da5b..3c091cb1 100644
--- a/Tests/Tests.csproj
+++ b/Tests/Tests.csproj
@@ -9,10 +9,10 @@
1.0.0.0
8
enable
+ true
-