using System.Collections.ObjectModel;
using System.Collections.Specialized;
using Eto.Drawing;
using Eto.Forms;
namespace DiscImageChef.Gui.Controls
{
///
/// Draws a line chart
///
public class LineChart : Drawable
{
bool absoluteMargins;
Color axesColor;
Color backgroundColor;
Color colorX;
Color colorY;
bool drawAxes;
Color lineColor;
float marginX;
float marginXrated;
float marginY;
float marginYrated;
float maxX;
float maxY;
float minX;
float minY;
PointF previousPoint;
float ratioX;
float ratioY;
RectangleF rect;
bool showStepsX;
bool showStepsY;
float stepsX;
float stepsY;
public LineChart()
{
Values = new ObservableCollection();
Values.CollectionChanged += OnValuesChanged;
showStepsX = true;
stepsX = 5;
showStepsY = true;
stepsY = 5;
minX = 0;
maxX = 100;
minY = 0;
maxY = 100;
backgroundColor = Colors.Transparent;
colorX = Colors.DarkGray;
colorY = Colors.DarkGray;
lineColor = Colors.Red;
axesColor = Colors.Black;
drawAxes = true;
marginX = 5;
marginY = 5;
absoluteMargins = true;
previousPoint = new PointF(0, 0);
}
///
/// If set the margins would be in absolute pixels, otherwise in relative points
///
public bool AbsoluteMargins
{
get => absoluteMargins;
set
{
if(absoluteMargins == value) return;
absoluteMargins = value;
Invalidate();
}
}
///
/// Margin between the leftmost border and the Y axis
///
public float MarginX
{
get => marginX;
set
{
marginX = value;
Invalidate();
}
}
///
/// Margin between the bottommost border and the X axis
///
public float MarginY
{
get => marginY;
set
{
marginY = value;
Invalidate();
}
}
///
/// Contains the relative poitns to be drawn
///
public ObservableCollection Values { get; }
///
/// If axes borders should be drawn
///
public bool DrawAxes
{
get => drawAxes;
set
{
if(drawAxes == value) return;
drawAxes = value;
Invalidate();
}
}
///
/// If a grid should be drawn every X step
///
public bool ShowStepsX
{
get => showStepsX;
set
{
if(showStepsX == value) return;
showStepsX = value;
Invalidate();
}
}
///
/// Separation between X grid lines
///
public float StepsX
{
get => stepsX;
set
{
stepsX = value;
Invalidate();
}
}
///
/// If a grid should be drawn every Y step
///
public bool ShowStepsY
{
get => showStepsY;
set
{
if(showStepsY == value) return;
showStepsY = value;
Invalidate();
}
}
///
/// Separation between X grid lines
///
public float StepsY
{
get => stepsY;
set
{
stepsY = value;
Invalidate();
}
}
///
/// Relative point that is equal to start of X
///
public float MinX
{
get => minX;
set
{
minX = value;
Invalidate();
}
}
///
/// Relative point that is equal to start of Y
///
public float MinY
{
get => minY;
set
{
minY = value;
Invalidate();
}
}
///
/// Relative point that is equal to end of X
///
public float MaxX
{
get => maxX;
set
{
maxX = value;
Invalidate();
}
}
///
/// Relative point that is equal to end of Y
///
public float MaxY
{
get => maxY;
set
{
maxY = value;
Invalidate();
}
}
///
/// Color for background
///
public new Color BackgroundColor
{
get => backgroundColor;
set
{
if(backgroundColor == value) return;
backgroundColor = value;
Invalidate();
}
}
///
/// Color to draw the axes borders
///
public Color AxesColor
{
get => axesColor;
set
{
if(axesColor == value) return;
axesColor = value;
Invalidate();
}
}
///
/// Color to draw the X grid
///
public Color ColorX
{
get => colorX;
set
{
if(colorX == value) return;
colorX = value;
Invalidate();
}
}
///
/// Color to draw the Y grid
///
public Color ColorY
{
get => colorY;
set
{
if(colorY == value) return;
colorY = value;
Invalidate();
}
}
///
/// Color to draw the line between points
///
public Color LineColor
{
get => lineColor;
set
{
if(lineColor == value) return;
lineColor = value;
Invalidate();
}
}
void OnValuesChanged(object sender, NotifyCollectionChangedEventArgs args)
{
// If we do not support to drawn on the graphics we will need to redraw it, slowly
if(!SupportsCreateGraphics) Invalidate();
Graphics g;
// If the control is not visible (hidden in another tab) this raises an exception
try { g = CreateGraphics(); }
catch { return; }
switch(args.Action)
{
// Draw only next point
case NotifyCollectionChangedAction.Add:
foreach(object item in args.NewItems)
{
if(!(item is PointF nextPoint)) continue;
float prevXrated = previousPoint.X * ratioX;
float prevYrated = previousPoint.Y * ratioY;
float nextXrated = nextPoint.X * ratioX;
float nextYrated = nextPoint.Y * ratioY;
if(prevYrated <= 0) prevYrated = nextYrated;
g.DrawLine(lineColor, marginXrated + prevXrated, rect.Height - marginYrated - prevYrated,
marginXrated + nextXrated, rect.Height - marginYrated - nextYrated);
previousPoint = nextPoint;
}
break;
// Need to redraw all points
default:
Invalidate();
break;
}
g.Dispose();
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
Graphics g = e.Graphics;
rect = e.ClipRectangle;
g.FillRectangle(backgroundColor, rect);
ratioX = rect.Width / (maxX - minX);
ratioY = rect.Height / (maxY - minY);
marginXrated = marginX * (absoluteMargins ? 1 : ratioX);
marginYrated = marginY * (absoluteMargins ? 1 : ratioY);
if(drawAxes)
{
g.DrawLine(axesColor, marginXrated, 0, marginXrated, rect.Height);
g.DrawLine(axesColor, 0, rect.Height - marginYrated, rect.Width,
rect.Height - marginYrated);
}
if(showStepsX)
{
float stepsXraged = stepsX * ratioX;
for(float x = marginXrated + stepsXraged; x < rect.Width;
x += stepsXraged)
g.DrawLine(colorX, x, 0, x, rect.Height - marginYrated - 1);
}
if(showStepsY)
{
float stepsYraged = stepsY * ratioY;
for(float y = rect.Height - marginYrated - stepsYraged; y > 0; y -= stepsYraged)
g.DrawLine(colorY, marginXrated + 1, y, rect.Width, y);
}
previousPoint = new PointF(0, 0);
foreach(Point nextPoint in Values)
{
float prevXrated = previousPoint.X * ratioX;
float prevYrated = previousPoint.Y * ratioY;
float nextXrated = nextPoint.X * ratioX;
float nextYrated = nextPoint.Y * ratioY;
g.DrawLine(lineColor, marginXrated + prevXrated, rect.Height - marginYrated - prevYrated,
marginXrated + nextXrated, rect.Height - marginYrated - nextYrated);
previousPoint = nextPoint;
}
}
}
}