mirror of
https://github.com/nsnail/spectre.console.git
synced 2025-06-19 13:28:16 +08:00
Add breakdown chart support
This also cleans up the bar chart code slightly and fixes some minor bugs that were detected in related code. Closes #244
This commit is contained in:

committed by
Patrik Svensson

parent
58400fe74e
commit
b64e016e8c
99
src/Spectre.Console/Widgets/Charts/BarChart.cs
Normal file
99
src/Spectre.Console/Widgets/Charts/BarChart.cs
Normal file
@ -0,0 +1,99 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Spectre.Console.Rendering;
|
||||
|
||||
namespace Spectre.Console
|
||||
{
|
||||
/// <summary>
|
||||
/// A renderable (horizontal) bar chart.
|
||||
/// </summary>
|
||||
public sealed class BarChart : Renderable, IHasCulture
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the bar chart data.
|
||||
/// </summary>
|
||||
public List<IBarChartItem> 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>
|
||||
/// Gets or sets the culture that's used to format values.
|
||||
/// </summary>
|
||||
/// <remarks>Defaults to invariant culture.</remarks>
|
||||
public CultureInfo? Culture { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BarChart"/> class.
|
||||
/// </summary>
|
||||
public BarChart()
|
||||
{
|
||||
Data = new List<IBarChartItem>();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override Measurement Measure(RenderContext context, int maxWidth)
|
||||
{
|
||||
var width = Math.Min(Width ?? maxWidth, maxWidth);
|
||||
return new Measurement(width, width);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth)
|
||||
{
|
||||
var width = Math.Min(Width ?? maxWidth, maxWidth);
|
||||
var maxValue = Data.Max(item => item.Value);
|
||||
|
||||
var grid = new Grid();
|
||||
grid.Collapse();
|
||||
grid.AddColumn(new GridColumn().PadRight(2).RightAligned());
|
||||
grid.AddColumn(new GridColumn().PadLeft(0));
|
||||
grid.Width = width;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(Label))
|
||||
{
|
||||
grid.AddRow(Text.Empty, new Markup(Label).Alignment(LabelAlignment));
|
||||
}
|
||||
|
||||
foreach (var item in Data)
|
||||
{
|
||||
grid.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,
|
||||
Culture = Culture,
|
||||
});
|
||||
}
|
||||
|
||||
return ((IRenderable)grid).Render(context, width);
|
||||
}
|
||||
}
|
||||
}
|
38
src/Spectre.Console/Widgets/Charts/BarChartItem.cs
Normal file
38
src/Spectre.Console/Widgets/Charts/BarChartItem.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
42
src/Spectre.Console/Widgets/Charts/BreakdownBar.cs
Normal file
42
src/Spectre.Console/Widgets/Charts/BreakdownBar.cs
Normal file
@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Spectre.Console.Rendering;
|
||||
|
||||
namespace Spectre.Console
|
||||
{
|
||||
internal sealed class BreakdownBar : Renderable
|
||||
{
|
||||
private readonly List<IBreakdownChartItem> _data;
|
||||
|
||||
public int? Width { get; set; }
|
||||
|
||||
public BreakdownBar(List<IBreakdownChartItem> data)
|
||||
{
|
||||
_data = data ?? throw new ArgumentNullException(nameof(data));
|
||||
}
|
||||
|
||||
protected override Measurement Measure(RenderContext context, int maxWidth)
|
||||
{
|
||||
var width = Math.Min(Width ?? maxWidth, maxWidth);
|
||||
return new Measurement(width, width);
|
||||
}
|
||||
|
||||
protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth)
|
||||
{
|
||||
var width = Math.Min(Width ?? maxWidth, maxWidth);
|
||||
|
||||
// Chart
|
||||
var maxValue = _data.Sum(i => i.Value);
|
||||
var items = _data.ToArray();
|
||||
var bars = Ratio.Distribute(width, items.Select(i => Math.Max(0, (int)(width * (i.Value / maxValue)))).ToArray());
|
||||
|
||||
for (var index = 0; index < items.Length; index++)
|
||||
{
|
||||
yield return new Segment(new string('█', bars[index]), new Style(items[index].Color));
|
||||
}
|
||||
|
||||
yield return Segment.LineBreak;
|
||||
}
|
||||
}
|
||||
}
|
102
src/Spectre.Console/Widgets/Charts/BreakdownChart.cs
Normal file
102
src/Spectre.Console/Widgets/Charts/BreakdownChart.cs
Normal file
@ -0,0 +1,102 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using Spectre.Console.Rendering;
|
||||
|
||||
namespace Spectre.Console
|
||||
{
|
||||
/// <summary>
|
||||
/// A renderable breakdown chart.
|
||||
/// </summary>
|
||||
public sealed class BreakdownChart : Renderable, IHasCulture
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the breakdown chart data.
|
||||
/// </summary>
|
||||
public List<IBreakdownChartItem> Data { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the width of the breakdown chart.
|
||||
/// </summary>
|
||||
public int? Width { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not
|
||||
/// to show values as percentages or not.
|
||||
/// </summary>
|
||||
public bool ShowAsPercentages { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not to show tags.
|
||||
/// </summary>
|
||||
public bool ShowTags { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not to show tag values.
|
||||
/// </summary>
|
||||
public bool ShowTagValues { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not the
|
||||
/// chart and tags should be rendered in compact mode.
|
||||
/// </summary>
|
||||
public bool Compact { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="CultureInfo"/> to use
|
||||
/// when rendering values.
|
||||
/// </summary>
|
||||
/// <remarks>Defaults to invariant culture.</remarks>
|
||||
public CultureInfo? Culture { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BreakdownChart"/> class.
|
||||
/// </summary>
|
||||
public BreakdownChart()
|
||||
{
|
||||
Data = new List<IBreakdownChartItem>();
|
||||
Culture = CultureInfo.InvariantCulture;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override Measurement Measure(RenderContext context, int maxWidth)
|
||||
{
|
||||
var width = Math.Min(Width ?? maxWidth, maxWidth);
|
||||
return new Measurement(width, width);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth)
|
||||
{
|
||||
var width = Math.Min(Width ?? maxWidth, maxWidth);
|
||||
|
||||
var grid = new Grid().Width(width);
|
||||
grid.AddColumn(new GridColumn().NoWrap());
|
||||
|
||||
// Bar
|
||||
grid.AddRow(new BreakdownBar(Data)
|
||||
{
|
||||
Width = width,
|
||||
});
|
||||
|
||||
if (ShowTags)
|
||||
{
|
||||
if (!Compact)
|
||||
{
|
||||
grid.AddEmptyRow();
|
||||
}
|
||||
|
||||
// Tags
|
||||
grid.AddRow(new BreakdownTags(Data)
|
||||
{
|
||||
Width = width,
|
||||
Culture = Culture,
|
||||
ShowPercentages = ShowAsPercentages,
|
||||
ShowTagValues = ShowTagValues,
|
||||
});
|
||||
}
|
||||
|
||||
return ((IRenderable)grid).Render(context, width);
|
||||
}
|
||||
}
|
||||
}
|
38
src/Spectre.Console/Widgets/Charts/BreakdownChartItem.cs
Normal file
38
src/Spectre.Console/Widgets/Charts/BreakdownChartItem.cs
Normal file
@ -0,0 +1,38 @@
|
||||
using System;
|
||||
|
||||
namespace Spectre.Console
|
||||
{
|
||||
/// <summary>
|
||||
/// An item that's shown in a breakdown chart.
|
||||
/// </summary>
|
||||
public sealed class BreakdownChartItem : IBreakdownChartItem
|
||||
{
|
||||
/// <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="BreakdownChartItem"/> class.
|
||||
/// </summary>
|
||||
/// <param name="label">The item label.</param>
|
||||
/// <param name="value">The item value.</param>
|
||||
/// <param name="color">The item color.</param>
|
||||
public BreakdownChartItem(string label, double value, Color color)
|
||||
{
|
||||
Label = label ?? throw new ArgumentNullException(nameof(label));
|
||||
Value = value;
|
||||
Color = color;
|
||||
}
|
||||
}
|
||||
}
|
69
src/Spectre.Console/Widgets/Charts/BreakdownTags.cs
Normal file
69
src/Spectre.Console/Widgets/Charts/BreakdownTags.cs
Normal file
@ -0,0 +1,69 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using Spectre.Console.Rendering;
|
||||
|
||||
namespace Spectre.Console
|
||||
{
|
||||
internal sealed class BreakdownTags : Renderable
|
||||
{
|
||||
private readonly List<IBreakdownChartItem> _data;
|
||||
|
||||
public int? Width { get; set; }
|
||||
public CultureInfo? Culture { get; set; }
|
||||
public bool ShowPercentages { get; set; }
|
||||
public bool ShowTagValues { get; set; } = true;
|
||||
|
||||
public BreakdownTags(List<IBreakdownChartItem> data)
|
||||
{
|
||||
_data = data ?? throw new ArgumentNullException(nameof(data));
|
||||
}
|
||||
|
||||
protected override Measurement Measure(RenderContext context, int maxWidth)
|
||||
{
|
||||
var width = Math.Min(Width ?? maxWidth, maxWidth);
|
||||
return new Measurement(width, width);
|
||||
}
|
||||
|
||||
protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth)
|
||||
{
|
||||
var culture = Culture ?? CultureInfo.InvariantCulture;
|
||||
|
||||
var panels = new List<Panel>();
|
||||
foreach (var item in _data)
|
||||
{
|
||||
var panel = new Panel(GetTag(item, culture));
|
||||
panel.Inline = true;
|
||||
panel.Padding = new Padding(0, 0);
|
||||
panel.NoBorder();
|
||||
|
||||
panels.Add(panel);
|
||||
}
|
||||
|
||||
foreach (var segment in ((IRenderable)new Columns(panels).Padding(0, 0)).Render(context, maxWidth))
|
||||
{
|
||||
yield return segment;
|
||||
}
|
||||
}
|
||||
|
||||
private string GetTag(IBreakdownChartItem item, CultureInfo culture)
|
||||
{
|
||||
return string.Format(
|
||||
culture, "[{0}]■[/] {1}",
|
||||
item.Color.ToMarkup() ?? "default",
|
||||
FormatValue(item, culture)).Trim();
|
||||
}
|
||||
|
||||
private string FormatValue(IBreakdownChartItem item, CultureInfo culture)
|
||||
{
|
||||
if (ShowTagValues)
|
||||
{
|
||||
return string.Format(culture, "{0} [grey]{1}{2}[/]",
|
||||
item.Label.EscapeMarkup(), item.Value,
|
||||
ShowPercentages ? "%" : string.Empty);
|
||||
}
|
||||
|
||||
return item.Label.EscapeMarkup();
|
||||
}
|
||||
}
|
||||
}
|
23
src/Spectre.Console/Widgets/Charts/IBarChartItem.cs
Normal file
23
src/Spectre.Console/Widgets/Charts/IBarChartItem.cs
Normal 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; }
|
||||
}
|
||||
}
|
23
src/Spectre.Console/Widgets/Charts/IBreakdownChartItem.cs
Normal file
23
src/Spectre.Console/Widgets/Charts/IBreakdownChartItem.cs
Normal file
@ -0,0 +1,23 @@
|
||||
namespace Spectre.Console
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a breakdown chart item.
|
||||
/// </summary>
|
||||
public interface IBreakdownChartItem
|
||||
{
|
||||
/// <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; }
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user