2
0
Эх сурвалжийг харах

Fixes #2981. LegendAnnotation: 'Frame.DrawFrame(Rect, bool)' is obsolete: 'This method is obsolete in v2. Use use LineCanvas or Frame (#2982)

* Fixes #2981. LegendAnnotation: 'Frame.DrawFrame(Rect, bool)' is obsolete: 'This method is obsolete in v2. Use use LineCanvas or Frame

* Fixes #2983. View need a alternative DrawFrame for the v2.

* Use new DrawFrame method.

* Add a view for the legend annotations.

* Prefix with underscore.

* Change LegendAnnotation class to derived from View.

* The Bounds isn't needed, it's enough to set the Pos/Dim.

* Fix unit test to differentiate Bounds from Frame.

* Add DrawIncompleteFrame method and unit tests.

* Add more unit tests to LineCanvas.

* Fix newline conflict errors.

* Add DrawIncompleteFrame method and unit tests.

* Add more unit tests to LineCanvas.

* Fix newline conflict errors.

* I will never rely on zero-location-based unit test again.

* Fix TestTreeViewColor unit test fail.

* Add BorderStyle option to the menu.

* Revert "I will never rely on zero-location-based unit test again."

This reverts commit 62adf6f2850b0fb9e83782cee8c47a1202a3031e.

* Revert "Fix newline conflict errors."

This reverts commit 4acf72612dff0ba907f20c7f8826253d73c102c8.

* Revert "Add more unit tests to LineCanvas."

This reverts commit 66bc6f514e88ae9844c5608f89d7368938ed2486.

* Revert "Add DrawIncompleteFrame method and unit tests."

This reverts commit 680ba264e16b42e2261e697dca9aa54761feae1e.

* Revert "Fixes #2983. View need a alternative DrawFrame for the v2."

This reverts commit dade9fd767ce3aacc91fa13d0b4537d233de0a49.

* Removed resharper settings from editorconfig

* Rename to OnDrawAdornments.

* Added diagnostics as a double-check. Code cleanup. API doc improvements.

* Fix typo for retest again.

* Increase the graph size.

---------

Co-authored-by: Tig <[email protected]>
Co-authored-by: Tig Kindel <[email protected]>
BDisp 1 жил өмнө
parent
commit
17e7673cd3

+ 1 - 1
Terminal.Gui/View/Adornment/Border.cs

@@ -77,7 +77,7 @@ public class Border : Adornment {
 	/// setting the <see cref="Thickness"/> to <c>(1,1,1,1)</c> and setting the line style of the
 	/// views that comprise the border. If set to <see cref="LineStyle.None"/> no border will be drawn.
 	/// </summary>
-	public new LineStyle LineStyle {
+	public LineStyle LineStyle {
 		get {
 			if (_lineStyle.HasValue) {
 				return _lineStyle.Value;

+ 1 - 1
Terminal.Gui/View/Layout/ViewLayout.cs

@@ -211,7 +211,7 @@ public partial class View {
 	/// This internal method is overridden by Adornment to do nothing to prevent recursion during View construction.
 	/// And, because Adornments don't have Adornments. It's internal to support unit tests.
 	/// </summary>
-	/// <param name="adornment"></param>
+	/// <param name="adornmentType"></param>
 	/// <exception cref="ArgumentNullException"></exception>
 	/// <exception cref="ArgumentException"></exception>
 	internal virtual Adornment CreateAdornment (Type adornmentType)

+ 30 - 37
Terminal.Gui/Views/GraphView/Annotations.cs

@@ -114,62 +114,55 @@ namespace Terminal.Gui {
 	/// A box containing symbol definitions e.g. meanings for colors in a graph.
 	/// The 'Key' to the graph
 	/// </summary>
-	public class LegendAnnotation : IAnnotation {
-
+	public class LegendAnnotation : View, 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
+		/// Returns false i.e. Legends render after series
 		/// </summary>
-		public bool Border { get; set; } = true;
+		public bool BeforeSeries => false;
 
 		/// <summary>
-		/// Defines the screen area available for the legend to render in
+		/// Ordered collection of entries that are rendered in the legend.
 		/// </summary>
-		public Rect Bounds { get; set; }
+		List<Tuple<GraphCellToRender, string>> _entries = new List<Tuple<GraphCellToRender, string>> ();
 
 		/// <summary>
-		/// Returns false i.e. Lengends render after series
+		/// Creates a new empty legend at the empty screen coordinates.
 		/// </summary>
-		public bool BeforeSeries => false;
+		public LegendAnnotation () : this (Rect.Empty) { }
 
 		/// <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
+		/// 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;
+			X = legendBounds.X;
+			Y = legendBounds.Y;
+			Width = legendBounds.Width;
+			Height = legendBounds.Height;
+			BorderStyle = LineStyle.Single;
 		}
 
 		/// <summary>
-		/// Draws the Legend and all entries into the area within <see cref="Bounds"/>
+		/// Draws the Legend and all entries into the area within <see cref="View.Bounds"/>
 		/// </summary>
 		/// <param name="graph"></param>
 		public void Render (GraphView graph)
 		{
-			if (Border) {
-				graph.Border.DrawFrame (Bounds, true);
+			if (!IsInitialized) {
+				ColorScheme = new ColorScheme () { Normal = Application.Driver.GetAttribute () };
+				graph.Add (this);
 			}
 
-			// 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);
+			if (BorderStyle != LineStyle.None) {
+				OnDrawAdornments ();
+				OnRenderLineCanvas ();
+			}
 
 			int linesDrawn = 0;
 
-			foreach (var entry in entries) {
+			foreach (var entry in _entries) {
 
 				if (entry.Item1.Color.HasValue) {
 					Application.Driver.SetAttribute (entry.Item1.Color.Value);
@@ -178,35 +171,35 @@ namespace Terminal.Gui {
 				}
 
 				// add the symbol
-				graph.AddRune (x, y + linesDrawn, entry.Item1.Rune);
+				AddRune (0, linesDrawn, entry.Item1.Rune);
 
 				// switch to normal coloring (for the text)
 				graph.SetDriverColorToGraphColor ();
 
 				// add the text
-				graph.Move (x + 1, y + linesDrawn);
+				Move (1, linesDrawn);
 
-				string str = TextFormatter.ClipOrPad (entry.Item2, availableWidth - 1);
+				string str = TextFormatter.ClipOrPad (entry.Item2, Bounds.Width - 1);
 				Application.Driver.AddStr (str);
 
 				linesDrawn++;
-
+				
 				// Legend has run out of space
-				if (linesDrawn >= availableHeight) {
+				if (linesDrawn >= Bounds.Height) {
 					break;
 				}
 			}
 		}
 
 		/// <summary>
-		/// Adds an entry into the legend.  Duplicate entries are permissable
+		/// Adds an entry into the legend.  Duplicate entries are permissible
 		/// </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>
+		/// if outside of Legend <see cref="View.Bounds"/></param>
 		public void AddEntry (GraphCellToRender graphCellToRender, string text)
 		{
-			entries.Add (Tuple.Create (graphCellToRender, text));
+			_entries.Add (Tuple.Create (graphCellToRender, text));
 		}
 	}
 

+ 280 - 271
Terminal.Gui/Views/GraphView/GraphView.cs

@@ -1,321 +1,330 @@
-using System.Text;
+#nullable enable
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using System.Text;
+
+namespace Terminal.Gui; 
+
+/// <summary>
+/// View for rendering graphs (bar, scatter, etc...).
+/// </summary>
+public class GraphView : View {
 
-#nullable enable
-namespace Terminal.Gui {
 	/// <summary>
-	/// Control for rendering graphs (bar, scatter etc)
+	/// Creates a new graph with a 1 to 1 graph space with absolute layout.
 	/// </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 ();
-
-			// Things this view knows how to do
-			AddCommand (Command.ScrollUp, () => { Scroll (0, CellSize.Y); return true; });
-			AddCommand (Command.ScrollDown, () => { Scroll (0, -CellSize.Y); return true; });
-			AddCommand (Command.ScrollRight, () => { Scroll (CellSize.X, 0); return true; });
-			AddCommand (Command.ScrollLeft, () => { Scroll (-CellSize.X, 0); return true; });
-			AddCommand (Command.PageUp, () => { PageUp (); return true; });
-			AddCommand (Command.PageDown, () => { PageDown (); return true; });
-
-			KeyBindings.Add (KeyCode.CursorRight, Command.ScrollRight);
-			KeyBindings.Add (KeyCode.CursorLeft, Command.ScrollLeft);
-			KeyBindings.Add (KeyCode.CursorUp, Command.ScrollUp);
-			KeyBindings.Add (KeyCode.CursorDown, Command.ScrollDown);
-
-			// Not bound by default (preserves backwards compatibility)
-			//KeyBindings.Add (Key.PageUp, Command.PageUp);
-			//KeyBindings.Add (Key.PageDown, Command.PageDown);
-		}
+	public GraphView ()
+	{
+		CanFocus = true;
+
+		AxisX = new HorizontalAxis ();
+		AxisY = new VerticalAxis ();
+
+		// Things this view knows how to do
+		AddCommand (Command.ScrollUp, () => {
+			Scroll (0, CellSize.Y);
+			return true;
+		});
+		AddCommand (Command.ScrollDown, () => {
+			Scroll (0, -CellSize.Y);
+			return true;
+		});
+		AddCommand (Command.ScrollRight, () => {
+			Scroll (CellSize.X, 0);
+			return true;
+		});
+		AddCommand (Command.ScrollLeft, () => {
+			Scroll (-CellSize.X, 0);
+			return true;
+		});
+		AddCommand (Command.PageUp, () => {
+			PageUp ();
+			return true;
+		});
+		AddCommand (Command.PageDown, () => {
+			PageDown ();
+			return true;
+		});
+
+		KeyBindings.Add (KeyCode.CursorRight, Command.ScrollRight);
+		KeyBindings.Add (KeyCode.CursorLeft, Command.ScrollLeft);
+		KeyBindings.Add (KeyCode.CursorUp, Command.ScrollUp);
+		KeyBindings.Add (KeyCode.CursorDown, Command.ScrollDown);
+
+		// Not bound by default (preserves backwards compatibility)
+		//KeyBindings.Add (Key.PageUp, Command.PageUp);
+		//KeyBindings.Add (Key.PageDown, Command.PageDown);
+	}
 
-		/// <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 ();
-		}
+	/// <summary>
+	/// Horizontal axis.
+	/// </summary>
+	/// <value></value>
+	public HorizontalAxis AxisX { get; set; }
 
-		///<inheritdoc/>
-		public override void OnDrawContent (Rect contentArea)
-		{
-			if (CellSize.X == 0 || CellSize.Y == 0) {
-				throw new Exception ($"{nameof (CellSize)} cannot be 0");
-			}
+	/// <summary>
+	/// Vertical axis.
+	/// </summary>
+	/// <value></value>
+	public VerticalAxis AxisY { get; set; }
 
-			SetDriverColorToGraphColor ();
+	/// <summary>
+	/// Collection of data series that are rendered in the graph.
+	/// </summary>
+	public List<ISeries> Series { get; } = new ();
 
-			Move (0, 0);
+	/// <summary>
+	/// Elements drawn into graph after series have been drawn e.g. Legends etc.
+	/// </summary>
+	public List<IAnnotation> Annotations { get; } = new ();
 
-			// clear all old content
-			for (int i = 0; i < Bounds.Height; i++) {
-				Move (0, i);
-				Driver.AddStr (new string (' ', Bounds.Width));
-			}
+	/// <summary>
+	/// Amount of space to leave on left of the graph. Graph content (<see cref="Series"/>)
+	/// will not be rendered in margins but axis labels may be. Use <see cref="Padding"/> to
+	/// add a margin outside of the GraphView.
+	/// </summary>
+	public uint MarginLeft { get; set; }
 
-			// If there is no data do not display a graph
-			if (!Series.Any () && !Annotations.Any ()) {
-				return;
-			}
+	/// <summary>
+	/// Amount of space to leave on bottom of the graph. Graph content (<see cref="Series"/>)
+	/// will not be rendered in margins but axis labels may be. Use <see cref="Padding"/> to
+	/// add a margin outside of the GraphView.
+	/// </summary>
+	public uint MarginBottom { get; set; }
 
-			// The drawable area of the graph (anything that isn't in the margins)
-			var graphScreenWidth = Bounds.Width - ((int)MarginLeft);
-			var graphScreenHeight = Bounds.Height - (int)MarginBottom;
+	/// <summary>
+	/// The graph space position of the bottom left of the graph.
+	/// Changing this scrolls the viewport around in the graph.
+	/// </summary>
+	/// <value></value>
+	public PointF ScrollOffset { get; set; } = new (0, 0);
 
-			// if the margins take up the full draw bounds don't render
-			if (graphScreenWidth < 0 || graphScreenHeight < 0) {
-				return;
-			}
+	/// <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 (1, 1);
 
-			// Draw 'before' annotations
-			foreach (var a in Annotations.ToArray ().Where (a => a.BeforeSeries)) {
-				a.Render (this);
-			}
+	/// <summary>
+	/// The color of the background of the graph and axis/labels.
+	/// </summary>
+	public Attribute? GraphColor { get; set; }
 
-			SetDriverColorToGraphColor ();
+	/// <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 ();
+	}
 
-			AxisY.DrawAxisLine (this);
-			AxisX.DrawAxisLine (this);
+	///<inheritdoc/>
+	public override void OnDrawContent (Rect contentArea)
+	{
+		if (CellSize.X == 0 || CellSize.Y == 0) {
+			throw new Exception ($"{nameof (CellSize)} cannot be 0");
+		}
 
-			AxisY.DrawAxisLabels (this);
-			AxisX.DrawAxisLabels (this);
+		SetDriverColorToGraphColor ();
 
-			// Draw a cross where the two axis cross
-			var axisIntersection = new Point (AxisY.GetAxisXPosition (this), AxisX.GetAxisYPosition (this));
+		Move (0, 0);
 
-			if (AxisX.Visible && AxisY.Visible) {
-				Move (axisIntersection.X, axisIntersection.Y);
-				AddRune (axisIntersection.X, axisIntersection.Y, (Rune)'\u253C');
-			}
+		// clear all old content
+		for (var i = 0; i < Bounds.Height; i++) {
+			Move (0, i);
+			Driver.AddStr (new string (' ', Bounds.Width));
+		}
 
-			SetDriverColorToGraphColor ();
+		// If there is no data do not display a graph
+		if (!Series.Any () && !Annotations.Any ()) {
+			return;
+		}
 
-			Rect drawBounds = new Rect ((int)MarginLeft, 0, graphScreenWidth, graphScreenHeight);
+		// The drawable area of the graph (anything that isn't in the margins)
+		var graphScreenWidth = Bounds.Width - (int)MarginLeft;
+		var graphScreenHeight = Bounds.Height - (int)MarginBottom;
 
-			RectangleF graphSpace = ScreenToGraphSpace (drawBounds);
+		// if the margins take up the full draw bounds don't render
+		if (graphScreenWidth < 0 || graphScreenHeight < 0) {
+			return;
+		}
 
-			foreach (var s in Series.ToArray ()) {
+		// Draw 'before' annotations
+		foreach (var a in Annotations.ToArray ().Where (a => a.BeforeSeries)) {
+			a.Render (this);
+		}
 
-				s.DrawSeries (this, drawBounds, graphSpace);
+		SetDriverColorToGraphColor ();
 
-				// If a series changes the graph color reset it
-				SetDriverColorToGraphColor ();
-			}
+		AxisY.DrawAxisLine (this);
+		AxisX.DrawAxisLine (this);
 
-			SetDriverColorToGraphColor ();
+		AxisY.DrawAxisLabels (this);
+		AxisX.DrawAxisLabels (this);
 
-			// Draw 'after' annotations
-			foreach (var a in Annotations.ToArray ().Where (a => !a.BeforeSeries)) {
-				a.Render (this);
-			}
-		}
+		// Draw a cross where the two axis cross
+		var axisIntersection = new Point (AxisY.GetAxisXPosition (this), AxisX.GetAxisYPosition (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 ?? (GetNormalColor ()));
+		if (AxisX.Visible && AxisY.Visible) {
+			Move (axisIntersection.X, axisIntersection.Y);
+			AddRune (axisIntersection.X, axisIntersection.Y, (Rune)'\u253C');
 		}
 
-		/// <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);
-		}
+		SetDriverColorToGraphColor ();
 
-		/// <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)
-				);
-		}
+		var drawBounds = new Rect ((int)MarginLeft, 0, graphScreenWidth, graphScreenHeight);
 
-		/// <inheritdoc/>
-		/// <remarks>Also ensures that cursor is invisible after entering the <see cref="GraphView"/>.</remarks>
-		public override bool OnEnter (View view)
-		{
-			Driver.SetCursorVisibility (CursorVisibility.Invisible);
-			return base.OnEnter (view);
-		}
+		var graphSpace = ScreenToGraphSpace (drawBounds);
 
-		/// <summary>
-		/// Scrolls the graph up 1 page
-		/// </summary>
-		public void PageUp ()
-		{
-			Scroll (0, CellSize.Y * Bounds.Height);
-		}
+		foreach (var s in Series.ToArray ()) {
+
+			s.DrawSeries (this, drawBounds, graphSpace);
 
-		/// <summary>
-		/// Scrolls the graph down 1 page
-		/// </summary>
-		public void PageDown ()
-		{
-			Scroll (0, -1 * CellSize.Y * Bounds.Height);
+			// If a series changes the graph color reset it
+			SetDriverColorToGraphColor ();
 		}
-		/// <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>
-		public void Scroll (float offsetX, float offsetY)
-		{
-			ScrollOffset = new PointF (
-				ScrollOffset.X + offsetX,
-				ScrollOffset.Y + offsetY);
-
-			SetNeedsDisplay ();
+
+		SetDriverColorToGraphColor ();
+
+		// Draw 'after' annotations
+		foreach (var a in Annotations.ToArray ().Where (a => !a.BeforeSeries)) {
+			a.Render (this);
 		}
+	}
 
-		#region Bresenham's line algorithm
-		// https://rosettacode.org/wiki/Bitmap/Bresenham%27s_line_algorithm#C.23
+	/// <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 ?? GetNormalColor ());
 
-		int ipart (decimal x) { return (int)x; }
+	/// <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) => new (
+		ScrollOffset.X + (col - MarginLeft) * CellSize.X,
+		ScrollOffset.Y + (Bounds.Height - (row + MarginBottom + 1)) * CellSize.Y,
+		CellSize.X, CellSize.Y);
 
-		decimal fpart (decimal x)
-		{
-			if (x < 0) return (1 - (x - Math.Floor (x)));
-			return (x - Math.Floor (x));
-		}
+	/// <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>
-		/// 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;
-			}
+	/// <summary>
+	/// Calculates the screen location for a given point in graph space.
+	/// Bear in mind these may 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 content area of the view.
+	/// </returns>
+	public Point GraphSpaceToScreen (PointF location) => new (
+		(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/>
+	/// <remarks>Also ensures that cursor is invisible after entering the <see cref="GraphView"/>.</remarks>
+	public override bool OnEnter (View view)
+	{
+		Driver.SetCursorVisibility (CursorVisibility.Invisible);
+		return base.OnEnter (view);
+	}
 
-			int x0 = start.X;
-			int y0 = start.Y;
-			int x1 = end.X;
-			int y1 = end.Y;
+	/// <summary>
+	/// Scrolls the graph up 1 page.
+	/// </summary>
+	public void PageUp () => Scroll (0, CellSize.Y * Bounds.Height);
 
-			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;
+	/// <summary>
+	/// Scrolls the graph down 1 page.
+	/// </summary>
+	public void PageDown () => Scroll (0, -1 * CellSize.Y * Bounds.Height);
 
-			while (true) {
+	/// <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>
+	public void Scroll (float offsetX, float offsetY)
+	{
+		ScrollOffset = new PointF (
+			ScrollOffset.X + offsetX,
+			ScrollOffset.Y + offsetY);
+
+		SetNeedsDisplay ();
+	}
 
-				AddRune (x0, y0, symbol);
+	#region Bresenham's line algorithm
+	// https://rosettacode.org/wiki/Bitmap/Bresenham%27s_line_algorithm#C.23
 
-				if (x0 == x1 && y0 == y1) break;
-				e2 = err;
-				if (e2 > -dx) { err -= dy; x0 += sx; }
-				if (e2 < dy) { err += dx; y0 += sy; }
-			}
+	/// <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;
 		}
 
-		#endregion
+		var x0 = start.X;
+		var y0 = start.Y;
+		var x1 = end.X;
+		var 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
 }

+ 565 - 519
UICatalog/Scenarios/GraphViewExample.cs

@@ -3,263 +3,304 @@ using System.Collections.Generic;
 using System.Linq;
 using System.Text;
 using Terminal.Gui;
-using Color = Terminal.Gui.Color;
 
-namespace UICatalog.Scenarios {
+namespace UICatalog.Scenarios;
+
+[ScenarioMetadata ("Graph View", "Demos the GraphView control.")]
+[ScenarioCategory ("Controls")]
+[ScenarioCategory ("Drawing")]
+public class GraphViewExample : Scenario {
+	TextView _about;
+
+	int _currentGraph;
+	Action [] _graphs;
+
+	GraphView _graphView;
+	MenuItem _miDiags;
+	MenuItem _miShowBorder;
+
+	public override void Setup ()
+	{
+		Win.Title = GetName ();
+		Win.Y = 1; // menu
+		Win.Height = Dim.Fill (1); // status bar
+
+		_graphs = new [] {
+			() => 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 ("_File", new MenuItem [] {
+				new ("Scatter _Plot", "", () => _graphs [_currentGraph = 0] ()),
+				new ("_V Bar Graph", "", () => _graphs [_currentGraph = 1] ()),
+				new ("_H Bar Graph", "", () => _graphs [_currentGraph = 2] ()),
+				new ("P_opulation Pyramid", "", () => _graphs [_currentGraph = 3] ()),
+				new ("_Line Graph", "", () => _graphs [_currentGraph = 4] ()),
+				new ("Sine _Wave", "", () => _graphs [_currentGraph = 5] ()),
+				new ("Silent _Disco", "", () => _graphs [_currentGraph = 6] ()),
+				new ("_Multi Bar Graph", "", () => _graphs [_currentGraph = 7] ()),
+				new ("_Quit", "", () => Quit ())
+			}),
+			new ("_View", new [] {
+				new ("Zoom _In", "", () => Zoom (0.5f)),
+				new ("Zoom _Out", "", () => Zoom (2f)),
+				new ("MarginLeft++", "", () => Margin (true, true)),
+				new ("MarginLeft--", "", () => Margin (true, false)),
+				new ("MarginBottom++", "", () => Margin (false, true)),
+				new ("MarginBottom--", "", () => Margin (false, false)),
+				_miShowBorder = new MenuItem ("_Enable Margin, Border, and Padding", "", () => ShowBorder ()) {
+					Checked = true,
+					CheckType = MenuItemCheckStyle.Checked
+				},
+				_miDiags = new MenuItem ("Dri_ver Diagnostics", "", () => EnableDiagnostics ()) {
+					Checked = false,
+					CheckType = MenuItemCheckStyle.Checked
+				}
+			})
+
+		});
+		Application.Top.Add (menu);
+
+		_graphView = new GraphView {
+			X = 0,
+			Y = 0,
+			Width = 60,
+			Height = Dim.Fill (),
+			BorderStyle = LineStyle.Single
+		};
+
+		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 (Application.QuitKey, $"{Application.QuitKey} to Quit", () => Quit ()),
+			new (KeyCode.CtrlMask | KeyCode.G, "~^G~ Next", () => _graphs [_currentGraph++ % _graphs.Length] ())
+		});
+		Application.Top.Add (statusBar);
+	}
 
-	[ScenarioMetadata (Name: "Graph View", Description: "Demos the GraphView control.")]
-	[ScenarioCategory ("Controls")]
-	[ScenarioCategory ("Drawing")]
-	public class GraphViewExample : Scenario {
+	void ShowBorder ()
+	{
+		_miShowBorder.Checked = !_miShowBorder.Checked;
+
+		if (_miShowBorder.Checked == true) {
+			_graphView.BorderStyle = LineStyle.Single;
+			_graphView.Border.Thickness = new Thickness (1, 3, 1, 1);
+			_graphView.Margin.Thickness = new Thickness (2, 2, 2, 2);
+			_graphView.Padding.Thickness = new Thickness (2, 2, 2, 2);
+		} else {
+			_graphView.BorderStyle = LineStyle.None;
+			_graphView.Margin.Thickness = Thickness.Empty;
+			_graphView.Padding.Thickness = Thickness.Empty;
+		}
 
-		GraphView graphView;
-		private TextView about;
+	}
 
-		int currentGraph = 0;
-		Action [] graphs;
+	void EnableDiagnostics ()
+	{
+		_miDiags.Checked = !_miDiags.Checked;
 
-		public override void Setup ()
-		{
-			Win.Title = this.GetName ();
-			Win.Y = 1; // menu
-			Win.Height = Dim.Fill (1); // status bar
-
-			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)),
-					new MenuItem ("MarginLeft++", "", () => Margin(true,true)),
-					new MenuItem ("MarginLeft--", "", () => Margin(true,false)),
-					new MenuItem ("MarginBottom++", "", () => Margin(false,true)),
-					new MenuItem ("MarginBottom--", "", () => Margin(false,false)),
-				}),
-
-				});
-			Application.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 ()
-			});
+		ConsoleDriver.Diagnostics = _miDiags.Checked == true ? ConsoleDriver.DiagnosticFlags.FramePadding | ConsoleDriver.DiagnosticFlags.FrameRuler : ConsoleDriver.DiagnosticFlags.Off;
+		Application.Refresh ();
+	}
 
-			Win.Add (frameRight);
+	void MultiBarGraph ()
+	{
+		_graphView.Reset ();
 
-			var statusBar = new StatusBar (new StatusItem [] {
-				new StatusItem(Application.QuitKey, $"{Application.QuitKey} to Quit", () => Quit()),
-				new StatusItem(KeyCode.CtrlMask | KeyCode.G, "~^G~ Next", ()=>graphs[currentGraph++%graphs.Length]()),
-			});
-			Application.Top.Add (statusBar);
-		}
+		_graphView.Title = "Multi Bar";
 
-		private void MultiBarGraph ()
-		{
-			graphView.Reset ();
+		_about.Text = "Housing Expenditures by income thirds 1996-2003";
 
-			about.Text = "Housing Expenditures by income thirds 1996-2003";
+		var fore = _graphView.ColorScheme.Normal.Foreground == new Color (ColorName.Black) ? new Color (ColorName.White) : _graphView.ColorScheme.Normal.Foreground;
+		var black = new Attribute (fore, Color.Black);
+		var cyan = new Attribute (Color.BrightCyan, Color.Black);
+		var magenta = new Attribute (Color.BrightMagenta, Color.Black);
+		var red = new Attribute (Color.BrightRed, Color.Black);
 
-			var fore = graphView.ColorScheme.Normal.Foreground == new Color(ColorName.Black) ? new Color(ColorName.White) : graphView.ColorScheme.Normal.Foreground;
-			var black = new Attribute (fore, Color.Black);
-			var cyan = new Attribute (Color.BrightCyan, Color.Black);
-			var magenta = new Attribute (Color.BrightMagenta, Color.Black);
-			var red = new Attribute (Color.BrightRed, Color.Black);
+		_graphView.GraphColor = black;
 
-			graphView.GraphColor = black;
+		var series = new MultiBarSeries (3, 1, 0.25f, new [] { magenta, cyan, red });
 
-			var series = new MultiBarSeries (3, 1, 0.25f, new [] { magenta, cyan, red });
+		var stiple = CM.Glyphs.Stipple;
 
-			var stiple = CM.Glyphs.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);
 
-			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.CellSize = new PointF (0.25f, 1000);
-			graphView.Series.Add (series);
-			graphView.SetNeedsDisplay ();
+		_graphView.MarginLeft = 3;
+		_graphView.MarginBottom = 1;
 
-			graphView.MarginLeft = 3;
-			graphView.MarginBottom = 1;
+		_graphView.AxisY.LabelGetter = v => '$' + (v.Value / 1000f).ToString ("N0") + 'k';
 
-			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;
 
-			// 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;
 
-			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);
+	}
 
-			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);
-		}
+	void SetupLineGraph ()
+	{
+		_graphView.Reset ();
 
-		private void SetupLineGraph ()
-		{
-			graphView.Reset ();
+		_graphView.Title = "Line";
 
-			about.Text = "This graph shows random points";
+		_about.Text = "This graph shows random points";
 
-			var black = new Attribute (graphView.ColorScheme.Normal.Foreground, Color.Black);
-			var cyan = new Attribute (Color.BrightCyan, Color.Black);
-			var magenta = new Attribute (Color.BrightMagenta, Color.Black);
-			var red = new Attribute (Color.BrightRed, Color.Black);
+		var black = new Attribute (_graphView.ColorScheme.Normal.Foreground, Color.Black);
+		var cyan = new Attribute (Color.BrightCyan, Color.Black);
+		var magenta = new Attribute (Color.BrightMagenta, Color.Black);
+		var red = new Attribute (Color.BrightRed, Color.Black);
 
-			graphView.GraphColor = black;
+		_graphView.GraphColor = black;
 
-			List<PointF> randomPoints = new List<PointF> ();
+		var randomPoints = new List<PointF> ();
 
-			Random r = new Random ();
+		var r = new Random ();
 
-			for (int i = 0; i < 10; i++) {
-				randomPoints.Add (new PointF (r.Next (100), r.Next (100)));
-			}
+		for (var i = 0; i < 10; i++) {
+			randomPoints.Add (new PointF (r.Next (100), r.Next (100)));
+		}
 
-			var points = new ScatterSeries () {
-				Points = randomPoints
-			};
+		var points = new ScatterSeries {
+			Points = randomPoints
+		};
 
-			var line = new PathAnnotation () {
-				LineColor = cyan,
-				Points = randomPoints.OrderBy (p => p.X).ToList (),
-				BeforeSeries = true,
-			};
+		var line = new PathAnnotation {
+			LineColor = cyan,
+			Points = randomPoints.OrderBy (p => p.X).ToList (),
+			BeforeSeries = true
+		};
 
-			graphView.Series.Add (points);
-			graphView.Annotations.Add (line);
+		_graphView.Series.Add (points);
+		_graphView.Annotations.Add (line);
 
-			randomPoints = new List<PointF> ();
+		randomPoints = new List<PointF> ();
 
-			for (int i = 0; i < 10; i++) {
-				randomPoints.Add (new PointF (r.Next (100), r.Next (100)));
-			}
+		for (var i = 0; i < 10; i++) {
+			randomPoints.Add (new PointF (r.Next (100), r.Next (100)));
+		}
 
-			var points2 = new ScatterSeries () {
-				Points = randomPoints,
-				Fill = new GraphCellToRender ((Rune)'x', red)
-			};
+		var points2 = new ScatterSeries {
+			Points = randomPoints,
+			Fill = new GraphCellToRender ((Rune)'x', red)
+		};
 
-			var line2 = new PathAnnotation () {
-				LineColor = magenta,
-				Points = randomPoints.OrderBy (p => p.X).ToList (),
-				BeforeSeries = true,
-			};
+		var line2 = new PathAnnotation {
+			LineColor = magenta,
+			Points = randomPoints.OrderBy (p => p.X).ToList (),
+			BeforeSeries = true
+		};
 
-			graphView.Series.Add (points2);
-			graphView.Annotations.Add (line2);
+		_graphView.Series.Add (points2);
+		_graphView.Annotations.Add (line2);
 
-			// How much graph space each cell of the console depicts
-			graphView.CellSize = new PointF (2, 5);
+		// 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;
+		// 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 →";
+		// 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";
+		_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) });
+		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 ();
-		}
+		_graphView.SetNeedsDisplay ();
+	}
 
-		private void SetupSineWave ()
-		{
-			graphView.Reset ();
+	void SetupSineWave ()
+	{
+		_graphView.Reset ();
 
-			about.Text = "This graph shows a sine wave";
+		_graphView.Title = "Sine Wave";
 
-			var points = new ScatterSeries ();
-			var line = new PathAnnotation ();
+		_about.Text = "This graph shows a sine wave";
 
-			// Draw line first so it does not draw over top of points or axis labels
-			line.BeforeSeries = true;
+		var points = new ScatterSeries ();
+		var line = new PathAnnotation ();
 
-			// 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)));
-			}
+		// Draw line first so it does not draw over top of points or axis labels
+		line.BeforeSeries = true;
 
-			graphView.Series.Add (points);
-			graphView.Annotations.Add (line);
+		// 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)));
+		}
 
-			// How much graph space each cell of the console depicts
-			graphView.CellSize = new PointF (0.1f, 0.1f);
+		_graphView.Series.Add (points);
+		_graphView.Annotations.Add (line);
 
-			// leave space for axis labels
-			graphView.MarginBottom = 2;
-			graphView.MarginLeft = 3;
+		// How much graph space each cell of the console depicts
+		_graphView.CellSize = new PointF (0.1f, 0.1f);
 
-			// 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");
+		// leave space for axis labels
+		_graphView.MarginBottom = 2;
+		_graphView.MarginLeft = 3;
 
-			graphView.AxisY.Increment = 0.2f;
-			graphView.AxisY.ShowLabelsEvery = 2;
-			graphView.AxisY.Text = "↑Y";
-			graphView.AxisY.LabelGetter = (v) => v.Value.ToString ("N2");
+		// 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.ScrollOffset = new PointF (-2.5f, -1);
+		_graphView.AxisY.Increment = 0.2f;
+		_graphView.AxisY.ShowLabelsEvery = 2;
+		_graphView.AxisY.Text = "↑Y";
+		_graphView.AxisY.LabelGetter = v => v.Value.ToString ("N2");
 
-			graphView.SetNeedsDisplay ();
-		}
-		/*
-		Country,Both,Male,Female
+		_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
@@ -291,95 +332,97 @@ namespace UICatalog.Scenarios {
 "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 ((Rune)'\u2591');
-			var mediumStiple = new GraphCellToRender ((Rune)'\u2592');
-
-			var barSeries = new BarSeries () {
-				Bars = new List<BarSeriesBar> () {
-					new BarSeriesBar ("Switzerland", softStiple, 83.4f),
-					new BarSeriesBar ("South Korea", !verticalBars?mediumStiple:softStiple, 83.3f),
-					new BarSeriesBar ("Singapore", softStiple, 83.2f),
-					new BarSeriesBar ("Spain", !verticalBars?mediumStiple:softStiple, 83.2f),
-					new BarSeriesBar ("Cyprus", softStiple, 83.1f),
-					new BarSeriesBar ("Australia", !verticalBars?mediumStiple:softStiple, 83),
-					new BarSeriesBar ("Italy", softStiple, 83),
-					new BarSeriesBar ("Norway", !verticalBars?mediumStiple:softStiple, 83),
-					new BarSeriesBar ("Israel", softStiple, 82.6f),
-					new BarSeriesBar ("France", !verticalBars?mediumStiple:softStiple, 82.5f),
-					new BarSeriesBar ("Luxembourg", softStiple, 82.4f),
-					new BarSeriesBar ("Sweden", !verticalBars?mediumStiple:softStiple, 82.4f),
-					new BarSeriesBar ("Iceland", softStiple, 82.3f),
-					new BarSeriesBar ("Canada", !verticalBars?mediumStiple:softStiple, 82.2f),
-					new BarSeriesBar ("New Zealand", softStiple, 82),
-					new BarSeriesBar ("Malta", !verticalBars?mediumStiple:softStiple, 81.9f),
-					new BarSeriesBar ("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;
+	void SetupLifeExpectancyBarGraph (bool verticalBars)
+	{
+		_graphView.Reset ();
+
+		_graphView.Title = $"Life Expectancy - {(verticalBars ? "Vertical" : "Horizontal")}";
+
+		_about.Text = "This graph shows the life expectancy at birth of a range of countries";
+
+		var softStiple = new GraphCellToRender ((Rune)'\u2591');
+		var mediumStiple = new GraphCellToRender ((Rune)'\u2592');
+
+		var barSeries = new BarSeries {
+			Bars = new List<BarSeriesBar> {
+				new ("Switzerland", softStiple, 83.4f),
+				new ("South Korea", !verticalBars ? mediumStiple : softStiple, 83.3f),
+				new ("Singapore", softStiple, 83.2f),
+				new ("Spain", !verticalBars ? mediumStiple : softStiple, 83.2f),
+				new ("Cyprus", softStiple, 83.1f),
+				new ("Australia", !verticalBars ? mediumStiple : softStiple, 83),
+				new ("Italy", softStiple, 83),
+				new ("Norway", !verticalBars ? mediumStiple : softStiple, 83),
+				new ("Israel", softStiple, 82.6f),
+				new ("France", !verticalBars ? mediumStiple : softStiple, 82.5f),
+				new ("Luxembourg", softStiple, 82.4f),
+				new ("Sweden", !verticalBars ? mediumStiple : softStiple, 82.4f),
+				new ("Iceland", softStiple, 82.3f),
+				new ("Canada", !verticalBars ? mediumStiple : softStiple, 82.2f),
+				new ("New Zealand", softStiple, 82),
+				new ("Malta", !verticalBars ? mediumStiple : softStiple, 81.9f),
+				new ("Ireland", softStiple, 81.8f)
+			}
+		};
 
-				graphView.AxisY.Increment = 1f;
-				graphView.AxisY.ShowLabelsEvery = 1;
-				graphView.AxisY.LabelGetter = v => v.Value.ToString ("N2");
-				graphView.AxisY.Minimum = 0;
-				graphView.AxisY.Text = "Age";
+		_graphView.Series.Add (barSeries);
 
-				// leave space for axis labels and title
-				graphView.MarginBottom = 2;
-				graphView.MarginLeft = 6;
+		if (verticalBars) {
 
-				// Start the graph at 80 years because that is where most of our data is
-				graphView.ScrollOffset = new PointF (0, 80);
+			barSeries.Orientation = Orientation.Vertical;
 
-			} else {
-				barSeries.Orientation = Orientation.Horizontal;
+			// 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;
 
-				// 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.AxisY.Increment = 1f;
+			_graphView.AxisY.ShowLabelsEvery = 1;
+			_graphView.AxisY.LabelGetter = v => v.Value.ToString ("N2");
+			_graphView.AxisY.Minimum = 0;
+			_graphView.AxisY.Text = "Age";
 
-				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 = 6;
 
-				// 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 (0, 80);
 
-				// Start the graph at 80 years because that is where most of our data is
-				graphView.ScrollOffset = new PointF (80, 0);
-			}
+		} else {
+			barSeries.Orientation = Orientation.Horizontal;
 
-			graphView.SetNeedsDisplay ();
+			// 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);
 		}
 
-		private void SetupPopulationPyramid ()
-		{
-			/*
-			Age,M,F
+		_graphView.SetNeedsDisplay ();
+	}
+
+	void SetupPopulationPyramid ()
+	{
+		/*
+		Age,M,F
 0-4,2009363,1915127
 5-9,2108550,2011016
 10-14,2022370,1933970
@@ -402,280 +445,283 @@ namespace UICatalog.Scenarios {
 95-99,34524,95559
 100+,3016,12818*/
 
-			about.Text = "This graph shows population of each age divided by gender";
+		_about.Text = "This graph shows population of each age divided by gender";
+
+		_graphView.Title = "Population Pyramid";
+
+		_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 (CM.Glyphs.Stipple);
+
+		// Bars in 2 directions
+
+		// Males (negative to make the bars go left)
+		var malesSeries = new BarSeries {
+			Orientation = Orientation.Horizontal,
+			Bars = new List<BarSeriesBar> {
+				new ("0-4", stiple, -2009363),
+				new ("5-9", stiple, -2108550),
+				new ("10-14", stiple, -2022370),
+				new ("15-19", stiple, -1880611),
+				new ("20-24", stiple, -2072674),
+				new ("25-29", stiple, -2275138),
+				new ("30-34", stiple, -2361054),
+				new ("35-39", stiple, -2279836),
+				new ("40-44", stiple, -2148253),
+				new ("45-49", stiple, -2128343),
+				new ("50-54", stiple, -2281421),
+				new ("55-59", stiple, -2232388),
+				new ("60-64", stiple, -1919839),
+				new ("65-69", stiple, -1647391),
+				new ("70-74", stiple, -1624635),
+				new ("75-79", stiple, -1137438),
+				new ("80-84", stiple, -766956),
+				new ("85-89", stiple, -438663),
+				new ("90-94", stiple, -169952),
+				new ("95-99", stiple, -34524),
+				new ("100+", stiple, -3016)
 
-			graphView.Reset ();
+			}
+		};
+		_graphView.Series.Add (malesSeries);
+
+		// Females
+		var femalesSeries = new BarSeries {
+			Orientation = Orientation.Horizontal,
+			Bars = new List<BarSeriesBar> {
+				new ("0-4", stiple, 1915127),
+				new ("5-9", stiple, 2011016),
+				new ("10-14", stiple, 1933970),
+				new ("15-19", stiple, 1805522),
+				new ("20-24", stiple, 2001966),
+				new ("25-29", stiple, 2208929),
+				new ("30-34", stiple, 2345774),
+				new ("35-39", stiple, 2308360),
+				new ("40-44", stiple, 2159877),
+				new ("45-49", stiple, 2167778),
+				new ("50-54", stiple, 2353119),
+				new ("55-59", stiple, 2306537),
+				new ("60-64", stiple, 1985177),
+				new ("65-69", stiple, 1734370),
+				new ("70-74", stiple, 1763853),
+				new ("75-79", stiple, 1304709),
+				new ("80-84", stiple, 969611),
+				new ("85-89", stiple, 638892),
+				new ("90-94", stiple, 320625),
+				new ("95-99", stiple, 95559),
+				new ("100+", stiple, 12818)
+			}
+		};
 
-			// 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 (CM.Glyphs.Stipple);
-
-			// Bars in 2 directions
-
-			// Males (negative to make the bars go left)
-			var malesSeries = new BarSeries () {
-				Orientation = Orientation.Horizontal,
-				Bars = new List<BarSeriesBar> ()
-				{
-					new BarSeriesBar("0-4",stiple,-2009363),
-					new BarSeriesBar("5-9",stiple,-2108550),
-					new BarSeriesBar("10-14",stiple,-2022370),
-					new BarSeriesBar("15-19",stiple,-1880611),
-					new BarSeriesBar("20-24",stiple,-2072674),
-					new BarSeriesBar("25-29",stiple,-2275138),
-					new BarSeriesBar("30-34",stiple,-2361054),
-					new BarSeriesBar("35-39",stiple,-2279836),
-					new BarSeriesBar("40-44",stiple,-2148253),
-					new BarSeriesBar("45-49",stiple,-2128343),
-					new BarSeriesBar("50-54",stiple,-2281421),
-					new BarSeriesBar("55-59",stiple,-2232388),
-					new BarSeriesBar("60-64",stiple,-1919839),
-					new BarSeriesBar("65-69",stiple,-1647391),
-					new BarSeriesBar("70-74",stiple,-1624635),
-					new BarSeriesBar("75-79",stiple,-1137438),
-					new BarSeriesBar("80-84",stiple,-766956),
-					new BarSeriesBar("85-89",stiple,-438663),
-					new BarSeriesBar("90-94",stiple,-169952),
-					new BarSeriesBar("95-99",stiple,-34524),
-					new BarSeriesBar("100+",stiple,-3016)
+		var softStiple = new GraphCellToRender ((Rune)'\u2591');
+		var mediumStiple = new GraphCellToRender ((Rune)'\u2592');
 
-				}
-			};
-			graphView.Series.Add (malesSeries);
-
-			// Females
-			var femalesSeries = new BarSeries () {
-				Orientation = Orientation.Horizontal,
-				Bars = new List<BarSeriesBar> ()
-				{
-					new BarSeriesBar("0-4",stiple,1915127),
-					new BarSeriesBar("5-9",stiple,2011016),
-					new BarSeriesBar("10-14",stiple,1933970),
-					new BarSeriesBar("15-19",stiple,1805522),
-					new BarSeriesBar("20-24",stiple,2001966),
-					new BarSeriesBar("25-29",stiple,2208929),
-					new BarSeriesBar("30-34",stiple,2345774),
-					new BarSeriesBar("35-39",stiple,2308360),
-					new BarSeriesBar("40-44",stiple,2159877),
-					new BarSeriesBar("45-49",stiple,2167778),
-					new BarSeriesBar("50-54",stiple,2353119),
-					new BarSeriesBar("55-59",stiple,2306537),
-					new BarSeriesBar("60-64",stiple,1985177),
-					new BarSeriesBar("65-69",stiple,1734370),
-					new BarSeriesBar("70-74",stiple,1763853),
-					new BarSeriesBar("75-79",stiple,1304709),
-					new BarSeriesBar("80-84",stiple,969611),
-					new BarSeriesBar("85-89",stiple,638892),
-					new BarSeriesBar("90-94",stiple,320625),
-					new BarSeriesBar("95-99",stiple,95559),
-					new BarSeriesBar("100+",stiple,12818)
-				}
-			};
+		for (var 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;
+		}
 
-			var softStiple = new GraphCellToRender ((Rune)'\u2591');
-			var mediumStiple = new GraphCellToRender ((Rune)'\u2592');
+		_graphView.Series.Add (femalesSeries);
 
-			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.Annotations.Add (new TextAnnotation { Text = "M", ScreenPosition = new Point (0, 10) });
+		_graphView.Annotations.Add (new TextAnnotation { Text = "F", ScreenPosition = new Point (_graphView.Bounds.Width - 1, 10) });
 
-			graphView.Series.Add (femalesSeries);
+		_graphView.SetNeedsDisplay ();
 
-			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 ();
+	void SetupDisco ()
+	{
+		_graphView.Reset ();
 
-		}
+		_graphView.Title = "Graphic Equalizer";
 
-		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 = new Attribute (Color.BrightGreen, Color.Black);
-				brightgreen = new Attribute (Color.Green, Color.Black);
-				brightyellow = new Attribute (Color.BrightYellow, Color.Black);
-				red = new Attribute (Color.Red, Color.Black);
-				brightred = new Attribute (Color.BrightRed, Color.Black);
-			}
-			protected override void DrawBarLine (GraphView graph, Terminal.Gui.Point start, Terminal.Gui.Point end, BarSeriesBar 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);
-				}
-			}
-		}
+		_about.Text = "This graph shows a graphic equalizer for an imaginary song";
 
-		private void SetupDisco ()
-		{
-			graphView.Reset ();
+		_graphView.GraphColor = new Attribute (Color.White, Color.Black);
 
-			about.Text = "This graph shows a graphic equaliser for an imaginary song";
+		var stiple = new GraphCellToRender ((Rune)'\u2593');
 
-			graphView.GraphColor = new Attribute (Color.White, Color.Black);
+		var r = new Random ();
+		var series = new DiscoBarSeries ();
+		var bars = new List<BarSeriesBar> ();
 
-			var stiple = new GraphCellToRender ((Rune)'\u2593');
+		var genSample = () => {
 
-			Random r = new Random ();
-			var series = new DiscoBarSeries ();
-			var bars = new List<BarSeriesBar> ();
+			bars.Clear ();
+			// generate an imaginary sample
+			for (var i = 0; i < 31; i++) {
+				bars.Add (
+					new BarSeriesBar (null, stiple, r.Next (0, 100)) {
+						//ColorGetter = colorDelegate
+					});
+			}
+			_graphView.SetNeedsDisplay ();
 
-			Func<bool> genSample = () => {
+			// while the equaliser is showing
+			return _graphView.Series.Contains (series);
+		};
 
-				bars.Clear ();
-				// generate an imaginary sample
-				for (int i = 0; i < 31; i++) {
-					bars.Add (
-						new BarSeriesBar (null, stiple, r.Next (0, 100)) {
-							//ColorGetter = colorDelegate
-						});
-				}
-				graphView.SetNeedsDisplay ();
+		Application.AddTimeout (TimeSpan.FromMilliseconds (250), genSample);
 
-				// while the equaliser is showing
-				return graphView.Series.Contains (series);
-			};
+		series.Bars = bars;
 
-			Application.AddTimeout (TimeSpan.FromMilliseconds (250), genSample);
+		_graphView.Series.Add (series);
 
-			series.Bars = bars;
+		// 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.Series.Add (series);
+		_graphView.AxisX.Visible = false;
+		_graphView.AxisY.Visible = false;
 
-			// 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.SetNeedsDisplay ();
+	}
 
-			graphView.AxisX.Visible = false;
-			graphView.AxisY.Visible = false;
+	void SetupPeriodicTableScatterPlot ()
+	{
+		_graphView.Reset ();
+
+		_graphView.Title = "Scatter Plot";
+
+		_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 (1, 1.007f), new (2, 4.002f), new (3, 6.941f), new (4, 9.012f), new (5, 10.811f), new (6, 12.011f),
+					new (7, 14.007f), new (8, 15.999f), new (9, 18.998f), new (10, 20.18f), new (11, 22.99f), new (12, 24.305f),
+					new (13, 26.982f), new (14, 28.086f), new (15, 30.974f), new (16, 32.065f), new (17, 35.453f), new (18, 39.948f),
+					new (19, 39.098f), new (20, 40.078f), new (21, 44.956f), new (22, 47.867f), new (23, 50.942f), new (24, 51.996f),
+					new (25, 54.938f), new (26, 55.845f), new (27, 58.933f), new (28, 58.693f), new (29, 63.546f), new (30, 65.38f),
+					new (31, 69.723f), new (32, 72.64f), new (33, 74.922f), new (34, 78.96f), new (35, 79.904f), new (36, 83.798f),
+					new (37, 85.468f), new (38, 87.62f), new (39, 88.906f), new (40, 91.224f), new (41, 92.906f), new (42, 95.96f),
+					new (43, 98f), new (44, 101.07f), new (45, 102.906f), new (46, 106.42f), new (47, 107.868f), new (48, 112.411f),
+					new (49, 114.818f), new (50, 118.71f), new (51, 121.76f), new (52, 127.6f), new (53, 126.904f), new (54, 131.293f),
+					new (55, 132.905f), new (56, 137.327f), new (57, 138.905f), new (58, 140.116f), new (59, 140.908f), new (60, 144.242f),
+					new (61, 145), new (62, 150.36f), new (63, 151.964f), new (64, 157.25f), new (65, 158.925f), new (66, 162.5f),
+					new (67, 164.93f), new (68, 167.259f), new (69, 168.934f), new (70, 173.054f), new (71, 174.967f), new (72, 178.49f),
+					new (73, 180.948f), new (74, 183.84f), new (75, 186.207f), new (76, 190.23f), new (77, 192.217f), new (78, 195.084f),
+					new (79, 196.967f), new (80, 200.59f), new (81, 204.383f), new (82, 207.2f), new (83, 208.98f), new (84, 210),
+					new (85, 210), new (86, 222), new (87, 223), new (88, 226), new (89, 227), new (90, 232.038f), new (91, 231.036f),
+					new (92, 238.029f), new (93, 237), new (94, 244), new (95, 243), new (96, 247), new (97, 247), new (98, 251),
+					new (99, 252), new (100, 257), new (101, 258), new (102, 259), new (103, 262), new (104, 261), new (105, 262),
+					new (106, 266), new (107, 264), new (108, 267), new (109, 268), new (113, 284), new (114, 289), new (115, 288),
+					new (116, 292), new (117, 295), new (118, 294)
+				}
+			});
 
-			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);
 
-			// 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;
 
-			// 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 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;
 
-			// One label every 5 atomic weight
-			graphView.AxisY.Increment = 5;
-			graphView.AxisY.ShowLabelsEvery = 1;
-			graphView.AxisY.Minimum = 0;
+		_graphView.SetNeedsDisplay ();
+	}
 
-			graphView.SetNeedsDisplay ();
-		}
+	void Zoom (float factor)
+	{
+		_graphView.CellSize = new PointF (
+			_graphView.CellSize.X * factor,
+			_graphView.CellSize.Y * factor
+		);
 
-		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.AxisX.Increment *= factor;
-			graphView.AxisY.Increment *= factor;
+		_graphView.SetNeedsDisplay ();
+	}
 
-			graphView.SetNeedsDisplay ();
+	void Margin (bool left, bool increase)
+	{
+		if (left) {
+			_graphView.MarginLeft = (uint)Math.Max (0, _graphView.MarginLeft + (increase ? 1 : -1));
+		} else {
+			_graphView.MarginBottom = (uint)Math.Max (0, _graphView.MarginBottom + (increase ? 1 : -1));
 		}
-		private void Margin (bool left, bool increase)
+
+		_graphView.SetNeedsDisplay ();
+	}
+
+	void Quit () => Application.RequestStop ();
+
+	class DiscoBarSeries : BarSeries {
+		readonly Attribute brightgreen;
+		readonly Attribute brightred;
+		readonly Attribute brightyellow;
+		readonly Attribute green;
+		readonly Attribute red;
+
+		public DiscoBarSeries ()
 		{
-			if (left) {
-				graphView.MarginLeft = (uint)Math.Max (0, graphView.MarginLeft + (increase ? 1 : -1));
-			} else {
-				graphView.MarginBottom = (uint)Math.Max (0, graphView.MarginBottom + (increase ? 1 : -1));
-			}
 
-			graphView.SetNeedsDisplay ();
+			green = new Attribute (Color.BrightGreen, Color.Black);
+			brightgreen = new Attribute (Color.Green, Color.Black);
+			brightyellow = new Attribute (Color.BrightYellow, Color.Black);
+			red = new Attribute (Color.Red, Color.Black);
+			brightred = new Attribute (Color.BrightRed, Color.Black);
 		}
 
-		private void Quit ()
+		protected override void DrawBarLine (GraphView graph, Point start, Point end, BarSeriesBar beingDrawn)
 		{
-			Application.RequestStop ();
+			var driver = Application.Driver;
+
+			var x = start.X;
+			for (var 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);
+			}
 		}
 	}
-}
+}

+ 21 - 1
UnitTests/Views/GraphViewTests.cs

@@ -1252,6 +1252,26 @@ namespace Terminal.Gui.ViewsTests {
 			this.output = output;
 		}
 
+		[Fact]
+		public void Constructors_Defaults ()
+		{
+			var legend = new LegendAnnotation ();
+			Assert.Equal (Rect.Empty, legend.Bounds);
+			Assert.Equal (Rect.Empty, legend.Frame);
+			Assert.Equal (LineStyle.Single, legend.BorderStyle);
+			Assert.False (legend.BeforeSeries);
+
+			var bounds = new Rect (1, 2, 10, 3);
+			legend = new LegendAnnotation (bounds);
+			Assert.Equal (new Rect (0, 0, 8, 1), legend.Bounds);
+			Assert.Equal (bounds, legend.Frame);
+			Assert.Equal (LineStyle.Single, legend.BorderStyle);
+			Assert.False (legend.BeforeSeries);
+			legend.BorderStyle = LineStyle.None;
+			Assert.Equal (new Rect (0, 0, 10, 3), legend.Bounds);
+			Assert.Equal (bounds, legend.Frame);
+		}
+
 		[Fact]
 		public void LegendNormalUsage_WithBorder ()
 		{
@@ -1286,7 +1306,7 @@ namespace Terminal.Gui.ViewsTests {
 			legend.AddEntry (new GraphCellToRender ((Rune)'B'), "?"); // this will exercise pad
 			legend.AddEntry (new GraphCellToRender ((Rune)'C'), "Cat");
 			legend.AddEntry (new GraphCellToRender ((Rune)'H'), "Hattter"); // not enough space for this oen
-			legend.Border = false;
+			legend.BorderStyle = LineStyle.None;
 
 			gv.Annotations.Add (legend);
 			gv.Draw ();