From bc9e2effe5939317b8a5c2289763c0539cb58e3b Mon Sep 17 00:00:00 2001 From: Sunny Date: Wed, 4 Oct 2023 11:33:07 +0800 Subject: [PATCH] =?UTF-8?q?*=20UILineChart:=20=E5=A2=9E=E5=8A=A0=E4=BA=86Y?= =?UTF-8?q?=E8=BD=B4=E6=95=B0=E6=8D=AE=E7=94=B1=E4=B8=8A=E5=90=91=E4=B8=8B?= =?UTF-8?q?=E7=BB=98=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SunnyUI/Charts/UILineChart.cs | 48 ++++++++++++++++++----------- SunnyUI/Charts/UILineChartOption.cs | 12 ++++++-- SunnyUI/Charts/UIScale.cs | 18 +++++++---- 3 files changed, 52 insertions(+), 26 deletions(-) diff --git a/SunnyUI/Charts/UILineChart.cs b/SunnyUI/Charts/UILineChart.cs index 01465f0e..6eb7dc73 100644 --- a/SunnyUI/Charts/UILineChart.cs +++ b/SunnyUI/Charts/UILineChart.cs @@ -48,6 +48,7 @@ * 2023-07-02: V3.3.9 增加PointFormat,鼠标选中值显示格式化事件 * 2023-07-02: V3.3.9 增加了数据沿Y轴变化时鼠标移动到数据点时显示数据点标签 * 2023-07-14: V3.4.0 增加了坐标轴绘制时显示箭头,并在箭头处显示数量单位的功能 + * 2023-10-04: V3.5.0 增加了Y轴数据由上向下绘制 ******************************************************************************/ using System; @@ -77,7 +78,7 @@ namespace Sunny.UI } [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)] 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) { if (series.IsY2) - series.CalcData(this, XScale, Y2Scale); + series.CalcData(this, XScale, Y2Scale, Option.YDataOrder); else - series.CalcData(this, XScale, YScale); + series.CalcData(this, XScale, YScale, Option.YDataOrder); if (series is UISwitchLineSeries lineSeries) { lineSeries.YOffsetPos = series.IsY2 ? - Y2Scale.CalcYPixel(lineSeries.YOffset, DrawOrigin.Y, DrawSize.Height) : - YScale.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, 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); 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 (Option.ShowZeroLine) @@ -395,13 +396,19 @@ namespace Sunny.UI if (xx > xr && xx + sf.Width < 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) { - 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; @@ -424,7 +431,7 @@ namespace Sunny.UI //Y Tick { 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; for (int i = 0; i < labels.Length; i++) { @@ -474,7 +481,7 @@ namespace Sunny.UI if (Option.HaveY2) { 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; for (int i = 0; i < labels.Length; i++) { @@ -644,7 +651,7 @@ namespace Sunny.UI 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) { wTop = Option.Grid.Top; @@ -660,7 +667,7 @@ namespace Sunny.UI 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) { wBottom = Height - Option.Grid.Bottom; @@ -770,7 +777,7 @@ namespace Sunny.UI { 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; @@ -794,7 +801,7 @@ namespace Sunny.UI { 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; using (Pen pn = new Pen(line.Color, line.Size)) @@ -1085,13 +1092,18 @@ namespace Sunny.UI var zoomArea = new ZoomArea(); 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.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) { - zoomArea.Y2Max = Y2Scale.CalcYPos(Math.Min(StartPoint.Y, StopPoint.Y), DrawOrigin.Y, DrawSize.Height); - zoomArea.Y2Min = Y2Scale.CalcYPos(Math.Max(StartPoint.Y, StopPoint.Y), DrawOrigin.Y, DrawSize.Height); + y1 = Y2Scale.CalcYPos(Math.Min(StartPoint.Y, StopPoint.Y), DrawOrigin.Y, DrawSize.Height, Option.YDataOrder); + 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); diff --git a/SunnyUI/Charts/UILineChartOption.cs b/SunnyUI/Charts/UILineChartOption.cs index cfbe409b..3bf99dbd 100644 --- a/SunnyUI/Charts/UILineChartOption.cs +++ b/SunnyUI/Charts/UILineChartOption.cs @@ -38,6 +38,12 @@ namespace Sunny.UI Y } + public enum UIYDataOrder + { + Asc, + Desc + } + public sealed class UILineOption : UIOption, IDisposable { public bool ShowZeroLine { get; set; } = true; @@ -52,6 +58,8 @@ namespace Sunny.UI public UILineToolTip ToolTip { get; private set; } = new UILineToolTip(); + public UIYDataOrder YDataOrder { get; set; } = UIYDataOrder.Asc; + /// /// 析构函数 /// @@ -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(); 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); } diff --git a/SunnyUI/Charts/UIScale.cs b/SunnyUI/Charts/UIScale.cs index 4cfffa55..e1fdfb5c 100644 --- a/SunnyUI/Charts/UIScale.cs +++ b/SunnyUI/Charts/UIScale.cs @@ -56,14 +56,20 @@ namespace Sunny.UI 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) @@ -81,7 +87,7 @@ namespace Sunny.UI 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; float[] result = new float[labels.Length]; @@ -90,7 +96,7 @@ namespace Sunny.UI if (labels[i].IsInfinity() || labels[i].IsNan()) result[i] = float.NaN; else - result[i] = CalcYPixel(labels[i], origin, height); + result[i] = CalcYPixel(labels[i], origin, height, order); } return result;