Add support for bar charts

Closes #103
This commit is contained in:
Patrik Svensson
2020-12-22 22:26:17 +01:00
committed by Patrik Svensson
parent 1cf30f62fc
commit 7dccb310f3
21 changed files with 597 additions and 8 deletions

View File

@ -0,0 +1,82 @@
using System.Collections.Generic;
using System.Linq;
using Spectre.Console.Rendering;
namespace Spectre.Console
{
/// <summary>
/// A renderable (horizontal) bar chart.
/// </summary>
public sealed class BarChart : Renderable
{
/// <summary>
/// Gets the bar chart data.
/// </summary>
public List<BarChartItem> Data { get; }
/// <summary>
/// Gets or sets the width of the bar chart.
/// </summary>
public int? Width { get; set; }
/// <summary>
/// Gets or sets the bar chart label.
/// </summary>
public string? Label { get; set; }
/// <summary>
/// Gets or sets the bar chart label alignment.
/// </summary>
public Justify? LabelAlignment { get; set; } = Justify.Center;
/// <summary>
/// Gets or sets a value indicating whether or not
/// values should be shown next to each bar.
/// </summary>
public bool ShowValues { get; set; } = true;
/// <summary>
/// Initializes a new instance of the <see cref="BarChart"/> class.
/// </summary>
public BarChart()
{
Data = new List<BarChartItem>();
}
/// <inheritdoc/>
protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth)
{
var maxValue = Data.Max(item => item.Value);
var table = new Grid();
table.Collapse();
table.AddColumn(new GridColumn().PadRight(2).RightAligned());
table.AddColumn(new GridColumn().PadLeft(0));
table.Width = Width;
if (!string.IsNullOrWhiteSpace(Label))
{
table.AddRow(Text.Empty, new Markup(Label).Alignment(LabelAlignment));
}
foreach (var item in Data)
{
table.AddRow(
new Markup(item.Label),
new ProgressBar()
{
Value = item.Value,
MaxValue = maxValue,
ShowRemaining = false,
CompletedStyle = new Style().Foreground(item.Color ?? Color.Default),
FinishedStyle = new Style().Foreground(item.Color ?? Color.Default),
UnicodeBar = '█',
AsciiBar = '█',
ShowValue = ShowValues,
});
}
return ((IRenderable)table).Render(context, maxWidth);
}
}
}

View File

@ -0,0 +1,38 @@
using System;
namespace Spectre.Console
{
/// <summary>
/// An item that's shown in a bar chart.
/// </summary>
public sealed class BarChartItem : IBarChartItem
{
/// <summary>
/// Gets the item label.
/// </summary>
public string Label { get; }
/// <summary>
/// Gets the item value.
/// </summary>
public double Value { get; }
/// <summary>
/// Gets the item color.
/// </summary>
public Color? Color { get; }
/// <summary>
/// Initializes a new instance of the <see cref="BarChartItem"/> class.
/// </summary>
/// <param name="label">The item label.</param>
/// <param name="value">The item value.</param>
/// <param name="color">The item color.</param>
public BarChartItem(string label, double value, Color? color = null)
{
Label = label ?? throw new ArgumentNullException(nameof(label));
Value = value;
Color = color;
}
}
}

View File

@ -42,6 +42,11 @@ namespace Spectre.Console
set => MarkAsDirty(() => _alignment = value);
}
/// <summary>
/// Gets or sets the width of the grid.
/// </summary>
public int? Width { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="Grid"/> class.
/// </summary>
@ -124,6 +129,7 @@ namespace Spectre.Console
ShowHeaders = false,
IsGrid = true,
PadRightCell = _padRightCell,
Width = Width,
};
foreach (var column in _columns)

View File

@ -0,0 +1,23 @@
namespace Spectre.Console
{
/// <summary>
/// Represents a bar chart item.
/// </summary>
public interface IBarChartItem
{
/// <summary>
/// Gets the item label.
/// </summary>
string Label { get; }
/// <summary>
/// Gets the item value.
/// </summary>
double Value { get; }
/// <summary>
/// Gets the item color.
/// </summary>
Color? Color { get; }
}
}

View File

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using Spectre.Console.Rendering;
namespace Spectre.Console
@ -10,6 +11,10 @@ namespace Spectre.Console
public double MaxValue { get; set; } = 100;
public int? Width { get; set; }
public bool ShowRemaining { get; set; } = true;
public char UnicodeBar { get; set; } = '━';
public char AsciiBar { get; set; } = '-';
public bool ShowValue { get; set; }
public Style CompletedStyle { get; set; } = new Style(foreground: Color.Yellow);
public Style FinishedStyle { get; set; } = new Style(foreground: Color.Green);
@ -26,15 +31,38 @@ namespace Spectre.Console
var width = Math.Min(Width ?? maxWidth, maxWidth);
var completed = Math.Min(MaxValue, Math.Max(0, Value));
var token = !context.Unicode || context.LegacyConsole ? '-' : '━';
var token = !context.Unicode || context.LegacyConsole ? AsciiBar : UnicodeBar;
var style = completed >= MaxValue ? FinishedStyle : CompletedStyle;
var bars = Math.Max(0, (int)(width * (completed / MaxValue)));
var value = completed.ToString(CultureInfo.InvariantCulture);
if (ShowValue)
{
bars = bars - value.Length - 1;
}
yield return new Segment(new string(token, bars), style);
if (ShowValue)
{
yield return new Segment(" " + value, style);
}
if (bars < width)
{
yield return new Segment(new string(token, width - bars), RemainingStyle);
var diff = width - bars;
if (ShowValue)
{
diff = diff - value.Length - 1;
if (diff <= 0)
{
yield break;
}
}
var remainingToken = ShowRemaining ? token : ' ';
yield return new Segment(new string(remainingToken, diff), RemainingStyle);
}
}
}