* UILineChart: 增加了Y轴数据由上向下绘制

This commit is contained in:
Sunny 2023-10-04 11:33:07 +08:00
parent 069ed8c606
commit bc9e2effe5
3 changed files with 52 additions and 26 deletions

View File

@ -48,6 +48,7 @@
* 2023-07-02: V3.3.9 PointFormat * 2023-07-02: V3.3.9 PointFormat
* 2023-07-02: V3.3.9 沿Y轴变化时鼠标移动到数据点时显示数据点标签 * 2023-07-02: V3.3.9 沿Y轴变化时鼠标移动到数据点时显示数据点标签
* 2023-07-14: V3.4.0 * 2023-07-14: V3.4.0
* 2023-10-04: V3.5.0 Y轴数据由上向下绘制
******************************************************************************/ ******************************************************************************/
using System; using System;
@ -77,7 +78,7 @@ namespace Sunny.UI
} }
[Browsable(false)] [Browsable(false)]
public Point DrawOrigin => new Point(Option.Grid.Left, Height - Option.Grid.Bottom); public Point DrawOrigin => new Point(Option.Grid.Left, Option.YDataOrder == UIYDataOrder.Asc ? Height - Option.Grid.Bottom : Option.Grid.Top);
[Browsable(false)] [Browsable(false)]
public Size DrawSize => new Size(Width - Option.Grid.Left - Option.Grid.Right, Height - Option.Grid.Top - Option.Grid.Bottom); public Size DrawSize => new Size(Width - Option.Grid.Left - Option.Grid.Right, Height - Option.Grid.Top - Option.Grid.Bottom);
@ -98,15 +99,15 @@ namespace Sunny.UI
foreach (var series in Option.Series.Values) foreach (var series in Option.Series.Values)
{ {
if (series.IsY2) if (series.IsY2)
series.CalcData(this, XScale, Y2Scale); series.CalcData(this, XScale, Y2Scale, Option.YDataOrder);
else else
series.CalcData(this, XScale, YScale); series.CalcData(this, XScale, YScale, Option.YDataOrder);
if (series is UISwitchLineSeries lineSeries) if (series is UISwitchLineSeries lineSeries)
{ {
lineSeries.YOffsetPos = series.IsY2 ? lineSeries.YOffsetPos = series.IsY2 ?
Y2Scale.CalcYPixel(lineSeries.YOffset, DrawOrigin.Y, DrawSize.Height) : Y2Scale.CalcYPixel(lineSeries.YOffset, DrawOrigin.Y, DrawSize.Height, Option.YDataOrder) :
YScale.CalcYPixel(lineSeries.YOffset, DrawOrigin.Y, DrawSize.Height); YScale.CalcYPixel(lineSeries.YOffset, DrawOrigin.Y, DrawSize.Height, Option.YDataOrder);
} }
} }
@ -298,7 +299,7 @@ namespace Sunny.UI
g.DrawLine(ForeColor, Option.Grid.Left, Height - Option.Grid.Bottom, Width - Option.Grid.Right, Height - Option.Grid.Bottom); g.DrawLine(ForeColor, Option.Grid.Left, Height - Option.Grid.Bottom, Width - Option.Grid.Right, Height - Option.Grid.Bottom);
using var TempFont = Font.DPIScaleFont(UIStyles.DefaultSubFontSize); using var TempFont = Font.DPIScaleFont(UIStyles.DefaultSubFontSize);
float zeroPos = YScale.CalcYPixel(0, DrawOrigin.Y, DrawSize.Height); float zeroPos = YScale.CalcYPixel(0, DrawOrigin.Y, DrawSize.Height, Option.YDataOrder);
if (zeroPos > Option.Grid.Top && zeroPos < Height - Option.Grid.Bottom) if (zeroPos > Option.Grid.Top && zeroPos < Height - Option.Grid.Bottom)
{ {
if (Option.ShowZeroLine) if (Option.ShowZeroLine)
@ -395,13 +396,19 @@ namespace Sunny.UI
if (xx > xr && xx + sf.Width < Width) if (xx > xr && xx + sf.Width < Width)
{ {
xr = xx + sf.Width; xr = xx + sf.Width;
g.DrawString(label, TempFont, ForeColor, new Rectangle((int)x - Width, DrawOrigin.Y + Option.XAxis.AxisTick.Length, Width * 2, Height), ContentAlignment.TopCenter); if (Option.YDataOrder == UIYDataOrder.Asc)
g.DrawString(label, TempFont, ForeColor, new Rectangle((int)x - Width, DrawOrigin.Y + Option.XAxis.AxisTick.Length, Width * 2, Height), ContentAlignment.TopCenter);
else
g.DrawString(label, TempFont, ForeColor, new Rectangle((int)x - Width, 0, Width * 2, (int)(DrawOrigin.Y - Option.XAxis.AxisTick.Length)), ContentAlignment.BottomCenter);
} }
} }
if (Option.XAxis.AxisTick.Show) if (Option.XAxis.AxisTick.Show)
{ {
g.DrawLine(ForeColor, x, DrawOrigin.Y, x, DrawOrigin.Y + Option.XAxis.AxisTick.Length); if (Option.YDataOrder == UIYDataOrder.Asc)
g.DrawLine(ForeColor, x, DrawOrigin.Y, x, DrawOrigin.Y + Option.XAxis.AxisTick.Length);
else
g.DrawLine(ForeColor, x, DrawOrigin.Y, x, DrawOrigin.Y - Option.XAxis.AxisTick.Length);
} }
if (x.Equals(DrawOrigin.X)) continue; if (x.Equals(DrawOrigin.X)) continue;
@ -424,7 +431,7 @@ namespace Sunny.UI
//Y Tick //Y Tick
{ {
double[] YLabels = Option.YAxis.HaveCustomLabels ? Option.YAxis.CustomLabels.LabelValues() : YScale.CalcLabels(); double[] YLabels = Option.YAxis.HaveCustomLabels ? Option.YAxis.CustomLabels.LabelValues() : YScale.CalcLabels();
float[] labels = YScale.CalcYPixels(YLabels, DrawOrigin.Y, DrawSize.Height); float[] labels = YScale.CalcYPixels(YLabels, DrawOrigin.Y, DrawSize.Height, Option.YDataOrder);
float widthMax = 0; float widthMax = 0;
for (int i = 0; i < labels.Length; i++) for (int i = 0; i < labels.Length; i++)
{ {
@ -474,7 +481,7 @@ namespace Sunny.UI
if (Option.HaveY2) if (Option.HaveY2)
{ {
double[] Y2Labels = Option.Y2Axis.HaveCustomLabels ? Option.Y2Axis.CustomLabels.LabelValues() : Y2Scale.CalcLabels(); double[] Y2Labels = Option.Y2Axis.HaveCustomLabels ? Option.Y2Axis.CustomLabels.LabelValues() : Y2Scale.CalcLabels();
float[] labels = Y2Scale.CalcYPixels(Y2Labels, DrawOrigin.Y, DrawSize.Height); float[] labels = Y2Scale.CalcYPixels(Y2Labels, DrawOrigin.Y, DrawSize.Height, Option.YDataOrder);
float widthMax = 0; float widthMax = 0;
for (int i = 0; i < labels.Length; i++) for (int i = 0; i < labels.Length; i++)
{ {
@ -644,7 +651,7 @@ namespace Sunny.UI
if (Option.GreaterWarningArea != null) if (Option.GreaterWarningArea != null)
{ {
wTop = YScale.CalcYPixel(Option.GreaterWarningArea.Value, DrawOrigin.Y, DrawSize.Height); wTop = YScale.CalcYPixel(Option.GreaterWarningArea.Value, DrawOrigin.Y, DrawSize.Height, Option.YDataOrder);
if (wTop < Option.Grid.Top) if (wTop < Option.Grid.Top)
{ {
wTop = Option.Grid.Top; wTop = Option.Grid.Top;
@ -660,7 +667,7 @@ namespace Sunny.UI
if (Option.LessWarningArea != null) if (Option.LessWarningArea != null)
{ {
wBottom = YScale.CalcYPixel(Option.LessWarningArea.Value, DrawOrigin.Y, DrawSize.Height); wBottom = YScale.CalcYPixel(Option.LessWarningArea.Value, DrawOrigin.Y, DrawSize.Height, Option.YDataOrder);
if (wBottom > Height - Option.Grid.Bottom) if (wBottom > Height - Option.Grid.Bottom)
{ {
wBottom = Height - Option.Grid.Bottom; wBottom = Height - Option.Grid.Bottom;
@ -770,7 +777,7 @@ namespace Sunny.UI
{ {
foreach (var line in Option.YAxisScaleLines) foreach (var line in Option.YAxisScaleLines)
{ {
float pos = YScale.CalcYPixel(line.Value, DrawOrigin.Y, DrawSize.Height); float pos = YScale.CalcYPixel(line.Value, DrawOrigin.Y, DrawSize.Height, Option.YDataOrder);
if (pos <= Option.Grid.Top || pos >= Height - Option.Grid.Bottom) continue; if (pos <= Option.Grid.Top || pos >= Height - Option.Grid.Bottom) continue;
@ -794,7 +801,7 @@ namespace Sunny.UI
{ {
foreach (var line in Option.Y2AxisScaleLines) foreach (var line in Option.Y2AxisScaleLines)
{ {
float pos = Y2Scale.CalcYPixel(line.Value, DrawOrigin.Y, DrawSize.Height); float pos = Y2Scale.CalcYPixel(line.Value, DrawOrigin.Y, DrawSize.Height, Option.YDataOrder);
if (pos <= Option.Grid.Top || pos >= Height - Option.Grid.Bottom) continue; if (pos <= Option.Grid.Top || pos >= Height - Option.Grid.Bottom) continue;
using (Pen pn = new Pen(line.Color, line.Size)) using (Pen pn = new Pen(line.Color, line.Size))
@ -1085,13 +1092,18 @@ namespace Sunny.UI
var zoomArea = new ZoomArea(); var zoomArea = new ZoomArea();
zoomArea.XMin = XScale.CalcXPos(Math.Min(StartPoint.X, StopPoint.X), DrawOrigin.X, DrawSize.Width); zoomArea.XMin = XScale.CalcXPos(Math.Min(StartPoint.X, StopPoint.X), DrawOrigin.X, DrawSize.Width);
zoomArea.XMax = XScale.CalcXPos(Math.Max(StartPoint.X, StopPoint.X), DrawOrigin.X, DrawSize.Width); zoomArea.XMax = XScale.CalcXPos(Math.Max(StartPoint.X, StopPoint.X), DrawOrigin.X, DrawSize.Width);
zoomArea.YMax = YScale.CalcYPos(Math.Min(StartPoint.Y, StopPoint.Y), DrawOrigin.Y, DrawSize.Height);
zoomArea.YMin = YScale.CalcYPos(Math.Max(StartPoint.Y, StopPoint.Y), DrawOrigin.Y, DrawSize.Height); double y1 = YScale.CalcYPos(Math.Min(StartPoint.Y, StopPoint.Y), DrawOrigin.Y, DrawSize.Height, Option.YDataOrder);
double y2 = YScale.CalcYPos(Math.Max(StartPoint.Y, StopPoint.Y), DrawOrigin.Y, DrawSize.Height, Option.YDataOrder);
zoomArea.YMax = Math.Max(y1, y2);
zoomArea.YMin = Math.Min(y1, y2);
if (Option.HaveY2) if (Option.HaveY2)
{ {
zoomArea.Y2Max = Y2Scale.CalcYPos(Math.Min(StartPoint.Y, StopPoint.Y), DrawOrigin.Y, DrawSize.Height); y1 = Y2Scale.CalcYPos(Math.Min(StartPoint.Y, StopPoint.Y), DrawOrigin.Y, DrawSize.Height, Option.YDataOrder);
zoomArea.Y2Min = Y2Scale.CalcYPos(Math.Max(StartPoint.Y, StopPoint.Y), DrawOrigin.Y, DrawSize.Height); y2 = Y2Scale.CalcYPos(Math.Max(StartPoint.Y, StopPoint.Y), DrawOrigin.Y, DrawSize.Height, Option.YDataOrder);
zoomArea.Y2Max = Math.Max(y1, y2);
zoomArea.Y2Min = Math.Min(y1, y2);
} }
AddZoomArea(zoomArea); AddZoomArea(zoomArea);

View File

@ -38,6 +38,12 @@ namespace Sunny.UI
Y Y
} }
public enum UIYDataOrder
{
Asc,
Desc
}
public sealed class UILineOption : UIOption, IDisposable public sealed class UILineOption : UIOption, IDisposable
{ {
public bool ShowZeroLine { get; set; } = true; public bool ShowZeroLine { get; set; } = true;
@ -52,6 +58,8 @@ namespace Sunny.UI
public UILineToolTip ToolTip { get; private set; } = new UILineToolTip(); public UILineToolTip ToolTip { get; private set; } = new UILineToolTip();
public UIYDataOrder YDataOrder { get; set; } = UIYDataOrder.Asc;
/// <summary> /// <summary>
/// 析构函数 /// 析构函数
/// </summary> /// </summary>
@ -663,11 +671,11 @@ namespace Sunny.UI
} }
} }
internal void CalcData(UILineChart chart, UIScale XScale, UIScale YScale) internal void CalcData(UILineChart chart, UIScale XScale, UIScale YScale, UIYDataOrder order = UIYDataOrder.Asc)
{ {
ClearPoints(); ClearPoints();
float[] x = XScale.CalcXPixels(XData.ToArray(), chart.DrawOrigin.X, chart.DrawSize.Width); float[] x = XScale.CalcXPixels(XData.ToArray(), chart.DrawOrigin.X, chart.DrawSize.Width);
float[] y = YScale.CalcYPixels(YData.ToArray(), chart.DrawOrigin.Y, chart.DrawSize.Height); float[] y = YScale.CalcYPixels(YData.ToArray(), chart.DrawOrigin.Y, chart.DrawSize.Height, order);
AddPoints(x, y); AddPoints(x, y);
} }

View File

@ -56,14 +56,20 @@ namespace Sunny.UI
return (float)(_min + (value - origin) * (_max - _min) * 1.0f / width); return (float)(_min + (value - origin) * (_max - _min) * 1.0f / width);
} }
public float CalcYPixel(double value, int origin, int height) public float CalcYPixel(double value, int origin, int height, UIYDataOrder order = UIYDataOrder.Asc)
{ {
return origin - (float)((value - _min) * 1.0f * height / (_max - _min)); if (order == UIYDataOrder.Asc)
return origin - (float)((value - _min) * 1.0f * height / (_max - _min));
else
return origin + (float)((value - _min) * 1.0f * height / (_max - _min));
} }
public double CalcYPos(double value, int origin, int height) public double CalcYPos(double value, int origin, int height, UIYDataOrder order)
{ {
return (float)(_min + (origin - value) * (_max - _min) * 1.0f / height); if (order == UIYDataOrder.Asc)
return (float)(_min + (origin - value) * (_max - _min) * 1.0f / height);
else
return (float)(_min + (value - origin) * (_max - _min) * 1.0f / height);
} }
public float[] CalcXPixels(double[] labels, int origin, int width) public float[] CalcXPixels(double[] labels, int origin, int width)
@ -81,7 +87,7 @@ namespace Sunny.UI
return result; return result;
} }
public float[] CalcYPixels(double[] labels, int origin, int height) public float[] CalcYPixels(double[] labels, int origin, int height, UIYDataOrder order = UIYDataOrder.Asc)
{ {
if (labels == null) return null; if (labels == null) return null;
float[] result = new float[labels.Length]; float[] result = new float[labels.Length];
@ -90,7 +96,7 @@ namespace Sunny.UI
if (labels[i].IsInfinity() || labels[i].IsNan()) if (labels[i].IsInfinity() || labels[i].IsNan())
result[i] = float.NaN; result[i] = float.NaN;
else else
result[i] = CalcYPixel(labels[i], origin, height); result[i] = CalcYPixel(labels[i], origin, height, order);
} }
return result; return result;