using System;
using System.Collections.Generic;
using System.Text;
namespace Terminal.Gui {
///
/// Describes an overlay element that is rendered either before or
/// after a series.
///
/// Annotations can be positioned either in screen space (e.g.
/// a legend) or in graph space (e.g. a line showing high point)
///
/// Unlike , annotations are allowed to
/// draw into graph margins
///
///
public interface IAnnotation {
///
/// True if annotation should be drawn before . This
/// allows Series and later annotations to potentially draw over the top
/// of this annotation.
///
bool BeforeSeries { get; }
///
/// Called once after series have been rendered (or before if is true).
/// Use to draw and to avoid drawing outside of
/// graph
///
///
void Render (GraphView graph);
}
///
/// Displays text at a given position (in screen space or graph space)
///
public class TextAnnotation : IAnnotation {
///
/// The location on screen to draw the regardless
/// of scroll/zoom settings. This overrides
/// if specified.
///
public Point? ScreenPosition { get; set; }
///
/// The location in graph space to draw the . This
/// annotation will only show if the point is in the current viewable
/// area of the graph presented in the
///
public PointF GraphPosition { get; set; }
///
/// Text to display on the graph
///
public string Text { get; set; }
///
/// True to add text before plotting series. Defaults to false
///
public bool BeforeSeries { get; set; }
///
/// Draws the annotation
///
///
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);
}
///
/// Draws the at the given coordinates with truncation to avoid
/// spilling over of the
///
///
/// Screen x position to start drawing string
/// Screen y position to start drawing string
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));
}
}
}
///
/// A box containing symbol definitions e.g. meanings for colors in a graph.
/// The 'Key' to the graph
///
public class LegendAnnotation : View, IAnnotation {
///
/// Returns false i.e. Legends render after series
///
public bool BeforeSeries => false;
///
/// Ordered collection of entries that are rendered in the legend.
///
List> _entries = new List> ();
///
/// Creates a new empty legend at the empty screen coordinates.
///
public LegendAnnotation () : this (Rect.Empty) { }
///
/// Creates a new empty legend at the given screen coordinates.
///
/// Defines the area available for the legend to render in
/// (within the graph). This is in screen units (i.e. not graph space)
public LegendAnnotation (Rect legendBounds)
{
X = legendBounds.X;
Y = legendBounds.Y;
Width = legendBounds.Width;
Height = legendBounds.Height;
BorderStyle = LineStyle.Single;
}
///
/// Draws the Legend and all entries into the area within
///
///
public void Render (GraphView graph)
{
if (!IsInitialized) {
ColorScheme = new ColorScheme () { Normal = Application.Driver.GetAttribute () };
graph.Add (this);
}
if (BorderStyle != LineStyle.None) {
OnDrawAdornments ();
OnRenderLineCanvas ();
}
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
AddRune (0, linesDrawn, entry.Item1.Rune);
// switch to normal coloring (for the text)
graph.SetDriverColorToGraphColor ();
// add the text
Move (1, linesDrawn);
string str = TextFormatter.ClipOrPad (entry.Item2, Bounds.Width - 1);
Application.Driver.AddStr (str);
linesDrawn++;
// Legend has run out of space
if (linesDrawn >= Bounds.Height) {
break;
}
}
}
///
/// Adds an entry into the legend. Duplicate entries are permissible
///
/// The symbol appearing on the graph that should appear in the legend
/// Text to render on this line of the legend. Will be truncated
/// if outside of Legend
public void AddEntry (GraphCellToRender graphCellToRender, string text)
{
_entries.Add (Tuple.Create (graphCellToRender, text));
}
}
///
/// Sequence of lines to connect points e.g. of a
///
public class PathAnnotation : IAnnotation {
///
/// Points that should be connected. Lines will be drawn between points in the order
/// they appear in the list
///
public List Points { get; set; } = new List ();
///
/// Color for the line that connects points
///
public Attribute? LineColor { get; set; }
///
/// The symbol that gets drawn along the line, defaults to '.'
///
public Rune LineRune { get; set; } = new Rune ('.');
///
/// True to add line before plotting series. Defaults to false
///
public bool BeforeSeries { get; set; }
///
/// Draws lines connecting each of the
///
///
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);
}
}
///
/// Generates lines joining
///
///
private IEnumerable PointsToLines ()
{
for (int i = 0; i < Points.Count - 1; i++) {
yield return new LineF (Points [i], Points [i + 1]);
}
}
}
}