using System;
using System.Collections.Generic;
using System.Linq;
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
/// allowes 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 : IAnnotation {
///
/// True to draw a solid border around the legend.
/// Defaults to true. This border will be within the
/// and so reduces the width/height
/// available for text by 2
///
public bool Border { get; set; } = true;
///
/// Defines the screen area available for the legend to render in
///
public Rect Bounds { get; set; }
///
/// Returns false i.e. Lengends 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 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)
{
Bounds = legendBounds;
}
///
/// Draws the Legend and all entries into the area within
///
///
public void Render (GraphView graph)
{
if (Border) {
graph.Border.DrawFrame (Bounds, 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 = TextFormatter.ClipOrPad (entry.Item2, availableWidth - 1);
Application.Driver.AddStr (str);
linesDrawn++;
// Legend has run out of space
if (linesDrawn >= availableHeight) {
break;
}
}
}
///
/// Adds an entry into the legend. Duplicate entries are permissable
///
/// 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]);
}
}
///
/// Describes two points in graph space and a line between them
///
public class LineF {
///
/// The start of the line
///
public PointF Start { get; }
///
/// The end point of the line
///
public PointF End { get; }
///
/// Creates a new line between the points
///
public LineF (PointF start, PointF end)
{
this.Start = start;
this.End = end;
}
}
}
}