mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2026-01-02 01:03:29 +01:00
Merge branch 'main' of tig:migueldeicaza/gui.cs
This commit is contained in:
@@ -26,6 +26,11 @@ namespace Terminal.Gui {
|
||||
int [,,] contents;
|
||||
bool [] dirtyLine;
|
||||
|
||||
/// <summary>
|
||||
/// Assists with testing, the format is rows, columns and 3 values on the last column: Rune, Attribute and Dirty Flag
|
||||
/// </summary>
|
||||
public int [,,] Contents => contents;
|
||||
|
||||
void UpdateOffscreen ()
|
||||
{
|
||||
int cols = Cols;
|
||||
|
||||
310
Terminal.Gui/Core/Graphs/Annotations.cs
Normal file
310
Terminal.Gui/Core/Graphs/Annotations.cs
Normal file
@@ -0,0 +1,310 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Terminal.Gui.Graphs {
|
||||
/// <summary>
|
||||
/// <para>Describes an overlay element that is rendered either before or
|
||||
/// after a series.</para>
|
||||
///
|
||||
/// <para>Annotations can be positioned either in screen space (e.g.
|
||||
/// a legend) or in graph space (e.g. a line showing high point)
|
||||
/// </para>
|
||||
/// <para>Unlike <see cref="ISeries"/>, annotations are allowed to
|
||||
/// draw into graph margins
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public interface IAnnotation {
|
||||
/// <summary>
|
||||
/// True if annotation should be drawn before <see cref="ISeries"/>. This
|
||||
/// allowes Series and later annotations to potentially draw over the top
|
||||
/// of this annotation.
|
||||
/// </summary>
|
||||
bool BeforeSeries { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Called once after series have been rendered (or before if <see cref="BeforeSeries"/> is true).
|
||||
/// Use <see cref="View.Driver"/> to draw and <see cref="View.Bounds"/> to avoid drawing outside of
|
||||
/// graph
|
||||
/// </summary>
|
||||
/// <param name="graph"></param>
|
||||
void Render (GraphView graph);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Displays text at a given position (in screen space or graph space)
|
||||
/// </summary>
|
||||
public class TextAnnotation : IAnnotation {
|
||||
|
||||
/// <summary>
|
||||
/// The location on screen to draw the <see cref="Text"/> regardless
|
||||
/// of scroll/zoom settings. This overrides <see cref="GraphPosition"/>
|
||||
/// if specified.
|
||||
/// </summary>
|
||||
public Point? ScreenPosition { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The location in graph space to draw the <see cref="Text"/>. This
|
||||
/// annotation will only show if the point is in the current viewable
|
||||
/// area of the graph presented in the <see cref="GraphView"/>
|
||||
/// </summary>
|
||||
public PointF GraphPosition { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Text to display on the graph
|
||||
/// </summary>
|
||||
public string Text { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// True to add text before plotting series. Defaults to false
|
||||
/// </summary>
|
||||
public bool BeforeSeries { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Draws the annotation
|
||||
/// </summary>
|
||||
/// <param name="graph"></param>
|
||||
public void Render (GraphView graph)
|
||||
{
|
||||
if (ScreenPosition.HasValue) {
|
||||
DrawText (graph, ScreenPosition.Value.X, ScreenPosition.Value.Y);
|
||||
return;
|
||||
}
|
||||
|
||||
var screenPos = graph.GraphSpaceToScreen (GraphPosition);
|
||||
DrawText (graph, screenPos.X, screenPos.Y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws the <see cref="Text"/> at the given coordinates with truncation to avoid
|
||||
/// spilling over <see name="View.Bounds"/> of the <paramref name="graph"/>
|
||||
/// </summary>
|
||||
/// <param name="graph"></param>
|
||||
/// <param name="x">Screen x position to start drawing string</param>
|
||||
/// <param name="y">Screen y position to start drawing string</param>
|
||||
protected void DrawText (GraphView graph, int x, int y)
|
||||
{
|
||||
// the draw point is out of control bounds
|
||||
if (!graph.Bounds.Contains (new Point (x, y))) {
|
||||
return;
|
||||
}
|
||||
|
||||
// There is no text to draw
|
||||
if (string.IsNullOrWhiteSpace (Text)) {
|
||||
return;
|
||||
}
|
||||
|
||||
graph.Move (x, y);
|
||||
|
||||
int availableWidth = graph.Bounds.Width - x;
|
||||
|
||||
if (availableWidth <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Text.Length < availableWidth) {
|
||||
View.Driver.AddStr (Text);
|
||||
} else {
|
||||
View.Driver.AddStr (Text.Substring (0, availableWidth));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A box containing symbol definitions e.g. meanings for colors in a graph.
|
||||
/// The 'Key' to the graph
|
||||
/// </summary>
|
||||
public class LegendAnnotation : IAnnotation {
|
||||
|
||||
/// <summary>
|
||||
/// True to draw a solid border around the legend.
|
||||
/// Defaults to true. This border will be within the
|
||||
/// <see cref="Bounds"/> and so reduces the width/height
|
||||
/// available for text by 2
|
||||
/// </summary>
|
||||
public bool Border { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Defines the screen area available for the legend to render in
|
||||
/// </summary>
|
||||
public Rect Bounds { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns false i.e. Lengends render after series
|
||||
/// </summary>
|
||||
public bool BeforeSeries => false;
|
||||
|
||||
/// <summary>
|
||||
/// Ordered collection of entries that are rendered in the legend.
|
||||
/// </summary>
|
||||
List<Tuple<GraphCellToRender, string>> entries = new List<Tuple<GraphCellToRender, string>> ();
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new empty legend at the given screen coordinates
|
||||
/// </summary>
|
||||
/// <param name="legendBounds">Defines the area available for the legend to render in
|
||||
/// (within the graph). This is in screen units (i.e. not graph space)</param>
|
||||
public LegendAnnotation (Rect legendBounds)
|
||||
{
|
||||
Bounds = legendBounds;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws the Legend and all entries into the area within <see cref="Bounds"/>
|
||||
/// </summary>
|
||||
/// <param name="graph"></param>
|
||||
public void Render (GraphView graph)
|
||||
{
|
||||
if (Border) {
|
||||
graph.DrawFrame (Bounds, 0, true);
|
||||
}
|
||||
|
||||
// start the legend at
|
||||
int y = Bounds.Top + (Border ? 1 : 0);
|
||||
int x = Bounds.Left + (Border ? 1 : 0);
|
||||
|
||||
// how much horizontal space is available for writing legend entries?
|
||||
int availableWidth = Bounds.Width - (Border ? 2 : 0);
|
||||
int availableHeight = Bounds.Height - (Border ? 2 : 0);
|
||||
|
||||
int linesDrawn = 0;
|
||||
|
||||
foreach (var entry in entries) {
|
||||
|
||||
if (entry.Item1.Color.HasValue) {
|
||||
Application.Driver.SetAttribute (entry.Item1.Color.Value);
|
||||
} else {
|
||||
graph.SetDriverColorToGraphColor ();
|
||||
}
|
||||
|
||||
// add the symbol
|
||||
graph.AddRune (x, y + linesDrawn, entry.Item1.Rune);
|
||||
|
||||
// switch to normal coloring (for the text)
|
||||
graph.SetDriverColorToGraphColor ();
|
||||
|
||||
// add the text
|
||||
graph.Move (x + 1, y + linesDrawn);
|
||||
|
||||
string str = TruncateOrPad (entry.Item2, availableWidth - 1);
|
||||
Application.Driver.AddStr (str);
|
||||
|
||||
linesDrawn++;
|
||||
|
||||
// Legend has run out of space
|
||||
if (linesDrawn >= availableHeight) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string TruncateOrPad (string text, int width)
|
||||
{
|
||||
if (string.IsNullOrEmpty (text))
|
||||
return text;
|
||||
|
||||
// if value is not wide enough
|
||||
if (text.Sum (c => Rune.ColumnWidth (c)) < width) {
|
||||
|
||||
// pad it out with spaces to the given alignment
|
||||
int toPad = width - (text.Sum (c => Rune.ColumnWidth (c)));
|
||||
|
||||
return text + new string (' ', toPad);
|
||||
}
|
||||
|
||||
// value is too wide
|
||||
return new string (text.TakeWhile (c => (width -= Rune.ColumnWidth (c)) >= 0).ToArray ());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds an entry into the legend. Duplicate entries are permissable
|
||||
/// </summary>
|
||||
/// <param name="graphCellToRender">The symbol appearing on the graph that should appear in the legend</param>
|
||||
/// <param name="text">Text to render on this line of the legend. Will be truncated
|
||||
/// if outside of Legend <see cref="Bounds"/></param>
|
||||
public void AddEntry (GraphCellToRender graphCellToRender, string text)
|
||||
{
|
||||
entries.Add (Tuple.Create (graphCellToRender, text));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sequence of lines to connect points e.g. of a <see cref="ScatterSeries"/>
|
||||
/// </summary>
|
||||
public class PathAnnotation : IAnnotation {
|
||||
|
||||
/// <summary>
|
||||
/// Points that should be connected. Lines will be drawn between points in the order
|
||||
/// they appear in the list
|
||||
/// </summary>
|
||||
public List<PointF> Points { get; set; } = new List<PointF> ();
|
||||
|
||||
/// <summary>
|
||||
/// Color for the line that connects points
|
||||
/// </summary>
|
||||
public Attribute? LineColor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The symbol that gets drawn along the line, defaults to '.'
|
||||
/// </summary>
|
||||
public Rune LineRune { get; set; } = new Rune ('.');
|
||||
|
||||
/// <summary>
|
||||
/// True to add line before plotting series. Defaults to false
|
||||
/// </summary>
|
||||
public bool BeforeSeries { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Draws lines connecting each of the <see cref="Points"/>
|
||||
/// </summary>
|
||||
/// <param name="graph"></param>
|
||||
public void Render (GraphView graph)
|
||||
{
|
||||
View.Driver.SetAttribute (LineColor ?? graph.ColorScheme.Normal);
|
||||
|
||||
foreach (var line in PointsToLines ()) {
|
||||
|
||||
var start = graph.GraphSpaceToScreen (line.Start);
|
||||
var end = graph.GraphSpaceToScreen (line.End);
|
||||
graph.DrawLine (start, end, LineRune);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates lines joining <see cref="Points"/>
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private IEnumerable<LineF> PointsToLines ()
|
||||
{
|
||||
for (int i = 0; i < Points.Count - 1; i++) {
|
||||
yield return new LineF (Points [i], Points [i + 1]);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Describes two points in graph space and a line between them
|
||||
/// </summary>
|
||||
public class LineF {
|
||||
/// <summary>
|
||||
/// The start of the line
|
||||
/// </summary>
|
||||
public PointF Start { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The end point of the line
|
||||
/// </summary>
|
||||
public PointF End { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new line between the points
|
||||
/// </summary>
|
||||
public LineF (PointF start, PointF end)
|
||||
{
|
||||
this.Start = start;
|
||||
this.End = end;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
565
Terminal.Gui/Core/Graphs/Axis.cs
Normal file
565
Terminal.Gui/Core/Graphs/Axis.cs
Normal file
@@ -0,0 +1,565 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Terminal.Gui.Graphs {
|
||||
|
||||
/// <summary>
|
||||
/// Renders a continuous line with grid line ticks and labels
|
||||
/// </summary>
|
||||
public abstract class Axis {
|
||||
/// <summary>
|
||||
/// Default value for <see cref="ShowLabelsEvery"/>
|
||||
/// </summary>
|
||||
const uint DefaultShowLabelsEvery = 5;
|
||||
|
||||
/// <summary>
|
||||
/// Direction of the axis
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
public Orientation Orientation { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of units of graph space between ticks on axis. 0 for no ticks
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
public float Increment { get; set; } = 1;
|
||||
|
||||
/// <summary>
|
||||
/// The number of <see cref="Increment"/> before an label is added.
|
||||
/// 0 = never show labels
|
||||
/// </summary>
|
||||
public uint ShowLabelsEvery { get; set; } = DefaultShowLabelsEvery;
|
||||
|
||||
/// <summary>
|
||||
/// True to render axis. Defaults to true
|
||||
/// </summary>
|
||||
public bool Visible { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Allows you to control what label text is rendered for a given <see cref="Increment"/>
|
||||
/// when <see cref="ShowLabelsEvery"/> is above 0
|
||||
/// </summary>
|
||||
public LabelGetterDelegate LabelGetter;
|
||||
|
||||
/// <summary>
|
||||
/// Displayed below/to left of labels (see <see cref="Orientation"/>).
|
||||
/// If text is not visible, check <see cref="GraphView.MarginBottom"/> / <see cref="GraphView.MarginLeft"/>
|
||||
/// </summary>
|
||||
public string Text;
|
||||
|
||||
/// <summary>
|
||||
/// The minimum axis point to show. Defaults to null (no minimum)
|
||||
/// </summary>
|
||||
public float? Minimum { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Populates base properties and sets the read only <see cref="Orientation"/>
|
||||
/// </summary>
|
||||
/// <param name="orientation"></param>
|
||||
protected Axis (Orientation orientation)
|
||||
{
|
||||
Orientation = orientation;
|
||||
LabelGetter = DefaultLabelGetter;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws the solid line of the axis
|
||||
/// </summary>
|
||||
/// <param name="graph"></param>
|
||||
public abstract void DrawAxisLine (GraphView graph);
|
||||
|
||||
/// <summary>
|
||||
/// Draws a single cell of the solid line of the axis
|
||||
/// </summary>
|
||||
/// <param name="graph"></param>
|
||||
/// <param name="x"></param>
|
||||
/// <param name="y"></param>
|
||||
protected abstract void DrawAxisLine (GraphView graph, int x, int y);
|
||||
|
||||
/// <summary>
|
||||
/// Draws labels and axis <see cref="Increment"/> ticks
|
||||
/// </summary>
|
||||
/// <param name="graph"></param>
|
||||
|
||||
public abstract void DrawAxisLabels (GraphView graph);
|
||||
|
||||
/// <summary>
|
||||
/// Draws a custom label <paramref name="text"/> at <paramref name="screenPosition"/> units
|
||||
/// along the axis (X or Y depending on <see cref="Orientation"/>)
|
||||
/// </summary>
|
||||
/// <param name="graph"></param>
|
||||
/// <param name="screenPosition"></param>
|
||||
/// <param name="text"></param>
|
||||
public abstract void DrawAxisLabel (GraphView graph, int screenPosition, string text);
|
||||
|
||||
/// <summary>
|
||||
/// Resets all configurable properties of the axis to default values
|
||||
/// </summary>
|
||||
public virtual void Reset ()
|
||||
{
|
||||
Increment = 1;
|
||||
ShowLabelsEvery = DefaultShowLabelsEvery;
|
||||
Visible = true;
|
||||
Text = "";
|
||||
LabelGetter = DefaultLabelGetter;
|
||||
Minimum = null;
|
||||
}
|
||||
|
||||
private string DefaultLabelGetter (AxisIncrementToRender toRender)
|
||||
{
|
||||
return toRender.Value.ToString ("N0");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The horizontal (x axis) of a <see cref="GraphView"/>
|
||||
/// </summary>
|
||||
public class HorizontalAxis : Axis {
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of axis with an <see cref="Orientation"/> of <see cref="Orientation.Horizontal"/>
|
||||
/// </summary>
|
||||
public HorizontalAxis () : base (Orientation.Horizontal)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Draws the horizontal axis line
|
||||
/// </summary>
|
||||
/// <param name="graph"></param>
|
||||
public override void DrawAxisLine (GraphView graph)
|
||||
{
|
||||
if (!Visible) {
|
||||
return;
|
||||
}
|
||||
var bounds = graph.Bounds;
|
||||
|
||||
graph.Move (0, 0);
|
||||
|
||||
var y = GetAxisYPosition (graph);
|
||||
|
||||
// start the x axis at left of screen (either 0 or margin)
|
||||
var xStart = (int)graph.MarginLeft;
|
||||
|
||||
// but if the x axis has a minmum (minimum is in graph space units)
|
||||
if (Minimum.HasValue) {
|
||||
|
||||
// start at the screen location of the minimum
|
||||
var minimumScreenX = graph.GraphSpaceToScreen (new PointF (Minimum.Value, y)).X;
|
||||
|
||||
// unless that is off the screen to the left
|
||||
xStart = Math.Max (xStart, minimumScreenX);
|
||||
}
|
||||
|
||||
for (int i = xStart; i < bounds.Width; i++) {
|
||||
|
||||
DrawAxisLine (graph, i, y);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Draws a horizontal axis line at the given <paramref name="x"/>, <paramref name="y"/>
|
||||
/// screen coordinates
|
||||
/// </summary>
|
||||
/// <param name="graph"></param>
|
||||
/// <param name="x"></param>
|
||||
/// <param name="y"></param>
|
||||
protected override void DrawAxisLine (GraphView graph, int x, int y)
|
||||
{
|
||||
graph.Move (x, y);
|
||||
Application.Driver.AddRune (Application.Driver.HLine);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws the horizontal x axis labels and <see cref="Axis.Increment"/> ticks
|
||||
/// </summary>
|
||||
public override void DrawAxisLabels (GraphView graph)
|
||||
{
|
||||
if (!Visible || Increment == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var bounds = graph.Bounds;
|
||||
|
||||
var labels = GetLabels (graph, bounds);
|
||||
|
||||
foreach (var label in labels) {
|
||||
DrawAxisLabel (graph, label.ScreenLocation, label.Text);
|
||||
}
|
||||
|
||||
// if there is a title
|
||||
if (!string.IsNullOrWhiteSpace (Text)) {
|
||||
|
||||
string toRender = Text;
|
||||
|
||||
// if label is too long
|
||||
if (toRender.Length > graph.Bounds.Width) {
|
||||
toRender = toRender.Substring (0, graph.Bounds.Width);
|
||||
}
|
||||
|
||||
graph.Move (graph.Bounds.Width / 2 - (toRender.Length / 2), graph.Bounds.Height - 1);
|
||||
Application.Driver.AddStr (toRender);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws the given <paramref name="text"/> on the axis at x <paramref name="screenPosition"/>.
|
||||
/// For the screen y position use <see cref="GetAxisYPosition(GraphView)"/>
|
||||
/// </summary>
|
||||
/// <param name="graph">Graph being drawn onto</param>
|
||||
/// <param name="screenPosition">Number of screen columns along the axis to take before rendering</param>
|
||||
/// <param name="text">Text to render under the axis tick</param>
|
||||
public override void DrawAxisLabel (GraphView graph, int screenPosition, string text)
|
||||
{
|
||||
var driver = Application.Driver;
|
||||
var y = GetAxisYPosition (graph);
|
||||
|
||||
graph.Move (screenPosition, y);
|
||||
|
||||
// draw the tick on the axis
|
||||
driver.AddRune (driver.TopTee);
|
||||
|
||||
// and the label text
|
||||
if (!string.IsNullOrWhiteSpace (text)) {
|
||||
|
||||
// center the label but don't draw it outside bounds of the graph
|
||||
int drawAtX = Math.Max (0, screenPosition - (text.Length / 2));
|
||||
string toRender = text;
|
||||
|
||||
// this is how much space is left
|
||||
int xSpaceAvailable = graph.Bounds.Width - drawAtX;
|
||||
|
||||
// There is no space for the label at all!
|
||||
if (xSpaceAvailable <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// if we are close to right side of graph, don't overspill
|
||||
if (toRender.Length > xSpaceAvailable) {
|
||||
toRender = toRender.Substring (0, xSpaceAvailable);
|
||||
}
|
||||
|
||||
graph.Move (drawAtX, Math.Min (y + 1, graph.Bounds.Height - 1));
|
||||
driver.AddStr (toRender);
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<AxisIncrementToRender> GetLabels (GraphView graph, Rect bounds)
|
||||
{
|
||||
// if no labels
|
||||
if (Increment == 0) {
|
||||
yield break;
|
||||
}
|
||||
|
||||
int labels = 0;
|
||||
int y = GetAxisYPosition (graph);
|
||||
|
||||
var start = graph.ScreenToGraphSpace (0, y);
|
||||
var end = graph.ScreenToGraphSpace (bounds.Width, y);
|
||||
|
||||
// don't draw labels below the minimum
|
||||
if (Minimum.HasValue) {
|
||||
start.X = Math.Max (start.X, Minimum.Value);
|
||||
}
|
||||
|
||||
var current = start;
|
||||
|
||||
while (current.X < end.X) {
|
||||
|
||||
int screenX = graph.GraphSpaceToScreen (new PointF (current.X, current.Y)).X;
|
||||
|
||||
// Ensure the axis point does not draw into the margin
|
||||
if (screenX >= graph.MarginLeft) {
|
||||
// The increment we will render (normally a top T unicode symbol)
|
||||
var toRender = new AxisIncrementToRender (Orientation, screenX, current.X);
|
||||
|
||||
// Not every increment has to have a label
|
||||
if (ShowLabelsEvery != 0) {
|
||||
|
||||
// if this increment does also needs a label
|
||||
if (labels++ % ShowLabelsEvery == 0) {
|
||||
toRender.Text = LabelGetter (toRender);
|
||||
};
|
||||
}
|
||||
|
||||
// Label or no label definetly render it
|
||||
yield return toRender;
|
||||
}
|
||||
|
||||
current.X += Increment;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Returns the Y screen position of the origin (typically 0,0) of graph space.
|
||||
/// Return value is bounded by the screen i.e. the axis is always rendered even
|
||||
/// if the origin is offscreen.
|
||||
/// </summary>
|
||||
/// <param name="graph"></param>
|
||||
public int GetAxisYPosition (GraphView graph)
|
||||
{
|
||||
// find the origin of the graph in screen space (this allows for 'crosshair' style
|
||||
// graphs where positive and negative numbers visible
|
||||
var origin = graph.GraphSpaceToScreen (new PointF (0, 0));
|
||||
|
||||
// float the X axis so that it accurately represents the origin of the graph
|
||||
// but anchor it to top/bottom if the origin is offscreen
|
||||
return Math.Min (Math.Max (0, origin.Y), graph.Bounds.Height - ((int)graph.MarginBottom + 1));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The vertical (i.e. Y axis) of a <see cref="GraphView"/>
|
||||
/// </summary>
|
||||
public class VerticalAxis : Axis {
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Orientation.Vertical"/> axis
|
||||
/// </summary>
|
||||
public VerticalAxis () : base (Orientation.Vertical)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws the vertical axis line
|
||||
/// </summary>
|
||||
/// <param name="graph"></param>
|
||||
public override void DrawAxisLine (GraphView graph)
|
||||
{
|
||||
if (!Visible) {
|
||||
return;
|
||||
}
|
||||
Rect bounds = graph.Bounds;
|
||||
|
||||
var x = GetAxisXPosition (graph);
|
||||
|
||||
var yEnd = GetAxisYEnd (graph);
|
||||
|
||||
// don't draw down further than the control bounds
|
||||
yEnd = Math.Min (yEnd, bounds.Height - (int)graph.MarginBottom);
|
||||
|
||||
// Draw solid line
|
||||
for (int i = 0; i < yEnd; i++) {
|
||||
|
||||
DrawAxisLine (graph, x, i);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws a vertical axis line at the given <paramref name="x"/>, <paramref name="y"/>
|
||||
/// screen coordinates
|
||||
/// </summary>
|
||||
/// <param name="graph"></param>
|
||||
/// <param name="x"></param>
|
||||
/// <param name="y"></param>
|
||||
protected override void DrawAxisLine (GraphView graph, int x, int y)
|
||||
{
|
||||
graph.Move (x, y);
|
||||
Application.Driver.AddRune (Application.Driver.VLine);
|
||||
}
|
||||
|
||||
private int GetAxisYEnd (GraphView graph)
|
||||
{
|
||||
// draw down the screen (0 is top of screen)
|
||||
// end at the bottom of the screen
|
||||
|
||||
//unless there is a minimum
|
||||
if (Minimum.HasValue) {
|
||||
return graph.GraphSpaceToScreen (new PointF (0, Minimum.Value)).Y;
|
||||
}
|
||||
|
||||
return graph.Bounds.Height;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Draws axis <see cref="Axis.Increment"/> markers and labels
|
||||
/// </summary>
|
||||
/// <param name="graph"></param>
|
||||
public override void DrawAxisLabels (GraphView graph)
|
||||
{
|
||||
if (!Visible || Increment == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var bounds = graph.Bounds;
|
||||
var labels = GetLabels (graph, bounds);
|
||||
|
||||
foreach (var label in labels) {
|
||||
|
||||
DrawAxisLabel (graph, label.ScreenLocation, label.Text);
|
||||
}
|
||||
|
||||
// if there is a title
|
||||
if (!string.IsNullOrWhiteSpace (Text)) {
|
||||
|
||||
string toRender = Text;
|
||||
|
||||
// if label is too long
|
||||
if (toRender.Length > graph.Bounds.Height) {
|
||||
toRender = toRender.Substring (0, graph.Bounds.Height);
|
||||
}
|
||||
|
||||
// Draw it 1 letter at a time vertically down row 0 of the control
|
||||
int startDrawingAtY = graph.Bounds.Height / 2 - (toRender.Length / 2);
|
||||
|
||||
for (int i = 0; i < toRender.Length; i++) {
|
||||
|
||||
graph.Move (0, startDrawingAtY + i);
|
||||
Application.Driver.AddRune (toRender [i]);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<AxisIncrementToRender> GetLabels (GraphView graph, Rect bounds)
|
||||
{
|
||||
// if no labels
|
||||
if (Increment == 0) {
|
||||
yield break;
|
||||
}
|
||||
|
||||
int labels = 0;
|
||||
int x = GetAxisXPosition (graph);
|
||||
|
||||
// remember screen space is top down so the lowest graph
|
||||
// space value is at the bottom of the screen
|
||||
var start = graph.ScreenToGraphSpace (x, bounds.Height - 1);
|
||||
var end = graph.ScreenToGraphSpace (x, 0);
|
||||
|
||||
// don't draw labels below the minimum
|
||||
if (Minimum.HasValue) {
|
||||
start.Y = Math.Max (start.Y, Minimum.Value);
|
||||
}
|
||||
|
||||
var current = start;
|
||||
var dontDrawBelowScreenY = bounds.Height - graph.MarginBottom;
|
||||
|
||||
while (current.Y < end.Y) {
|
||||
|
||||
int screenY = graph.GraphSpaceToScreen (new PointF (current.X, current.Y)).Y;
|
||||
|
||||
// if the axis label is above the bottom margin (screen y starts at 0 at the top)
|
||||
if (screenY < dontDrawBelowScreenY) {
|
||||
// Create the axis symbol
|
||||
var toRender = new AxisIncrementToRender (Orientation, screenY, current.Y);
|
||||
|
||||
// and the label (if we are due one)
|
||||
if (ShowLabelsEvery != 0) {
|
||||
|
||||
// if this increment also needs a label
|
||||
if (labels++ % ShowLabelsEvery == 0) {
|
||||
toRender.Text = LabelGetter (toRender);
|
||||
};
|
||||
}
|
||||
|
||||
// draw the axis symbol (and label if it has one)
|
||||
yield return toRender;
|
||||
}
|
||||
|
||||
current.Y += Increment;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws the given <paramref name="text"/> on the axis at y <paramref name="screenPosition"/>.
|
||||
/// For the screen x position use <see cref="GetAxisXPosition(GraphView)"/>
|
||||
/// </summary>
|
||||
/// <param name="graph">Graph being drawn onto</param>
|
||||
/// <param name="screenPosition">Number of rows from the top of the screen (i.e. down the axis) before rendering</param>
|
||||
/// <param name="text">Text to render to the left of the axis tick. Ensure to
|
||||
/// set <see cref="GraphView.MarginLeft"/> or <see cref="GraphView.ScrollOffset"/> sufficient that it is visible</param>
|
||||
public override void DrawAxisLabel (GraphView graph, int screenPosition, string text)
|
||||
{
|
||||
var x = GetAxisXPosition (graph);
|
||||
var labelThickness = text.Length;
|
||||
|
||||
graph.Move (x, screenPosition);
|
||||
|
||||
// draw the tick on the axis
|
||||
Application.Driver.AddRune (Application.Driver.RightTee);
|
||||
|
||||
// and the label text
|
||||
if (!string.IsNullOrWhiteSpace (text)) {
|
||||
graph.Move (Math.Max (0, x - labelThickness), screenPosition);
|
||||
Application.Driver.AddStr (text);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the X screen position of the origin (typically 0,0) of graph space.
|
||||
/// Return value is bounded by the screen i.e. the axis is always rendered even
|
||||
/// if the origin is offscreen.
|
||||
/// </summary>
|
||||
/// <param name="graph"></param>
|
||||
public int GetAxisXPosition (GraphView graph)
|
||||
{
|
||||
// find the origin of the graph in screen space (this allows for 'crosshair' style
|
||||
// graphs where positive and negative numbers visible
|
||||
var origin = graph.GraphSpaceToScreen (new PointF (0, 0));
|
||||
|
||||
// float the Y axis so that it accurately represents the origin of the graph
|
||||
// but anchor it to left/right if the origin is offscreen
|
||||
return Math.Min (Math.Max ((int)graph.MarginLeft, origin.X), graph.Bounds.Width - 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A location on an axis of a <see cref="GraphView"/> that may
|
||||
/// or may not have a label associated with it
|
||||
/// </summary>
|
||||
public class AxisIncrementToRender {
|
||||
|
||||
/// <summary>
|
||||
/// Direction of the parent axis
|
||||
/// </summary>
|
||||
public Orientation Orientation { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The screen location (X or Y depending on <see cref="Orientation"/>) that the
|
||||
/// increment will be rendered at
|
||||
/// </summary>
|
||||
public int ScreenLocation { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The value at this position on the axis in graph space
|
||||
/// </summary>
|
||||
public float Value { get; }
|
||||
|
||||
private string _text = "";
|
||||
|
||||
/// <summary>
|
||||
/// The text (if any) that should be displayed at this axis increment
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
internal string Text {
|
||||
get => _text;
|
||||
set { _text = value ?? ""; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Describe a new section of an axis that requires an axis increment
|
||||
/// symbol and/or label
|
||||
/// </summary>
|
||||
/// <param name="orientation"></param>
|
||||
/// <param name="screen"></param>
|
||||
/// <param name="value"></param>
|
||||
public AxisIncrementToRender (Orientation orientation, int screen, float value)
|
||||
{
|
||||
Orientation = orientation;
|
||||
ScreenLocation = screen;
|
||||
Value = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delegate for custom formatting of axis labels. Determines what should be displayed at a given label
|
||||
/// </summary>
|
||||
/// <param name="toRender">The axis increment to which the label is attached</param>
|
||||
/// <returns></returns>
|
||||
public delegate string LabelGetterDelegate (AxisIncrementToRender toRender);
|
||||
|
||||
}
|
||||
45
Terminal.Gui/Core/Graphs/GraphCellToRender.cs
Normal file
45
Terminal.Gui/Core/Graphs/GraphCellToRender.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
using System;
|
||||
|
||||
namespace Terminal.Gui.Graphs {
|
||||
/// <summary>
|
||||
/// Describes how to render a single row/column of a <see cref="GraphView"/> based
|
||||
/// on the value(s) in <see cref="ISeries"/> at that location
|
||||
/// </summary>
|
||||
public class GraphCellToRender {
|
||||
|
||||
/// <summary>
|
||||
/// The character to render in the console
|
||||
/// </summary>
|
||||
public Rune Rune { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional color to render the <see cref="Rune"/> with
|
||||
/// </summary>
|
||||
public Attribute? Color { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates instance and sets <see cref="Rune"/> with default graph coloring
|
||||
/// </summary>
|
||||
/// <param name="rune"></param>
|
||||
public GraphCellToRender (Rune rune)
|
||||
{
|
||||
Rune = rune;
|
||||
}
|
||||
/// <summary>
|
||||
/// Creates instance and sets <see cref="Rune"/> with custom graph coloring
|
||||
/// </summary>
|
||||
/// <param name="rune"></param>
|
||||
/// <param name="color"></param>
|
||||
public GraphCellToRender (Rune rune, Attribute color) : this (rune)
|
||||
{
|
||||
Color = color;
|
||||
}
|
||||
/// <summary>
|
||||
/// Creates instance and sets <see cref="Rune"/> and <see cref="Color"/> (or default if null)
|
||||
/// </summary>
|
||||
public GraphCellToRender (Rune rune, Attribute? color) : this (rune)
|
||||
{
|
||||
Color = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
17
Terminal.Gui/Core/Graphs/Orientation.cs
Normal file
17
Terminal.Gui/Core/Graphs/Orientation.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
namespace Terminal.Gui.Graphs {
|
||||
/// <summary>
|
||||
/// Direction of an element (horizontal or vertical)
|
||||
/// </summary>
|
||||
public enum Orientation {
|
||||
|
||||
/// <summary>
|
||||
/// Left to right
|
||||
/// </summary>
|
||||
Horizontal,
|
||||
|
||||
/// <summary>
|
||||
/// Bottom to top
|
||||
/// </summary>
|
||||
Vertical
|
||||
}
|
||||
}
|
||||
323
Terminal.Gui/Core/Graphs/Series.cs
Normal file
323
Terminal.Gui/Core/Graphs/Series.cs
Normal file
@@ -0,0 +1,323 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
|
||||
namespace Terminal.Gui.Graphs {
|
||||
/// <summary>
|
||||
/// Describes a series of data that can be rendered into a <see cref="GraphView"/>>
|
||||
/// </summary>
|
||||
public interface ISeries {
|
||||
|
||||
/// <summary>
|
||||
/// Draws the <paramref name="graphBounds"/> section of a series into the
|
||||
/// <paramref name="graph"/> view <paramref name="drawBounds"/>
|
||||
/// </summary>
|
||||
/// <param name="graph">Graph series is to be drawn onto</param>
|
||||
/// <param name="drawBounds">Visible area of the graph in Console Screen units (excluding margins)</param>
|
||||
/// <param name="graphBounds">Visible area of the graph in Graph space units</param>
|
||||
void DrawSeries (GraphView graph, Rect drawBounds, RectangleF graphBounds);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Series composed of any number of discrete data points
|
||||
/// </summary>
|
||||
public class ScatterSeries : ISeries {
|
||||
/// <summary>
|
||||
/// Collection of each discrete point in the series
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public List<PointF> Points { get; set; } = new List<PointF> ();
|
||||
|
||||
/// <summary>
|
||||
/// The color and character that will be rendered in the console
|
||||
/// when there are point(s) in the corresponding graph space.
|
||||
/// Defaults to uncolored 'x'
|
||||
/// </summary>
|
||||
public GraphCellToRender Fill { get; set; } = new GraphCellToRender ('x');
|
||||
|
||||
/// <summary>
|
||||
/// Draws all points directly onto the graph
|
||||
/// </summary>
|
||||
public void DrawSeries (GraphView graph, Rect drawBounds, RectangleF graphBounds)
|
||||
{
|
||||
if (Fill.Color.HasValue) {
|
||||
Application.Driver.SetAttribute (Fill.Color.Value);
|
||||
}
|
||||
|
||||
foreach (var p in Points.Where (p => graphBounds.Contains (p))) {
|
||||
|
||||
var screenPoint = graph.GraphSpaceToScreen (p);
|
||||
graph.AddRune (screenPoint.X, screenPoint.Y, Fill.Rune);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Collection of <see cref="BarSeries"/> in which bars are clustered by category
|
||||
/// </summary>
|
||||
public class MultiBarSeries : ISeries {
|
||||
|
||||
BarSeries [] subSeries;
|
||||
|
||||
/// <summary>
|
||||
/// Sub collections. Each series contains the bars for a different category. Thus
|
||||
/// SubSeries[0].Bars[0] is the first bar on the axis and SubSeries[1].Bars[0] is the
|
||||
/// second etc
|
||||
/// </summary>
|
||||
public IReadOnlyCollection<BarSeries> SubSeries { get => new ReadOnlyCollection<BarSeries> (subSeries); }
|
||||
|
||||
/// <summary>
|
||||
/// The number of units of graph space between bars. Should be
|
||||
/// less than <see cref="BarSeries.BarEvery"/>
|
||||
/// </summary>
|
||||
public float Spacing { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new series of clustered bars.
|
||||
/// </summary>
|
||||
/// <param name="numberOfBarsPerCategory">Each category has this many bars</param>
|
||||
/// <param name="barsEvery">How far appart to put each category (in graph space)</param>
|
||||
/// <param name="spacing">How much spacing between bars in a category (should be less than <paramref name="barsEvery"/>/<paramref name="numberOfBarsPerCategory"/>)</param>
|
||||
/// <param name="colors">Array of colors that define bar color in each category. Length must match <paramref name="numberOfBarsPerCategory"/></param>
|
||||
public MultiBarSeries (int numberOfBarsPerCategory, float barsEvery, float spacing, Attribute [] colors = null)
|
||||
{
|
||||
subSeries = new BarSeries [numberOfBarsPerCategory];
|
||||
|
||||
if (colors != null && colors.Length != numberOfBarsPerCategory) {
|
||||
throw new ArgumentException ("Number of colors must match the number of bars", nameof (numberOfBarsPerCategory));
|
||||
}
|
||||
|
||||
|
||||
for (int i = 0; i < numberOfBarsPerCategory; i++) {
|
||||
subSeries [i] = new BarSeries ();
|
||||
subSeries [i].BarEvery = barsEvery;
|
||||
subSeries [i].Offset = i * spacing;
|
||||
|
||||
// Only draw labels for the first bar in each category
|
||||
subSeries [i].DrawLabels = i == 0;
|
||||
|
||||
if (colors != null) {
|
||||
subSeries [i].OverrideBarColor = colors [i];
|
||||
}
|
||||
}
|
||||
Spacing = spacing;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new cluster of bars
|
||||
/// </summary>
|
||||
/// <param name="label"></param>
|
||||
/// <param name="fill"></param>
|
||||
/// <param name="values">Values for each bar in category, must match the number of bars per category</param>
|
||||
public void AddBars (string label, Rune fill, params float [] values)
|
||||
{
|
||||
if (values.Length != subSeries.Length) {
|
||||
throw new ArgumentException ("Number of values must match the number of bars per category", nameof (values));
|
||||
}
|
||||
|
||||
for (int i = 0; i < values.Length; i++) {
|
||||
subSeries [i].Bars.Add (new BarSeries.Bar (label,
|
||||
new GraphCellToRender (fill), values [i]));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws all <see cref="SubSeries"/>
|
||||
/// </summary>
|
||||
/// <param name="graph"></param>
|
||||
/// <param name="drawBounds"></param>
|
||||
/// <param name="graphBounds"></param>
|
||||
public void DrawSeries (GraphView graph, Rect drawBounds, RectangleF graphBounds)
|
||||
{
|
||||
foreach (var bar in subSeries) {
|
||||
bar.DrawSeries (graph, drawBounds, graphBounds);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Series of bars positioned at regular intervals
|
||||
/// </summary>
|
||||
public class BarSeries : ISeries {
|
||||
|
||||
/// <summary>
|
||||
/// Ordered collection of graph bars to position along axis
|
||||
/// </summary>
|
||||
public List<Bar> Bars { get; set; } = new List<Bar> ();
|
||||
|
||||
/// <summary>
|
||||
/// Determines the spacing of bars along the axis. Defaults to 1 i.e.
|
||||
/// every 1 unit of graph space a bar is rendered. Note that you should
|
||||
/// also consider <see cref="GraphView.CellSize"/> when changing this.
|
||||
/// </summary>
|
||||
public float BarEvery { get; set; } = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Direction bars protrude from the corresponding axis.
|
||||
/// Defaults to vertical
|
||||
/// </summary>
|
||||
public Orientation Orientation { get; set; } = Orientation.Vertical;
|
||||
|
||||
/// <summary>
|
||||
/// The number of units of graph space along the axis before rendering the first bar
|
||||
/// (and subsequent bars - see <see cref="BarEvery"/>). Defaults to 0
|
||||
/// </summary>
|
||||
public float Offset { get; set; } = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the <see cref="Bar.Fill"/> with a fixed color
|
||||
/// </summary>
|
||||
public Attribute? OverrideBarColor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// True to draw <see cref="Bar.Text"/> along the axis under the bar. Defaults
|
||||
/// to true.
|
||||
/// </summary>
|
||||
public bool DrawLabels { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Applies any color overriding
|
||||
/// </summary>
|
||||
/// <param name="graphCellToRender"></param>
|
||||
/// <returns></returns>
|
||||
protected virtual GraphCellToRender AdjustColor (GraphCellToRender graphCellToRender)
|
||||
{
|
||||
if (OverrideBarColor.HasValue) {
|
||||
graphCellToRender.Color = OverrideBarColor;
|
||||
}
|
||||
|
||||
return graphCellToRender;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws bars that are currently in the <paramref name="drawBounds"/>
|
||||
/// </summary>
|
||||
/// <param name="graph"></param>
|
||||
/// <param name="drawBounds">Screen area of the graph excluding margins</param>
|
||||
/// <param name="graphBounds">Graph space area that should be drawn into <paramref name="drawBounds"/></param>
|
||||
public virtual void DrawSeries (GraphView graph, Rect drawBounds, RectangleF graphBounds)
|
||||
{
|
||||
for (int i = 0; i < Bars.Count; i++) {
|
||||
|
||||
float xStart = Orientation == Orientation.Horizontal ? 0 : Offset + ((i + 1) * BarEvery);
|
||||
float yStart = Orientation == Orientation.Horizontal ? Offset + ((i + 1) * BarEvery) : 0;
|
||||
|
||||
float endX = Orientation == Orientation.Horizontal ? Bars [i].Value : xStart;
|
||||
float endY = Orientation == Orientation.Horizontal ? yStart : Bars [i].Value;
|
||||
|
||||
// translate to screen positions
|
||||
var screenStart = graph.GraphSpaceToScreen (new PointF (xStart, yStart));
|
||||
var screenEnd = graph.GraphSpaceToScreen (new PointF (endX, endY));
|
||||
|
||||
// Start the bar from wherever the axis is
|
||||
if (Orientation == Orientation.Horizontal) {
|
||||
|
||||
screenStart.X = graph.AxisY.GetAxisXPosition (graph);
|
||||
|
||||
// dont draw bar off the right of the control
|
||||
screenEnd.X = Math.Min (graph.Bounds.Width - 1, screenEnd.X);
|
||||
|
||||
// if bar is off the screen
|
||||
if (screenStart.Y < 0 || screenStart.Y > drawBounds.Height - graph.MarginBottom) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
|
||||
// Start the axis
|
||||
screenStart.Y = graph.AxisX.GetAxisYPosition (graph);
|
||||
|
||||
// dont draw bar up above top of control
|
||||
screenEnd.Y = Math.Max (0, screenEnd.Y);
|
||||
|
||||
// if bar is off the screen
|
||||
if (screenStart.X < graph.MarginLeft || screenStart.X > graph.MarginLeft + drawBounds.Width - 1) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// draw the bar unless it has no height
|
||||
if (Bars [i].Value != 0) {
|
||||
DrawBarLine (graph, screenStart, screenEnd, Bars [i]);
|
||||
}
|
||||
|
||||
// If we are drawing labels and the bar has one
|
||||
if (DrawLabels && !string.IsNullOrWhiteSpace (Bars [i].Text)) {
|
||||
|
||||
// Add the label to the relevant axis
|
||||
if (Orientation == Orientation.Horizontal) {
|
||||
|
||||
graph.AxisY.DrawAxisLabel (graph, screenStart.Y, Bars [i].Text);
|
||||
} else if (Orientation == Orientation.Vertical) {
|
||||
|
||||
graph.AxisX.DrawAxisLabel (graph, screenStart.X, Bars [i].Text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Override to do custom drawing of the bar e.g. to apply varying color or changing the fill
|
||||
/// symbol mid bar.
|
||||
/// </summary>
|
||||
/// <param name="graph"></param>
|
||||
/// <param name="start">Screen position of the start of the bar</param>
|
||||
/// <param name="end">Screen position of the end of the bar</param>
|
||||
/// <param name="beingDrawn">The Bar that occupies this space and is being drawn</param>
|
||||
protected virtual void DrawBarLine (GraphView graph, Point start, Point end, Bar beingDrawn)
|
||||
{
|
||||
var adjusted = AdjustColor (beingDrawn.Fill);
|
||||
|
||||
if (adjusted.Color.HasValue) {
|
||||
Application.Driver.SetAttribute (adjusted.Color.Value);
|
||||
}
|
||||
|
||||
graph.DrawLine (start, end, adjusted.Rune);
|
||||
|
||||
graph.SetDriverColorToGraphColor ();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A single bar in a <see cref="BarSeries"/>
|
||||
/// </summary>
|
||||
public class Bar {
|
||||
|
||||
/// <summary>
|
||||
/// Optional text that describes the bar. This will be rendered on the corresponding
|
||||
/// <see cref="Axis"/> unless <see cref="DrawLabels"/> is false
|
||||
/// </summary>
|
||||
public string Text { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The color and character that will be rendered in the console
|
||||
/// when the bar extends over it
|
||||
/// </summary>
|
||||
public GraphCellToRender Fill { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The value in graph space X/Y (depending on <see cref="Orientation"/>) to which the bar extends.
|
||||
/// </summary>
|
||||
public float Value { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of a single bar rendered in the given <paramref name="fill"/> that extends
|
||||
/// out <paramref name="value"/> graph space units in the default <see cref="Orientation"/>
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
/// <param name="fill"></param>
|
||||
/// <param name="value"></param>
|
||||
public Bar (string text, GraphCellToRender fill, float value)
|
||||
{
|
||||
Text = text;
|
||||
Fill = fill;
|
||||
Value = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
138
Terminal.Gui/Types/PointF.cs
Normal file
138
Terminal.Gui/Types/PointF.cs
Normal file
@@ -0,0 +1,138 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
// Copied from: https://github.com/dotnet/corefx/tree/master/src/System.Drawing.Primitives/src/System/Drawing
|
||||
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace Terminal.Gui {
|
||||
/// <summary>
|
||||
/// Represents an ordered pair of x and y coordinates that define a point in a two-dimensional plane.
|
||||
/// </summary>
|
||||
public struct PointF : IEquatable<PointF> {
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref='Terminal.Gui.PointF'/> class with member data left uninitialized.
|
||||
/// </summary>
|
||||
public static readonly PointF Empty;
|
||||
private float x; // Do not rename (binary serialization)
|
||||
private float y; // Do not rename (binary serialization)
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref='Terminal.Gui.PointF'/> class with the specified coordinates.
|
||||
/// </summary>
|
||||
public PointF (float x, float y)
|
||||
{
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this <see cref='Terminal.Gui.PointF'/> is empty.
|
||||
/// </summary>
|
||||
[Browsable (false)]
|
||||
public bool IsEmpty => x == 0f && y == 0f;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the x-coordinate of this <see cref='Terminal.Gui.PointF'/>.
|
||||
/// </summary>
|
||||
public float X {
|
||||
get => x;
|
||||
set => x = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the y-coordinate of this <see cref='Terminal.Gui.PointF'/>.
|
||||
/// </summary>
|
||||
public float Y {
|
||||
get => y;
|
||||
set => y = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Translates a <see cref='Terminal.Gui.PointF'/> by a given <see cref='Terminal.Gui.Size'/> .
|
||||
/// </summary>
|
||||
public static PointF operator + (PointF pt, Size sz) => Add (pt, sz);
|
||||
|
||||
/// <summary>
|
||||
/// Translates a <see cref='Terminal.Gui.PointF'/> by the negative of a given <see cref='Terminal.Gui.Size'/> .
|
||||
/// </summary>
|
||||
public static PointF operator - (PointF pt, Size sz) => Subtract (pt, sz);
|
||||
|
||||
/// <summary>
|
||||
/// Translates a <see cref='Terminal.Gui.PointF'/> by a given <see cref='Terminal.Gui.SizeF'/> .
|
||||
/// </summary>
|
||||
public static PointF operator + (PointF pt, SizeF sz) => Add (pt, sz);
|
||||
|
||||
/// <summary>
|
||||
/// Translates a <see cref='Terminal.Gui.PointF'/> by the negative of a given <see cref='Terminal.Gui.SizeF'/> .
|
||||
/// </summary>
|
||||
public static PointF operator - (PointF pt, SizeF sz) => Subtract (pt, sz);
|
||||
|
||||
/// <summary>
|
||||
/// Compares two <see cref='Terminal.Gui.PointF'/> objects. The result specifies whether the values of the
|
||||
/// <see cref='Terminal.Gui.PointF.X'/> and <see cref='Terminal.Gui.PointF.Y'/> properties of the two
|
||||
/// <see cref='Terminal.Gui.PointF'/> objects are equal.
|
||||
/// </summary>
|
||||
public static bool operator == (PointF left, PointF right) => left.X == right.X && left.Y == right.Y;
|
||||
|
||||
/// <summary>
|
||||
/// Compares two <see cref='Terminal.Gui.PointF'/> objects. The result specifies whether the values of the
|
||||
/// <see cref='Terminal.Gui.PointF.X'/> or <see cref='Terminal.Gui.PointF.Y'/> properties of the two
|
||||
/// <see cref='Terminal.Gui.PointF'/> objects are unequal.
|
||||
/// </summary>
|
||||
public static bool operator != (PointF left, PointF right) => !(left == right);
|
||||
|
||||
/// <summary>
|
||||
/// Translates a <see cref='Terminal.Gui.PointF'/> by a given <see cref='Terminal.Gui.Size'/> .
|
||||
/// </summary>
|
||||
public static PointF Add (PointF pt, Size sz) => new PointF (pt.X + sz.Width, pt.Y + sz.Height);
|
||||
|
||||
/// <summary>
|
||||
/// Translates a <see cref='Terminal.Gui.PointF'/> by the negative of a given <see cref='Terminal.Gui.Size'/> .
|
||||
/// </summary>
|
||||
public static PointF Subtract (PointF pt, Size sz) => new PointF (pt.X - sz.Width, pt.Y - sz.Height);
|
||||
|
||||
/// <summary>
|
||||
/// Translates a <see cref='Terminal.Gui.PointF'/> by a given <see cref='Terminal.Gui.SizeF'/> .
|
||||
/// </summary>
|
||||
public static PointF Add (PointF pt, SizeF sz) => new PointF (pt.X + sz.Width, pt.Y + sz.Height);
|
||||
|
||||
/// <summary>
|
||||
/// Translates a <see cref='Terminal.Gui.PointF'/> by the negative of a given <see cref='Terminal.Gui.SizeF'/> .
|
||||
/// </summary>
|
||||
public static PointF Subtract (PointF pt, SizeF sz) => new PointF (pt.X - sz.Width, pt.Y - sz.Height);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Compares two <see cref='Terminal.Gui.PointF'/> objects. The result specifies whether the values of the
|
||||
/// <see cref='Terminal.Gui.PointF.X'/> and <see cref='Terminal.Gui.PointF.Y'/> properties of the two
|
||||
/// <see cref='Terminal.Gui.PointF'/> objects are equal.
|
||||
/// </summary>
|
||||
public override bool Equals (object obj) => obj is PointF && Equals ((PointF)obj);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Compares two <see cref='Terminal.Gui.PointF'/> objects. The result specifies whether the values of the
|
||||
/// <see cref='Terminal.Gui.PointF.X'/> and <see cref='Terminal.Gui.PointF.Y'/> properties of the two
|
||||
/// <see cref='Terminal.Gui.PointF'/> objects are equal.
|
||||
/// </summary>
|
||||
public bool Equals (PointF other) => this == other;
|
||||
|
||||
/// <summary>
|
||||
/// Generates a hashcode from the X and Y components
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override int GetHashCode ()
|
||||
{
|
||||
return X.GetHashCode() ^ Y.GetHashCode ();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string including the X and Y values
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override string ToString () => "{X=" + x.ToString () + ", Y=" + y.ToString () + "}";
|
||||
}
|
||||
}
|
||||
303
Terminal.Gui/Types/RectangleF.cs
Normal file
303
Terminal.Gui/Types/RectangleF.cs
Normal file
@@ -0,0 +1,303 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
// Copied from https://github.com/dotnet/corefx/tree/master/src/System.Drawing.Primitives/src/System/Drawing
|
||||
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace Terminal.Gui {
|
||||
/// <summary>
|
||||
/// Stores the location and size of a rectangular region.
|
||||
/// </summary>
|
||||
public struct RectangleF : IEquatable<RectangleF> {
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref='Terminal.Gui.RectangleF'/> class.
|
||||
/// </summary>
|
||||
public static readonly RectangleF Empty;
|
||||
|
||||
private float x; // Do not rename (binary serialization)
|
||||
private float y; // Do not rename (binary serialization)
|
||||
private float width; // Do not rename (binary serialization)
|
||||
private float height; // Do not rename (binary serialization)
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref='Terminal.Gui.RectangleF'/> class with the specified location
|
||||
/// and size.
|
||||
/// </summary>
|
||||
public RectangleF (float x, float y, float width, float height)
|
||||
{
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref='Terminal.Gui.RectangleF'/> class with the specified location
|
||||
/// and size.
|
||||
/// </summary>
|
||||
public RectangleF (PointF location, SizeF size)
|
||||
{
|
||||
x = location.X;
|
||||
y = location.Y;
|
||||
width = size.Width;
|
||||
height = size.Height;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref='Terminal.Gui.RectangleF'/> with the specified location and size.
|
||||
/// </summary>
|
||||
public static RectangleF FromLTRB (float left, float top, float right, float bottom) =>
|
||||
new RectangleF (left, top, right - left, bottom - top);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the coordinates of the upper-left corner of the rectangular region represented by this
|
||||
/// <see cref='Terminal.Gui.RectangleF'/>.
|
||||
/// </summary>
|
||||
[Browsable (false)]
|
||||
public PointF Location {
|
||||
get => new PointF (X, Y);
|
||||
set {
|
||||
X = value.X;
|
||||
Y = value.Y;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the size of this <see cref='Terminal.Gui.RectangleF'/>.
|
||||
/// </summary>
|
||||
[Browsable (false)]
|
||||
public SizeF Size {
|
||||
get => new SizeF (Width, Height);
|
||||
set {
|
||||
Width = value.Width;
|
||||
Height = value.Height;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the x-coordinate of the upper-left corner of the rectangular region defined by this
|
||||
/// <see cref='Terminal.Gui.RectangleF'/>.
|
||||
/// </summary>
|
||||
public float X {
|
||||
get => x;
|
||||
set => x = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the y-coordinate of the upper-left corner of the rectangular region defined by this
|
||||
/// <see cref='Terminal.Gui.RectangleF'/>.
|
||||
/// </summary>
|
||||
public float Y {
|
||||
get => y;
|
||||
set => y = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the width of the rectangular region defined by this <see cref='Terminal.Gui.RectangleF'/>.
|
||||
/// </summary>
|
||||
public float Width {
|
||||
get => width;
|
||||
set => width = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the height of the rectangular region defined by this <see cref='Terminal.Gui.RectangleF'/>.
|
||||
/// </summary>
|
||||
public float Height {
|
||||
get => height;
|
||||
set => height = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the x-coordinate of the upper-left corner of the rectangular region defined by this
|
||||
/// <see cref='Terminal.Gui.RectangleF'/> .
|
||||
/// </summary>
|
||||
[Browsable (false)]
|
||||
public float Left => X;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the y-coordinate of the upper-left corner of the rectangular region defined by this
|
||||
/// <see cref='Terminal.Gui.RectangleF'/>.
|
||||
/// </summary>
|
||||
[Browsable (false)]
|
||||
public float Top => Y;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the x-coordinate of the lower-right corner of the rectangular region defined by this
|
||||
/// <see cref='Terminal.Gui.RectangleF'/>.
|
||||
/// </summary>
|
||||
[Browsable (false)]
|
||||
public float Right => X + Width;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the y-coordinate of the lower-right corner of the rectangular region defined by this
|
||||
/// <see cref='Terminal.Gui.RectangleF'/>.
|
||||
/// </summary>
|
||||
[Browsable (false)]
|
||||
public float Bottom => Y + Height;
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether this <see cref='Terminal.Gui.RectangleF'/> has a <see cref='Terminal.Gui.RectangleF.Width'/> or a <see cref='Terminal.Gui.RectangleF.Height'/> of 0.
|
||||
/// </summary>
|
||||
[Browsable (false)]
|
||||
public bool IsEmpty => (Width <= 0) || (Height <= 0);
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether <paramref name="obj"/> is a <see cref='Terminal.Gui.RectangleF'/> with the same location and
|
||||
/// size of this <see cref='Terminal.Gui.RectangleF'/>.
|
||||
/// </summary>
|
||||
public override bool Equals (object obj) => obj is RectangleF && Equals ((RectangleF)obj);
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if two <see cref='Terminal.Gui.RectangleF'/> objects have equal location and size.
|
||||
/// </summary>
|
||||
/// <param name="other"></param>
|
||||
/// <returns></returns>
|
||||
public bool Equals (RectangleF other) => this == other;
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether two <see cref='Terminal.Gui.RectangleF'/> objects have equal location and size.
|
||||
/// </summary>
|
||||
public static bool operator == (RectangleF left, RectangleF right) =>
|
||||
left.X == right.X && left.Y == right.Y && left.Width == right.Width && left.Height == right.Height;
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether two <see cref='Terminal.Gui.RectangleF'/> objects differ in location or size.
|
||||
/// </summary>
|
||||
public static bool operator != (RectangleF left, RectangleF right) => !(left == right);
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the specified point is contained within the rectangular region defined by this
|
||||
/// <see cref='Terminal.Gui.Rect'/> .
|
||||
/// </summary>
|
||||
public bool Contains (float x, float y) => X <= x && x < X + Width && Y <= y && y < Y + Height;
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the specified point is contained within the rectangular region defined by this
|
||||
/// <see cref='Terminal.Gui.Rect'/> .
|
||||
/// </summary>
|
||||
public bool Contains (PointF pt) => Contains (pt.X, pt.Y);
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the rectangular region represented by <paramref name="rect"/> is entirely contained within
|
||||
/// the rectangular region represented by this <see cref='Terminal.Gui.Rect'/> .
|
||||
/// </summary>
|
||||
public bool Contains (RectangleF rect) =>
|
||||
(X <= rect.X) && (rect.X + rect.Width <= X + Width) && (Y <= rect.Y) && (rect.Y + rect.Height <= Y + Height);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the hash code for this <see cref='Terminal.Gui.RectangleF'/>.
|
||||
/// </summary>
|
||||
public override int GetHashCode ()
|
||||
{
|
||||
return (Height.GetHashCode () + Width.GetHashCode ()) ^ X.GetHashCode () + Y.GetHashCode ();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inflates this <see cref='Terminal.Gui.Rect'/> by the specified amount.
|
||||
/// </summary>
|
||||
public void Inflate (float x, float y)
|
||||
{
|
||||
X -= x;
|
||||
Y -= y;
|
||||
Width += 2 * x;
|
||||
Height += 2 * y;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inflates this <see cref='Terminal.Gui.Rect'/> by the specified amount.
|
||||
/// </summary>
|
||||
public void Inflate (SizeF size) => Inflate (size.Width, size.Height);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref='Terminal.Gui.Rect'/> that is inflated by the specified amount.
|
||||
/// </summary>
|
||||
public static RectangleF Inflate (RectangleF rect, float x, float y)
|
||||
{
|
||||
RectangleF r = rect;
|
||||
r.Inflate (x, y);
|
||||
return r;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a Rectangle that represents the intersection between this Rectangle and rect.
|
||||
/// </summary>
|
||||
public void Intersect (RectangleF rect)
|
||||
{
|
||||
RectangleF result = Intersect (rect, this);
|
||||
|
||||
X = result.X;
|
||||
Y = result.Y;
|
||||
Width = result.Width;
|
||||
Height = result.Height;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a rectangle that represents the intersection between a and b. If there is no intersection, an
|
||||
/// empty rectangle is returned.
|
||||
/// </summary>
|
||||
public static RectangleF Intersect (RectangleF a, RectangleF b)
|
||||
{
|
||||
float x1 = Math.Max (a.X, b.X);
|
||||
float x2 = Math.Min (a.X + a.Width, b.X + b.Width);
|
||||
float y1 = Math.Max (a.Y, b.Y);
|
||||
float y2 = Math.Min (a.Y + a.Height, b.Y + b.Height);
|
||||
|
||||
if (x2 >= x1 && y2 >= y1) {
|
||||
return new RectangleF (x1, y1, x2 - x1, y2 - y1);
|
||||
}
|
||||
|
||||
return Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if this rectangle intersects with rect.
|
||||
/// </summary>
|
||||
public bool IntersectsWith (RectangleF rect) =>
|
||||
(rect.X < X + Width) && (X < rect.X + rect.Width) && (rect.Y < Y + Height) && (Y < rect.Y + rect.Height);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a rectangle that represents the union between a and b.
|
||||
/// </summary>
|
||||
public static RectangleF Union (RectangleF a, RectangleF b)
|
||||
{
|
||||
float x1 = Math.Min (a.X, b.X);
|
||||
float x2 = Math.Max (a.X + a.Width, b.X + b.Width);
|
||||
float y1 = Math.Min (a.Y, b.Y);
|
||||
float y2 = Math.Max (a.Y + a.Height, b.Y + b.Height);
|
||||
|
||||
return new RectangleF (x1, y1, x2 - x1, y2 - y1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adjusts the location of this rectangle by the specified amount.
|
||||
/// </summary>
|
||||
public void Offset (PointF pos) => Offset (pos.X, pos.Y);
|
||||
|
||||
/// <summary>
|
||||
/// Adjusts the location of this rectangle by the specified amount.
|
||||
/// </summary>
|
||||
public void Offset (float x, float y)
|
||||
{
|
||||
X += x;
|
||||
Y += y;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the specified <see cref='Terminal.Gui.Rect'/> to a
|
||||
/// <see cref='Terminal.Gui.RectangleF'/>.
|
||||
/// </summary>
|
||||
public static implicit operator RectangleF (Rect r) => new RectangleF (r.X, r.Y, r.Width, r.Height);
|
||||
|
||||
/// <summary>
|
||||
/// Converts the <see cref='Terminal.Gui.RectangleF.Location'/> and <see cref='Terminal.Gui.RectangleF.Size'/>
|
||||
/// of this <see cref='Terminal.Gui.RectangleF'/> to a human-readable string.
|
||||
/// </summary>
|
||||
public override string ToString () =>
|
||||
"{X=" + X.ToString () + ",Y=" + Y.ToString () +
|
||||
",Width=" + Width.ToString () + ",Height=" + Height.ToString () + "}";
|
||||
}
|
||||
}
|
||||
168
Terminal.Gui/Types/SizeF.cs
Normal file
168
Terminal.Gui/Types/SizeF.cs
Normal file
@@ -0,0 +1,168 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
// Copied from: https://github.com/dotnet/corefx/tree/master/src/System.Drawing.Primitives/src/System/Drawing
|
||||
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace Terminal.Gui {
|
||||
/// <summary>
|
||||
/// Represents the size of a rectangular region with an ordered pair of width and height.
|
||||
/// </summary>
|
||||
public struct SizeF : IEquatable<SizeF> {
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref='Terminal.Gui.SizeF'/> class.
|
||||
/// </summary>
|
||||
public static readonly SizeF Empty;
|
||||
private float width; // Do not rename (binary serialization)
|
||||
private float height; // Do not rename (binary serialization)
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref='Terminal.Gui.SizeF'/> class from the specified
|
||||
/// existing <see cref='Terminal.Gui.SizeF'/>.
|
||||
/// </summary>
|
||||
public SizeF (SizeF size)
|
||||
{
|
||||
width = size.width;
|
||||
height = size.height;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref='Terminal.Gui.SizeF'/> class from the specified
|
||||
/// <see cref='Terminal.Gui.PointF'/>.
|
||||
/// </summary>
|
||||
public SizeF (PointF pt)
|
||||
{
|
||||
width = pt.X;
|
||||
height = pt.Y;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref='Terminal.Gui.SizeF'/> class from the specified dimensions.
|
||||
/// </summary>
|
||||
public SizeF (float width, float height)
|
||||
{
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs vector addition of two <see cref='Terminal.Gui.SizeF'/> objects.
|
||||
/// </summary>
|
||||
public static SizeF operator + (SizeF sz1, SizeF sz2) => Add (sz1, sz2);
|
||||
|
||||
/// <summary>
|
||||
/// Contracts a <see cref='Terminal.Gui.SizeF'/> by another <see cref='Terminal.Gui.SizeF'/>
|
||||
/// </summary>
|
||||
public static SizeF operator - (SizeF sz1, SizeF sz2) => Subtract (sz1, sz2);
|
||||
|
||||
/// <summary>
|
||||
/// Multiplies <see cref="SizeF"/> by a <see cref="float"/> producing <see cref="SizeF"/>.
|
||||
/// </summary>
|
||||
/// <param name="left">Multiplier of type <see cref="float"/>.</param>
|
||||
/// <param name="right">Multiplicand of type <see cref="SizeF"/>.</param>
|
||||
/// <returns>Product of type <see cref="SizeF"/>.</returns>
|
||||
public static SizeF operator * (float left, SizeF right) => Multiply (right, left);
|
||||
|
||||
/// <summary>
|
||||
/// Multiplies <see cref="SizeF"/> by a <see cref="float"/> producing <see cref="SizeF"/>.
|
||||
/// </summary>
|
||||
/// <param name="left">Multiplicand of type <see cref="SizeF"/>.</param>
|
||||
/// <param name="right">Multiplier of type <see cref="float"/>.</param>
|
||||
/// <returns>Product of type <see cref="SizeF"/>.</returns>
|
||||
public static SizeF operator * (SizeF left, float right) => Multiply (left, right);
|
||||
|
||||
/// <summary>
|
||||
/// Divides <see cref="SizeF"/> by a <see cref="float"/> producing <see cref="SizeF"/>.
|
||||
/// </summary>
|
||||
/// <param name="left">Dividend of type <see cref="SizeF"/>.</param>
|
||||
/// <param name="right">Divisor of type <see cref="int"/>.</param>
|
||||
/// <returns>Result of type <see cref="SizeF"/>.</returns>
|
||||
public static SizeF operator / (SizeF left, float right)
|
||||
=> new SizeF (left.width / right, left.height / right);
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether two <see cref='Terminal.Gui.SizeF'/> objects are identical.
|
||||
/// </summary>
|
||||
public static bool operator == (SizeF sz1, SizeF sz2) => sz1.Width == sz2.Width && sz1.Height == sz2.Height;
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether two <see cref='Terminal.Gui.SizeF'/> objects are different.
|
||||
/// </summary>
|
||||
public static bool operator != (SizeF sz1, SizeF sz2) => !(sz1 == sz2);
|
||||
|
||||
/// <summary>
|
||||
/// Converts the specified <see cref='Terminal.Gui.SizeF'/> to a <see cref='Terminal.Gui.PointF'/>.
|
||||
/// </summary>
|
||||
public static explicit operator PointF (SizeF size) => new PointF (size.Width, size.Height);
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether this <see cref='Terminal.Gui.SizeF'/> has zero width and height.
|
||||
/// </summary>
|
||||
[Browsable (false)]
|
||||
public bool IsEmpty => width == 0 && height == 0;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the horizontal component of this <see cref='Terminal.Gui.SizeF'/>.
|
||||
/// </summary>
|
||||
public float Width {
|
||||
get => width;
|
||||
set => width = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents the vertical component of this <see cref='Terminal.Gui.SizeF'/>.
|
||||
/// </summary>
|
||||
public float Height {
|
||||
get => height;
|
||||
set => height = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs vector addition of two <see cref='Terminal.Gui.SizeF'/> objects.
|
||||
/// </summary>
|
||||
public static SizeF Add (SizeF sz1, SizeF sz2) => new SizeF (sz1.Width + sz2.Width, sz1.Height + sz2.Height);
|
||||
|
||||
/// <summary>
|
||||
/// Contracts a <see cref='Terminal.Gui.SizeF'/> by another <see cref='Terminal.Gui.SizeF'/>.
|
||||
/// </summary>
|
||||
public static SizeF Subtract (SizeF sz1, SizeF sz2) => new SizeF (sz1.Width - sz2.Width, sz1.Height - sz2.Height);
|
||||
|
||||
/// <summary>
|
||||
/// Tests to see whether the specified object is a <see cref='Terminal.Gui.SizeF'/> with the same dimensions
|
||||
/// as this <see cref='Terminal.Gui.SizeF'/>.
|
||||
/// </summary>
|
||||
public override bool Equals (object obj) => obj is SizeF && Equals ((SizeF)obj);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether two <see cref='Terminal.Gui.SizeF'/> objects are identical.
|
||||
/// </summary>
|
||||
public bool Equals (SizeF other) => this == other;
|
||||
|
||||
/// <summary>
|
||||
/// Generates a hashcode from the width and height
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override int GetHashCode ()
|
||||
{
|
||||
return width.GetHashCode() ^ height.GetHashCode ();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a human-readable string that represents this <see cref='Terminal.Gui.SizeF'/>.
|
||||
/// </summary>
|
||||
public override string ToString () => "{Width=" + width.ToString () + ", Height=" + height.ToString () + "}";
|
||||
|
||||
/// <summary>
|
||||
/// Multiplies <see cref="SizeF"/> by a <see cref="float"/> producing <see cref="SizeF"/>.
|
||||
/// </summary>
|
||||
/// <param name="size">Multiplicand of type <see cref="SizeF"/>.</param>
|
||||
/// <param name="multiplier">Multiplier of type <see cref="float"/>.</param>
|
||||
/// <returns>Product of type SizeF.</returns>
|
||||
private static SizeF Multiply (SizeF size, float multiplier) =>
|
||||
new SizeF (size.width * multiplier, size.height * multiplier);
|
||||
}
|
||||
}
|
||||
318
Terminal.Gui/Views/GraphView.cs
Normal file
318
Terminal.Gui/Views/GraphView.cs
Normal file
@@ -0,0 +1,318 @@
|
||||
using NStack;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Terminal.Gui.Graphs;
|
||||
|
||||
namespace Terminal.Gui {
|
||||
|
||||
/// <summary>
|
||||
/// Control for rendering graphs (bar, scatter etc)
|
||||
/// </summary>
|
||||
public class GraphView : View {
|
||||
|
||||
/// <summary>
|
||||
/// Horizontal axis
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
public HorizontalAxis AxisX { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Vertical axis
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
public VerticalAxis AxisY { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Collection of data series that are rendered in the graph
|
||||
/// </summary>
|
||||
public List<ISeries> Series { get; } = new List<ISeries> ();
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Elements drawn into graph after series have been drawn e.g. Legends etc
|
||||
/// </summary>
|
||||
public List<IAnnotation> Annotations { get; } = new List<IAnnotation> ();
|
||||
|
||||
/// <summary>
|
||||
/// Amount of space to leave on left of control. Graph content (<see cref="Series"/>)
|
||||
/// will not be rendered in margins but axis labels may be
|
||||
/// </summary>
|
||||
public uint MarginLeft { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Amount of space to leave on bottom of control. Graph content (<see cref="Series"/>)
|
||||
/// will not be rendered in margins but axis labels may be
|
||||
/// </summary>
|
||||
public uint MarginBottom { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The graph space position of the bottom left of the control.
|
||||
/// Changing this scrolls the viewport around in the graph
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
public PointF ScrollOffset { get; set; } = new PointF (0, 0);
|
||||
|
||||
/// <summary>
|
||||
/// Translates console width/height into graph space. Defaults
|
||||
/// to 1 row/col of console space being 1 unit of graph space.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public PointF CellSize { get; set; } = new PointF (1, 1);
|
||||
|
||||
/// <summary>
|
||||
/// The color of the background of the graph and axis/labels
|
||||
/// </summary>
|
||||
public Attribute? GraphColor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new graph with a 1 to 1 graph space with absolute layout
|
||||
/// </summary>
|
||||
public GraphView ()
|
||||
{
|
||||
CanFocus = true;
|
||||
|
||||
AxisX = new HorizontalAxis ();
|
||||
AxisY = new VerticalAxis ();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears all settings configured on the graph and resets all properties
|
||||
/// to default values (<see cref="CellSize"/>, <see cref="ScrollOffset"/> etc)
|
||||
/// </summary>
|
||||
public void Reset ()
|
||||
{
|
||||
ScrollOffset = new PointF (0, 0);
|
||||
CellSize = new PointF (1, 1);
|
||||
AxisX.Reset ();
|
||||
AxisY.Reset ();
|
||||
Series.Clear ();
|
||||
Annotations.Clear ();
|
||||
GraphColor = null;
|
||||
SetNeedsDisplay ();
|
||||
}
|
||||
|
||||
///<inheritdoc/>
|
||||
public override void Redraw (Rect bounds)
|
||||
{
|
||||
if(CellSize.X == 0 || CellSize.Y == 0) {
|
||||
throw new Exception ($"{nameof(CellSize)} cannot be 0");
|
||||
}
|
||||
|
||||
SetDriverColorToGraphColor ();
|
||||
|
||||
Move (0, 0);
|
||||
|
||||
// clear all old content
|
||||
for (int i = 0; i < Bounds.Height; i++) {
|
||||
Move (0, i);
|
||||
Driver.AddStr (new string (' ', Bounds.Width));
|
||||
}
|
||||
|
||||
// If there is no data do not display a graph
|
||||
if (!Series.Any () && !Annotations.Any ()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Draw 'before' annotations
|
||||
foreach (var a in Annotations.Where (a => a.BeforeSeries)) {
|
||||
a.Render (this);
|
||||
}
|
||||
|
||||
SetDriverColorToGraphColor ();
|
||||
|
||||
AxisY.DrawAxisLine (this);
|
||||
AxisX.DrawAxisLine (this);
|
||||
|
||||
AxisY.DrawAxisLabels (this);
|
||||
AxisX.DrawAxisLabels (this);
|
||||
|
||||
// Draw a cross where the two axis cross
|
||||
var axisIntersection = new Point(AxisY.GetAxisXPosition(this),AxisX.GetAxisYPosition(this));
|
||||
|
||||
if (AxisX.Visible && AxisY.Visible) {
|
||||
Move (axisIntersection.X, axisIntersection.Y);
|
||||
AddRune (axisIntersection.X, axisIntersection.Y, '\u253C');
|
||||
}
|
||||
|
||||
SetDriverColorToGraphColor ();
|
||||
|
||||
// The drawable area of the graph (anything that isn't in the margins)
|
||||
Rect drawBounds = new Rect((int)MarginLeft,0, Bounds.Width - ((int)MarginLeft), Bounds.Height - (int)MarginBottom);
|
||||
RectangleF graphSpace = ScreenToGraphSpace (drawBounds);
|
||||
|
||||
foreach (var s in Series) {
|
||||
|
||||
s.DrawSeries (this, drawBounds, graphSpace);
|
||||
|
||||
// If a series changes the graph color reset it
|
||||
SetDriverColorToGraphColor ();
|
||||
}
|
||||
|
||||
SetDriverColorToGraphColor ();
|
||||
|
||||
// Draw 'after' annotations
|
||||
foreach (var a in Annotations.Where (a => !a.BeforeSeries)) {
|
||||
a.Render (this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the color attribute of <see cref="Application.Driver"/> to the <see cref="GraphColor"/>
|
||||
/// (if defined) or <see cref="ColorScheme"/> otherwise.
|
||||
/// </summary>
|
||||
public void SetDriverColorToGraphColor ()
|
||||
{
|
||||
Driver.SetAttribute (GraphColor ?? ColorScheme.Normal);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the section of the graph that is represented by the given
|
||||
/// screen position
|
||||
/// </summary>
|
||||
/// <param name="col"></param>
|
||||
/// <param name="row"></param>
|
||||
/// <returns></returns>
|
||||
public RectangleF ScreenToGraphSpace (int col, int row)
|
||||
{
|
||||
return new RectangleF (
|
||||
ScrollOffset.X + ((col - MarginLeft) * CellSize.X),
|
||||
ScrollOffset.Y + ((Bounds.Height - (row + MarginBottom + 1)) * CellSize.Y),
|
||||
CellSize.X, CellSize.Y);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns the section of the graph that is represented by the screen area
|
||||
/// </summary>
|
||||
/// <param name="screenArea"></param>
|
||||
/// <returns></returns>
|
||||
public RectangleF ScreenToGraphSpace (Rect screenArea)
|
||||
{
|
||||
// get position of the bottom left
|
||||
var pos = ScreenToGraphSpace (screenArea.Left, screenArea.Bottom-1);
|
||||
|
||||
return new RectangleF (pos.X, pos.Y, screenArea.Width * CellSize.X, screenArea.Height * CellSize.Y);
|
||||
}
|
||||
/// <summary>
|
||||
/// Calculates the screen location for a given point in graph space.
|
||||
/// Bear in mind these be off screen
|
||||
/// </summary>
|
||||
/// <param name="location">Point in graph space that may or may not be represented in the
|
||||
/// visible area of graph currently presented. E.g. 0,0 for origin</param>
|
||||
/// <returns>Screen position (Column/Row) which would be used to render the graph <paramref name="location"/>.
|
||||
/// Note that this can be outside the current client area of the control</returns>
|
||||
public Point GraphSpaceToScreen (PointF location)
|
||||
{
|
||||
return new Point (
|
||||
|
||||
(int)((location.X - ScrollOffset.X) / CellSize.X) + (int)MarginLeft,
|
||||
// screen coordinates are top down while graph coordinates are bottom up
|
||||
(Bounds.Height - 1) - (int)MarginBottom - (int)((location.Y - ScrollOffset.Y) / CellSize.Y)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool ProcessKey (KeyEvent keyEvent)
|
||||
{
|
||||
//&& Focused == tabsBar
|
||||
|
||||
if (HasFocus && CanFocus) {
|
||||
switch (keyEvent.Key) {
|
||||
|
||||
case Key.CursorLeft:
|
||||
Scroll (-CellSize.X, 0);
|
||||
return true;
|
||||
case Key.CursorLeft | Key.CtrlMask:
|
||||
Scroll (-CellSize.X * 5, 0);
|
||||
return true;
|
||||
case Key.CursorRight:
|
||||
Scroll (CellSize.X, 0);
|
||||
return true;
|
||||
case Key.CursorRight | Key.CtrlMask:
|
||||
Scroll (CellSize.X * 5, 0);
|
||||
return true;
|
||||
case Key.CursorDown:
|
||||
Scroll (0, -CellSize.Y);
|
||||
return true;
|
||||
case Key.CursorDown | Key.CtrlMask:
|
||||
Scroll (0, -CellSize.Y * 5);
|
||||
return true;
|
||||
case Key.CursorUp:
|
||||
Scroll (0, CellSize.Y);
|
||||
return true;
|
||||
case Key.CursorUp | Key.CtrlMask:
|
||||
Scroll (0, CellSize.Y * 5);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return base.ProcessKey (keyEvent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scrolls the view by a given number of units in graph space.
|
||||
/// See <see cref="CellSize"/> to translate this into rows/cols
|
||||
/// </summary>
|
||||
/// <param name="offsetX"></param>
|
||||
/// <param name="offsetY"></param>
|
||||
private void Scroll (float offsetX, float offsetY)
|
||||
{
|
||||
ScrollOffset = new PointF (
|
||||
ScrollOffset.X + offsetX,
|
||||
ScrollOffset.Y + offsetY);
|
||||
|
||||
SetNeedsDisplay ();
|
||||
}
|
||||
|
||||
|
||||
#region Bresenham's line algorithm
|
||||
// https://rosettacode.org/wiki/Bitmap/Bresenham%27s_line_algorithm#C.23
|
||||
|
||||
int ipart (decimal x) { return (int)x; }
|
||||
|
||||
|
||||
decimal fpart (decimal x)
|
||||
{
|
||||
if (x < 0) return (1 - (x - Math.Floor (x)));
|
||||
return (x - Math.Floor (x));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws a line between two points in screen space. Can be diagonals.
|
||||
/// </summary>
|
||||
/// <param name="start"></param>
|
||||
/// <param name="end"></param>
|
||||
/// <param name="symbol">The symbol to use for the line</param>
|
||||
public void DrawLine (Point start, Point end, Rune symbol)
|
||||
{
|
||||
if (Equals (start, end)) {
|
||||
return;
|
||||
}
|
||||
|
||||
int x0 = start.X;
|
||||
int y0 = start.Y;
|
||||
int x1 = end.X;
|
||||
int y1 = end.Y;
|
||||
|
||||
int dx = Math.Abs (x1 - x0), sx = x0 < x1 ? 1 : -1;
|
||||
int dy = Math.Abs (y1 - y0), sy = y0 < y1 ? 1 : -1;
|
||||
int err = (dx > dy ? dx : -dy) / 2, e2;
|
||||
|
||||
while (true) {
|
||||
|
||||
AddRune (x0, y0, symbol);
|
||||
|
||||
if (x0 == x1 && y0 == y1) break;
|
||||
e2 = err;
|
||||
if (e2 > -dx) { err -= dy; x0 += sx; }
|
||||
if (e2 < dy) { err += dx; y0 += sy; }
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
685
UICatalog/Scenarios/GraphViewExample.cs
Normal file
685
UICatalog/Scenarios/GraphViewExample.cs
Normal file
@@ -0,0 +1,685 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Terminal.Gui;
|
||||
using Terminal.Gui.Graphs;
|
||||
|
||||
using Color = Terminal.Gui.Color;
|
||||
|
||||
namespace UICatalog.Scenarios {
|
||||
|
||||
[ScenarioMetadata (Name: "Graph View", Description: "Demos GraphView control")]
|
||||
[ScenarioCategory ("Controls")]
|
||||
class GraphViewExample : Scenario {
|
||||
|
||||
GraphView graphView;
|
||||
private TextView about;
|
||||
|
||||
int currentGraph = 0;
|
||||
Action [] graphs;
|
||||
|
||||
public override void Setup ()
|
||||
{
|
||||
Win.Title = this.GetName ();
|
||||
Win.Y = 1; // menu
|
||||
Win.Height = Dim.Fill (1); // status bar
|
||||
Top.LayoutSubviews ();
|
||||
|
||||
graphs = new Action [] {
|
||||
()=>SetupPeriodicTableScatterPlot(), //0
|
||||
()=>SetupLifeExpectancyBarGraph(true), //1
|
||||
()=>SetupLifeExpectancyBarGraph(false), //2
|
||||
()=>SetupPopulationPyramid(), //3
|
||||
()=>SetupLineGraph(), //4
|
||||
()=>SetupSineWave(), //5
|
||||
()=>SetupDisco(), //6
|
||||
()=>MultiBarGraph() //7
|
||||
};
|
||||
|
||||
|
||||
var menu = new MenuBar (new MenuBarItem [] {
|
||||
new MenuBarItem ("_File", new MenuItem [] {
|
||||
new MenuItem ("Scatter _Plot", "",()=>graphs[currentGraph = 0]()),
|
||||
new MenuItem ("_V Bar Graph", "", ()=>graphs[currentGraph = 1]()),
|
||||
new MenuItem ("_H Bar Graph", "", ()=>graphs[currentGraph = 2]()) ,
|
||||
new MenuItem ("P_opulation Pyramid","",()=>graphs[currentGraph = 3]()),
|
||||
new MenuItem ("_Line Graph","",()=>graphs[currentGraph = 4]()),
|
||||
new MenuItem ("Sine _Wave","",()=>graphs[currentGraph = 5]()),
|
||||
new MenuItem ("Silent _Disco","",()=>graphs[currentGraph = 6]()),
|
||||
new MenuItem ("_Multi Bar Graph","",()=>graphs[currentGraph = 7]()),
|
||||
new MenuItem ("_Quit", "", () => Quit()),
|
||||
}),
|
||||
new MenuBarItem ("_View", new MenuItem [] {
|
||||
new MenuItem ("Zoom _In", "", () => Zoom(0.5f)),
|
||||
new MenuItem ("Zoom _Out", "", () => Zoom(2f)),
|
||||
}),
|
||||
|
||||
});
|
||||
Top.Add (menu);
|
||||
|
||||
graphView = new GraphView () {
|
||||
X = 1,
|
||||
Y = 1,
|
||||
Width = 60,
|
||||
Height = 20,
|
||||
};
|
||||
|
||||
|
||||
Win.Add (graphView);
|
||||
|
||||
|
||||
var frameRight = new FrameView ("About") {
|
||||
X = Pos.Right (graphView) + 1,
|
||||
Y = 0,
|
||||
Width = Dim.Fill (),
|
||||
Height = Dim.Fill (),
|
||||
};
|
||||
|
||||
|
||||
frameRight.Add (about = new TextView () {
|
||||
Width = Dim.Fill (),
|
||||
Height = Dim.Fill ()
|
||||
});
|
||||
|
||||
Win.Add (frameRight);
|
||||
|
||||
|
||||
var statusBar = new StatusBar (new StatusItem [] {
|
||||
new StatusItem(Key.CtrlMask | Key.Q, "~^Q~ Quit", () => Quit()),
|
||||
new StatusItem(Key.CtrlMask | Key.G, "~^G~ Next", ()=>graphs[currentGraph++%graphs.Length]()),
|
||||
});
|
||||
Top.Add (statusBar);
|
||||
}
|
||||
|
||||
private void MultiBarGraph ()
|
||||
{
|
||||
graphView.Reset ();
|
||||
|
||||
about.Text = "Housing Expenditures by income thirds 1996-2003";
|
||||
|
||||
var black = Application.Driver.MakeAttribute (graphView.ColorScheme.Normal.Foreground, Color.Black);
|
||||
var cyan = Application.Driver.MakeAttribute (Color.BrightCyan, Color.Black);
|
||||
var magenta = Application.Driver.MakeAttribute (Color.BrightMagenta, Color.Black);
|
||||
var red = Application.Driver.MakeAttribute (Color.BrightRed, Color.Black);
|
||||
|
||||
graphView.GraphColor = black;
|
||||
|
||||
var series = new MultiBarSeries (3, 1, 0.25f, new [] { magenta, cyan, red });
|
||||
|
||||
var stiple = Application.Driver.Stipple;
|
||||
|
||||
series.AddBars ("'96", stiple, 5900, 9000, 14000);
|
||||
series.AddBars ("'97", stiple, 6100, 9200, 14800);
|
||||
series.AddBars ("'98", stiple, 6000, 9300, 14600);
|
||||
series.AddBars ("'99", stiple, 6100, 9400, 14950);
|
||||
series.AddBars ("'00", stiple, 6200, 9500, 15200);
|
||||
series.AddBars ("'01", stiple, 6250, 9900, 16000);
|
||||
series.AddBars ("'02", stiple, 6600, 11000, 16700);
|
||||
series.AddBars ("'03", stiple, 7000, 12000, 17000);
|
||||
|
||||
graphView.CellSize = new PointF (0.25f, 1000);
|
||||
graphView.Series.Add (series);
|
||||
graphView.SetNeedsDisplay ();
|
||||
|
||||
graphView.MarginLeft = 3;
|
||||
graphView.MarginBottom = 1;
|
||||
|
||||
graphView.AxisY.LabelGetter = (v) => '$' + (v.Value / 1000f).ToString ("N0") + 'k';
|
||||
|
||||
// Do not show x axis labels (bars draw their own labels)
|
||||
graphView.AxisX.Increment = 0;
|
||||
graphView.AxisX.ShowLabelsEvery = 0;
|
||||
graphView.AxisX.Minimum = 0;
|
||||
|
||||
|
||||
graphView.AxisY.Minimum = 0;
|
||||
|
||||
var legend = new LegendAnnotation (new Rect (graphView.Bounds.Width - 20,0, 20, 5));
|
||||
legend.AddEntry (new GraphCellToRender (stiple, series.SubSeries.ElementAt (0).OverrideBarColor), "Lower Third");
|
||||
legend.AddEntry (new GraphCellToRender (stiple, series.SubSeries.ElementAt (1).OverrideBarColor), "Middle Third");
|
||||
legend.AddEntry (new GraphCellToRender (stiple, series.SubSeries.ElementAt (2).OverrideBarColor), "Upper Third");
|
||||
graphView.Annotations.Add (legend);
|
||||
}
|
||||
|
||||
private void SetupLineGraph ()
|
||||
{
|
||||
graphView.Reset ();
|
||||
|
||||
about.Text = "This graph shows random points";
|
||||
|
||||
var black = Application.Driver.MakeAttribute (graphView.ColorScheme.Normal.Foreground, Color.Black);
|
||||
var cyan = Application.Driver.MakeAttribute (Color.BrightCyan, Color.Black);
|
||||
var magenta = Application.Driver.MakeAttribute (Color.BrightMagenta, Color.Black);
|
||||
var red = Application.Driver.MakeAttribute (Color.BrightRed, Color.Black);
|
||||
|
||||
graphView.GraphColor = black;
|
||||
|
||||
List<PointF> randomPoints = new List<PointF> ();
|
||||
|
||||
Random r = new Random ();
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
randomPoints.Add (new PointF (r.Next (100), r.Next (100)));
|
||||
}
|
||||
|
||||
var points = new ScatterSeries () {
|
||||
Points = randomPoints
|
||||
};
|
||||
|
||||
var line = new PathAnnotation () {
|
||||
LineColor = cyan,
|
||||
Points = randomPoints.OrderBy (p => p.X).ToList (),
|
||||
BeforeSeries = true,
|
||||
};
|
||||
|
||||
graphView.Series.Add (points);
|
||||
graphView.Annotations.Add (line);
|
||||
|
||||
|
||||
randomPoints = new List<PointF> ();
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
randomPoints.Add (new PointF (r.Next (100), r.Next (100)));
|
||||
}
|
||||
|
||||
|
||||
var points2 = new ScatterSeries () {
|
||||
Points = randomPoints,
|
||||
Fill = new GraphCellToRender ('x', red)
|
||||
};
|
||||
|
||||
var line2 = new PathAnnotation () {
|
||||
LineColor = magenta,
|
||||
Points = randomPoints.OrderBy (p => p.X).ToList (),
|
||||
BeforeSeries = true,
|
||||
};
|
||||
|
||||
graphView.Series.Add (points2);
|
||||
graphView.Annotations.Add (line2);
|
||||
|
||||
// How much graph space each cell of the console depicts
|
||||
graphView.CellSize = new PointF (2, 5);
|
||||
|
||||
// leave space for axis labels
|
||||
graphView.MarginBottom = 2;
|
||||
graphView.MarginLeft = 3;
|
||||
|
||||
// One axis tick/label per
|
||||
graphView.AxisX.Increment = 20;
|
||||
graphView.AxisX.ShowLabelsEvery = 1;
|
||||
graphView.AxisX.Text = "X →";
|
||||
|
||||
graphView.AxisY.Increment = 20;
|
||||
graphView.AxisY.ShowLabelsEvery = 1;
|
||||
graphView.AxisY.Text = "↑Y";
|
||||
|
||||
var max = line.Points.Union (line2.Points).OrderByDescending (p => p.Y).First ();
|
||||
graphView.Annotations.Add (new TextAnnotation () { Text = "(Max)", GraphPosition = new PointF (max.X + (2 * graphView.CellSize.X), max.Y) });
|
||||
|
||||
graphView.SetNeedsDisplay ();
|
||||
}
|
||||
|
||||
private void SetupSineWave ()
|
||||
{
|
||||
graphView.Reset ();
|
||||
|
||||
about.Text = "This graph shows a sine wave";
|
||||
|
||||
var points = new ScatterSeries ();
|
||||
var line = new PathAnnotation ();
|
||||
|
||||
// Draw line first so it does not draw over top of points or axis labels
|
||||
line.BeforeSeries = true;
|
||||
|
||||
// Generate line graph with 2,000 points
|
||||
for (float x = -500; x < 500; x += 0.5f) {
|
||||
points.Points.Add (new PointF (x, (float)Math.Sin (x)));
|
||||
line.Points.Add (new PointF (x, (float)Math.Sin (x)));
|
||||
}
|
||||
|
||||
graphView.Series.Add (points);
|
||||
graphView.Annotations.Add (line);
|
||||
|
||||
// How much graph space each cell of the console depicts
|
||||
graphView.CellSize = new PointF (0.1f, 0.1f);
|
||||
|
||||
// leave space for axis labels
|
||||
graphView.MarginBottom = 2;
|
||||
graphView.MarginLeft = 3;
|
||||
|
||||
// One axis tick/label per
|
||||
graphView.AxisX.Increment = 0.5f;
|
||||
graphView.AxisX.ShowLabelsEvery = 2;
|
||||
graphView.AxisX.Text = "X →";
|
||||
graphView.AxisX.LabelGetter = (v) => v.Value.ToString ("N2");
|
||||
|
||||
graphView.AxisY.Increment = 0.2f;
|
||||
graphView.AxisY.ShowLabelsEvery = 2;
|
||||
graphView.AxisY.Text = "↑Y";
|
||||
graphView.AxisY.LabelGetter = (v) => v.Value.ToString ("N2");
|
||||
|
||||
graphView.ScrollOffset = new PointF (-2.5f, -1);
|
||||
|
||||
graphView.SetNeedsDisplay ();
|
||||
}
|
||||
/*
|
||||
Country,Both,Male,Female
|
||||
|
||||
"Switzerland",83.4,81.8,85.1
|
||||
"South Korea",83.3,80.3,86.1
|
||||
"Singapore",83.2,81,85.5
|
||||
"Spain",83.2,80.7,85.7
|
||||
"Cyprus",83.1,81.1,85.1
|
||||
"Australia",83,81.3,84.8
|
||||
"Italy",83,80.9,84.9
|
||||
"Norway",83,81.2,84.7
|
||||
"Israel",82.6,80.8,84.4
|
||||
"France",82.5,79.8,85.1
|
||||
"Luxembourg",82.4,80.6,84.2
|
||||
"Sweden",82.4,80.8,84
|
||||
"Iceland",82.3,80.8,83.9
|
||||
"Canada",82.2,80.4,84.1
|
||||
"New Zealand",82,80.4,83.5
|
||||
"Malta,81.9",79.9,83.8
|
||||
"Ireland",81.8,80.2,83.5
|
||||
"Netherlands",81.8,80.4,83.1
|
||||
"Germany",81.7,78.7,84.8
|
||||
"Austria",81.6,79.4,83.8
|
||||
"Finland",81.6,79.2,84
|
||||
"Portugal",81.6,78.6,84.4
|
||||
"Belgium",81.4,79.3,83.5
|
||||
"United Kingdom",81.4,79.8,83
|
||||
"Denmark",81.3,79.6,83
|
||||
"Slovenia",81.3,78.6,84.1
|
||||
"Greece",81.1,78.6,83.6
|
||||
"Kuwait",81,79.3,83.9
|
||||
"Costa Rica",80.8,78.3,83.4*/
|
||||
|
||||
private void SetupLifeExpectancyBarGraph (bool verticalBars)
|
||||
{
|
||||
graphView.Reset ();
|
||||
|
||||
about.Text = "This graph shows the life expectancy at birth of a range of countries";
|
||||
|
||||
var softStiple = new GraphCellToRender ('\u2591');
|
||||
var mediumStiple = new GraphCellToRender ('\u2592');
|
||||
|
||||
var barSeries = new BarSeries () {
|
||||
Bars = new List<BarSeries.Bar> () {
|
||||
new BarSeries.Bar ("Switzerland", softStiple, 83.4f),
|
||||
new BarSeries.Bar ("South Korea", !verticalBars?mediumStiple:softStiple, 83.3f),
|
||||
new BarSeries.Bar ("Singapore", softStiple, 83.2f),
|
||||
new BarSeries.Bar ("Spain", !verticalBars?mediumStiple:softStiple, 83.2f),
|
||||
new BarSeries.Bar ("Cyprus", softStiple, 83.1f),
|
||||
new BarSeries.Bar ("Australia", !verticalBars?mediumStiple:softStiple, 83),
|
||||
new BarSeries.Bar ("Italy", softStiple, 83),
|
||||
new BarSeries.Bar ("Norway", !verticalBars?mediumStiple:softStiple, 83),
|
||||
new BarSeries.Bar ("Israel", softStiple, 82.6f),
|
||||
new BarSeries.Bar ("France", !verticalBars?mediumStiple:softStiple, 82.5f),
|
||||
new BarSeries.Bar ("Luxembourg", softStiple, 82.4f),
|
||||
new BarSeries.Bar ("Sweden", !verticalBars?mediumStiple:softStiple, 82.4f),
|
||||
new BarSeries.Bar ("Iceland", softStiple, 82.3f),
|
||||
new BarSeries.Bar ("Canada", !verticalBars?mediumStiple:softStiple, 82.2f),
|
||||
new BarSeries.Bar ("New Zealand", softStiple, 82),
|
||||
new BarSeries.Bar ("Malta", !verticalBars?mediumStiple:softStiple, 81.9f),
|
||||
new BarSeries.Bar ("Ireland", softStiple, 81.8f)
|
||||
}
|
||||
};
|
||||
|
||||
graphView.Series.Add (barSeries);
|
||||
|
||||
if (verticalBars) {
|
||||
|
||||
barSeries.Orientation = Orientation.Vertical;
|
||||
|
||||
// How much graph space each cell of the console depicts
|
||||
graphView.CellSize = new PointF (0.1f, 0.25f);
|
||||
// No axis marks since Bar will add it's own categorical marks
|
||||
graphView.AxisX.Increment = 0f;
|
||||
graphView.AxisX.Text = "Country";
|
||||
graphView.AxisX.Minimum = 0;
|
||||
|
||||
graphView.AxisY.Increment = 1f;
|
||||
graphView.AxisY.ShowLabelsEvery = 1;
|
||||
graphView.AxisY.LabelGetter = v => v.Value.ToString ("N2");
|
||||
graphView.AxisY.Minimum = 0;
|
||||
graphView.AxisY.Text = "Age";
|
||||
|
||||
// leave space for axis labels and title
|
||||
graphView.MarginBottom = 2;
|
||||
graphView.MarginLeft = 6;
|
||||
|
||||
// Start the graph at 80 years because that is where most of our data is
|
||||
graphView.ScrollOffset = new PointF (0, 80);
|
||||
|
||||
} else {
|
||||
barSeries.Orientation = Orientation.Horizontal;
|
||||
|
||||
// How much graph space each cell of the console depicts
|
||||
graphView.CellSize = new PointF (0.1f, 1f);
|
||||
// No axis marks since Bar will add it's own categorical marks
|
||||
graphView.AxisY.Increment = 0f;
|
||||
graphView.AxisY.ShowLabelsEvery = 1;
|
||||
graphView.AxisY.Text = "Country";
|
||||
graphView.AxisY.Minimum = 0;
|
||||
|
||||
graphView.AxisX.Increment = 1f;
|
||||
graphView.AxisX.ShowLabelsEvery = 1;
|
||||
graphView.AxisX.LabelGetter = v => v.Value.ToString ("N2");
|
||||
graphView.AxisX.Text = "Age";
|
||||
graphView.AxisX.Minimum = 0;
|
||||
|
||||
// leave space for axis labels and title
|
||||
graphView.MarginBottom = 2;
|
||||
graphView.MarginLeft = (uint)barSeries.Bars.Max (b => b.Text.Length) + 2;
|
||||
|
||||
// Start the graph at 80 years because that is where most of our data is
|
||||
graphView.ScrollOffset = new PointF (80, 0);
|
||||
}
|
||||
|
||||
graphView.SetNeedsDisplay ();
|
||||
}
|
||||
|
||||
private void SetupPopulationPyramid ()
|
||||
{
|
||||
/*
|
||||
Age,M,F
|
||||
0-4,2009363,1915127
|
||||
5-9,2108550,2011016
|
||||
10-14,2022370,1933970
|
||||
15-19,1880611,1805522
|
||||
20-24,2072674,2001966
|
||||
25-29,2275138,2208929
|
||||
30-34,2361054,2345774
|
||||
35-39,2279836,2308360
|
||||
40-44,2148253,2159877
|
||||
45-49,2128343,2167778
|
||||
50-54,2281421,2353119
|
||||
55-59,2232388,2306537
|
||||
60-64,1919839,1985177
|
||||
65-69,1647391,1734370
|
||||
70-74,1624635,1763853
|
||||
75-79,1137438,1304709
|
||||
80-84,766956,969611
|
||||
85-89,438663,638892
|
||||
90-94,169952,320625
|
||||
95-99,34524,95559
|
||||
100+,3016,12818*/
|
||||
|
||||
about.Text = "This graph shows population of each age divided by gender";
|
||||
|
||||
graphView.Reset ();
|
||||
|
||||
// How much graph space each cell of the console depicts
|
||||
graphView.CellSize = new PointF (100_000, 1);
|
||||
|
||||
//center the x axis in middle of screen to show both sides
|
||||
graphView.ScrollOffset = new PointF (-3_000_000, 0);
|
||||
|
||||
graphView.AxisX.Text = "Number Of People";
|
||||
graphView.AxisX.Increment = 500_000;
|
||||
graphView.AxisX.ShowLabelsEvery = 2;
|
||||
|
||||
// use Abs to make negative axis labels positive
|
||||
graphView.AxisX.LabelGetter = (v) => Math.Abs (v.Value / 1_000_000).ToString ("N2") + "M";
|
||||
|
||||
// leave space for axis labels
|
||||
graphView.MarginBottom = 2;
|
||||
graphView.MarginLeft = 1;
|
||||
|
||||
// do not show axis titles (bars have their own categories)
|
||||
graphView.AxisY.Increment = 0;
|
||||
graphView.AxisY.ShowLabelsEvery = 0;
|
||||
graphView.AxisY.Minimum = 0;
|
||||
|
||||
var stiple = new GraphCellToRender (Application.Driver.Stipple);
|
||||
|
||||
// Bars in 2 directions
|
||||
|
||||
// Males (negative to make the bars go left)
|
||||
var malesSeries = new BarSeries () {
|
||||
Orientation = Orientation.Horizontal,
|
||||
Bars = new List<BarSeries.Bar> ()
|
||||
{
|
||||
new BarSeries.Bar("0-4",stiple,-2009363),
|
||||
new BarSeries.Bar("5-9",stiple,-2108550),
|
||||
new BarSeries.Bar("10-14",stiple,-2022370),
|
||||
new BarSeries.Bar("15-19",stiple,-1880611),
|
||||
new BarSeries.Bar("20-24",stiple,-2072674),
|
||||
new BarSeries.Bar("25-29",stiple,-2275138),
|
||||
new BarSeries.Bar("30-34",stiple,-2361054),
|
||||
new BarSeries.Bar("35-39",stiple,-2279836),
|
||||
new BarSeries.Bar("40-44",stiple,-2148253),
|
||||
new BarSeries.Bar("45-49",stiple,-2128343),
|
||||
new BarSeries.Bar("50-54",stiple,-2281421),
|
||||
new BarSeries.Bar("55-59",stiple,-2232388),
|
||||
new BarSeries.Bar("60-64",stiple,-1919839),
|
||||
new BarSeries.Bar("65-69",stiple,-1647391),
|
||||
new BarSeries.Bar("70-74",stiple,-1624635),
|
||||
new BarSeries.Bar("75-79",stiple,-1137438),
|
||||
new BarSeries.Bar("80-84",stiple,-766956),
|
||||
new BarSeries.Bar("85-89",stiple,-438663),
|
||||
new BarSeries.Bar("90-94",stiple,-169952),
|
||||
new BarSeries.Bar("95-99",stiple,-34524),
|
||||
new BarSeries.Bar("100+",stiple,-3016)
|
||||
|
||||
}
|
||||
};
|
||||
graphView.Series.Add (malesSeries);
|
||||
|
||||
|
||||
// Females
|
||||
var femalesSeries = new BarSeries () {
|
||||
Orientation = Orientation.Horizontal,
|
||||
Bars = new List<BarSeries.Bar> ()
|
||||
{
|
||||
new BarSeries.Bar("0-4",stiple,1915127),
|
||||
new BarSeries.Bar("5-9",stiple,2011016),
|
||||
new BarSeries.Bar("10-14",stiple,1933970),
|
||||
new BarSeries.Bar("15-19",stiple,1805522),
|
||||
new BarSeries.Bar("20-24",stiple,2001966),
|
||||
new BarSeries.Bar("25-29",stiple,2208929),
|
||||
new BarSeries.Bar("30-34",stiple,2345774),
|
||||
new BarSeries.Bar("35-39",stiple,2308360),
|
||||
new BarSeries.Bar("40-44",stiple,2159877),
|
||||
new BarSeries.Bar("45-49",stiple,2167778),
|
||||
new BarSeries.Bar("50-54",stiple,2353119),
|
||||
new BarSeries.Bar("55-59",stiple,2306537),
|
||||
new BarSeries.Bar("60-64",stiple,1985177),
|
||||
new BarSeries.Bar("65-69",stiple,1734370),
|
||||
new BarSeries.Bar("70-74",stiple,1763853),
|
||||
new BarSeries.Bar("75-79",stiple,1304709),
|
||||
new BarSeries.Bar("80-84",stiple,969611),
|
||||
new BarSeries.Bar("85-89",stiple,638892),
|
||||
new BarSeries.Bar("90-94",stiple,320625),
|
||||
new BarSeries.Bar("95-99",stiple,95559),
|
||||
new BarSeries.Bar("100+",stiple,12818)
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
var softStiple = new GraphCellToRender ('\u2591');
|
||||
var mediumStiple = new GraphCellToRender ('\u2592');
|
||||
|
||||
for (int i = 0; i < malesSeries.Bars.Count; i++) {
|
||||
malesSeries.Bars [i].Fill = i % 2 == 0 ? softStiple : mediumStiple;
|
||||
femalesSeries.Bars [i].Fill = i % 2 == 0 ? softStiple : mediumStiple;
|
||||
}
|
||||
|
||||
graphView.Series.Add (femalesSeries);
|
||||
|
||||
graphView.Annotations.Add (new TextAnnotation () { Text = "M", ScreenPosition = new Terminal.Gui.Point (0, 10) });
|
||||
graphView.Annotations.Add (new TextAnnotation () { Text = "F", ScreenPosition = new Terminal.Gui.Point (graphView.Bounds.Width - 1, 10) });
|
||||
|
||||
graphView.SetNeedsDisplay ();
|
||||
|
||||
}
|
||||
|
||||
class DiscoBarSeries : BarSeries {
|
||||
private Terminal.Gui.Attribute green;
|
||||
private Terminal.Gui.Attribute brightgreen;
|
||||
private Terminal.Gui.Attribute brightyellow;
|
||||
private Terminal.Gui.Attribute red;
|
||||
private Terminal.Gui.Attribute brightred;
|
||||
|
||||
public DiscoBarSeries ()
|
||||
{
|
||||
|
||||
green = Application.Driver.MakeAttribute (Color.BrightGreen, Color.Black);
|
||||
brightgreen = Application.Driver.MakeAttribute (Color.Green, Color.Black);
|
||||
brightyellow = Application.Driver.MakeAttribute (Color.BrightYellow, Color.Black);
|
||||
red = Application.Driver.MakeAttribute (Color.Red, Color.Black);
|
||||
brightred = Application.Driver.MakeAttribute (Color.BrightRed, Color.Black);
|
||||
}
|
||||
protected override void DrawBarLine (GraphView graph, Terminal.Gui.Point start, Terminal.Gui.Point end, Bar beingDrawn)
|
||||
{
|
||||
var driver = Application.Driver;
|
||||
|
||||
int x = start.X;
|
||||
for(int y = end.Y; y <= start.Y; y++) {
|
||||
|
||||
var height = graph.ScreenToGraphSpace (x, y).Y;
|
||||
|
||||
if (height >= 85) {
|
||||
driver.SetAttribute(red);
|
||||
}
|
||||
else
|
||||
if (height >= 66) {
|
||||
driver.SetAttribute (brightred);
|
||||
}
|
||||
else
|
||||
if (height >= 45) {
|
||||
driver.SetAttribute (brightyellow);
|
||||
}
|
||||
else
|
||||
if (height >= 25) {
|
||||
driver.SetAttribute (brightgreen);
|
||||
}
|
||||
else{
|
||||
driver.SetAttribute (green);
|
||||
}
|
||||
|
||||
graph.AddRune (x, y, beingDrawn.Fill.Rune);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SetupDisco ()
|
||||
{
|
||||
graphView.Reset ();
|
||||
|
||||
about.Text = "This graph shows a graphic equaliser for an imaginary song";
|
||||
|
||||
graphView.GraphColor = Application.Driver.MakeAttribute (Color.White, Color.Black);
|
||||
|
||||
var stiple = new GraphCellToRender ('\u2593');
|
||||
|
||||
Random r = new Random ();
|
||||
var series = new DiscoBarSeries ();
|
||||
var bars = new List<BarSeries.Bar> ();
|
||||
|
||||
Func<MainLoop, bool> genSample = (l) => {
|
||||
|
||||
bars.Clear ();
|
||||
// generate an imaginary sample
|
||||
for (int i = 0; i < 31; i++) {
|
||||
bars.Add (
|
||||
new BarSeries.Bar (null, stiple, r.Next (0, 100)) {
|
||||
//ColorGetter = colorDelegate
|
||||
});
|
||||
}
|
||||
graphView.SetNeedsDisplay ();
|
||||
|
||||
|
||||
// while the equaliser is showing
|
||||
return graphView.Series.Contains (series);
|
||||
};
|
||||
|
||||
Application.MainLoop.AddTimeout (TimeSpan.FromMilliseconds (250), genSample);
|
||||
|
||||
series.Bars = bars;
|
||||
|
||||
graphView.Series.Add (series);
|
||||
|
||||
// How much graph space each cell of the console depicts
|
||||
graphView.CellSize = new PointF (1, 10);
|
||||
graphView.AxisX.Increment = 0; // No graph ticks
|
||||
graphView.AxisX.ShowLabelsEvery = 0; // no labels
|
||||
|
||||
graphView.AxisX.Visible = false;
|
||||
graphView.AxisY.Visible = false;
|
||||
|
||||
graphView.SetNeedsDisplay ();
|
||||
}
|
||||
private void SetupPeriodicTableScatterPlot ()
|
||||
{
|
||||
graphView.Reset ();
|
||||
|
||||
about.Text = "This graph shows the atomic weight of each element in the periodic table.\nStarting with Hydrogen (atomic Number 1 with a weight of 1.007)";
|
||||
|
||||
//AtomicNumber and AtomicMass of all elements in the periodic table
|
||||
graphView.Series.Add (
|
||||
new ScatterSeries () {
|
||||
Points = new List<PointF>{
|
||||
new PointF(1,1.007f),new PointF(2,4.002f),new PointF(3,6.941f),new PointF(4,9.012f),new PointF(5,10.811f),new PointF(6,12.011f),
|
||||
new PointF(7,14.007f),new PointF(8,15.999f),new PointF(9,18.998f),new PointF(10,20.18f),new PointF(11,22.99f),new PointF(12,24.305f),
|
||||
new PointF(13,26.982f),new PointF(14,28.086f),new PointF(15,30.974f),new PointF(16,32.065f),new PointF(17,35.453f),new PointF(18,39.948f),
|
||||
new PointF(19,39.098f),new PointF(20,40.078f),new PointF(21,44.956f),new PointF(22,47.867f),new PointF(23,50.942f),new PointF(24,51.996f),
|
||||
new PointF(25,54.938f),new PointF(26,55.845f),new PointF(27,58.933f),new PointF(28,58.693f),new PointF(29,63.546f),new PointF(30,65.38f),
|
||||
new PointF(31,69.723f),new PointF(32,72.64f),new PointF(33,74.922f),new PointF(34,78.96f),new PointF(35,79.904f),new PointF(36,83.798f),
|
||||
new PointF(37,85.468f),new PointF(38,87.62f),new PointF(39,88.906f),new PointF(40,91.224f),new PointF(41,92.906f),new PointF(42,95.96f),
|
||||
new PointF(43,98f),new PointF(44,101.07f),new PointF(45,102.906f),new PointF(46,106.42f),new PointF(47,107.868f),new PointF(48,112.411f),
|
||||
new PointF(49,114.818f),new PointF(50,118.71f),new PointF(51,121.76f),new PointF(52,127.6f),new PointF(53,126.904f),new PointF(54,131.293f),
|
||||
new PointF(55,132.905f),new PointF(56,137.327f),new PointF(57,138.905f),new PointF(58,140.116f),new PointF(59,140.908f),new PointF(60,144.242f),
|
||||
new PointF(61,145),new PointF(62,150.36f),new PointF(63,151.964f),new PointF(64,157.25f),new PointF(65,158.925f),new PointF(66,162.5f),
|
||||
new PointF(67,164.93f),new PointF(68,167.259f),new PointF(69,168.934f),new PointF(70,173.054f),new PointF(71,174.967f),new PointF(72,178.49f),
|
||||
new PointF(73,180.948f),new PointF(74,183.84f),new PointF(75,186.207f),new PointF(76,190.23f),new PointF(77,192.217f),new PointF(78,195.084f),
|
||||
new PointF(79,196.967f),new PointF(80,200.59f),new PointF(81,204.383f),new PointF(82,207.2f),new PointF(83,208.98f),new PointF(84,210),
|
||||
new PointF(85,210),new PointF(86,222),new PointF(87,223),new PointF(88,226),new PointF(89,227),new PointF(90,232.038f),new PointF(91,231.036f),
|
||||
new PointF(92,238.029f),new PointF(93,237),new PointF(94,244),new PointF(95,243),new PointF(96,247),new PointF(97,247),new PointF(98,251),
|
||||
new PointF(99,252),new PointF(100,257),new PointF(101,258),new PointF(102,259),new PointF(103,262),new PointF(104,261),new PointF(105,262),
|
||||
new PointF(106,266),new PointF(107,264),new PointF(108,267),new PointF(109,268),new PointF(113,284),new PointF(114,289),new PointF(115,288),
|
||||
new PointF(116,292),new PointF(117,295),new PointF(118,294)
|
||||
}
|
||||
});
|
||||
|
||||
// How much graph space each cell of the console depicts
|
||||
graphView.CellSize = new PointF (1, 5);
|
||||
|
||||
// leave space for axis labels
|
||||
graphView.MarginBottom = 2;
|
||||
graphView.MarginLeft = 3;
|
||||
|
||||
// One axis tick/label per 5 atomic numbers
|
||||
graphView.AxisX.Increment = 5;
|
||||
graphView.AxisX.ShowLabelsEvery = 1;
|
||||
graphView.AxisX.Text = "Atomic Number";
|
||||
graphView.AxisX.Minimum = 0;
|
||||
|
||||
// One label every 5 atomic weight
|
||||
graphView.AxisY.Increment = 5;
|
||||
graphView.AxisY.ShowLabelsEvery = 1;
|
||||
graphView.AxisY.Minimum = 0;
|
||||
|
||||
graphView.SetNeedsDisplay ();
|
||||
}
|
||||
|
||||
private void Zoom (float factor)
|
||||
{
|
||||
graphView.CellSize = new PointF (
|
||||
graphView.CellSize.X * factor,
|
||||
graphView.CellSize.Y * factor
|
||||
);
|
||||
|
||||
graphView.AxisX.Increment *= factor;
|
||||
graphView.AxisY.Increment *= factor;
|
||||
|
||||
graphView.SetNeedsDisplay ();
|
||||
}
|
||||
|
||||
private void Quit ()
|
||||
{
|
||||
Application.RequestStop ();
|
||||
}
|
||||
}
|
||||
}
|
||||
1307
UnitTests/GraphViewTests.cs
Normal file
1307
UnitTests/GraphViewTests.cs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -8,6 +8,7 @@ using Xunit;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Terminal.Gui.Views {
|
||||
|
||||
public class TabViewTests {
|
||||
private TabView GetTabView ()
|
||||
{
|
||||
|
||||
@@ -8,6 +8,7 @@ using Xunit;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Terminal.Gui.Views {
|
||||
|
||||
public class TableViewTests
|
||||
{
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ using Terminal.Gui.Trees;
|
||||
using Xunit;
|
||||
|
||||
namespace Terminal.Gui.Views {
|
||||
|
||||
public class TreeViewTests {
|
||||
#region Test Setup Methods
|
||||
class Factory {
|
||||
|
||||
Reference in New Issue
Block a user