From 0fd72999620582da175a6f1d4ad9af1f00bedfe9 Mon Sep 17 00:00:00 2001 From: Sunny Date: Sat, 30 Jul 2022 10:02:13 +0800 Subject: [PATCH] =?UTF-8?q?*=20UILineChart:=20=E6=95=B0=E6=8D=AE=E6=98=BE?= =?UTF-8?q?=E7=A4=BA=E7=9A=84=E5=B0=8F=E6=95=B0=E4=BD=8D=E6=95=B0=E9=87=8D?= =?UTF-8?q?=E6=9E=84=E8=B0=83=E6=95=B4=E8=87=B3=E6=95=B0=E6=8D=AE=E5=BA=8F?= =?UTF-8?q?=E5=88=97Series.XAxisDecimalPlaces=EF=BC=8CXAxisDateTimeFormat?= =?UTF-8?q?=EF=BC=8CYAxisDecimalPlaces?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SunnyUI/Charts/UIBarChartOption.cs | 271 ---------------------------- SunnyUI/Charts/UILineChart.cs | 60 ++---- SunnyUI/Charts/UILineChartOption.cs | 80 ++++++-- SunnyUI/Charts/UIOption.cs | 238 ++++++++++++++++++++++++ 4 files changed, 314 insertions(+), 335 deletions(-) diff --git a/SunnyUI/Charts/UIBarChartOption.cs b/SunnyUI/Charts/UIBarChartOption.cs index 0021c07d..c96a48cf 100644 --- a/SunnyUI/Charts/UIBarChartOption.cs +++ b/SunnyUI/Charts/UIBarChartOption.cs @@ -138,277 +138,6 @@ namespace Sunny.UI Line, Shadow } - public class UIAxis - { - public UIAxis() - { - - } - - public UIAxis(UIAxisType axisType) - { - Type = axisType; - } - - public string Name { get; set; } - - public UIAxisType Type { get; } - - /// - /// 坐标轴的分割段数,需要注意的是这个分割段数只是个预估值 - /// 最后实际显示的段数会在这个基础上根据分割后坐标轴刻度显示的易读程度作调整。 - /// 在类目轴中无效。 - /// - public int SplitNumber { get; set; } = 5; - - /// - /// 是否是脱离 0 值比例。设置成 true 后坐标刻度不会强制包含零刻度。在双数值轴的散点图中比较有用。 - /// - public bool Scale { get; set; } - - /// - /// 坐标轴刻度 - /// - public UIAxisTick AxisTick = new UIAxisTick(); - - /// - /// 坐标轴标签 - /// - public UIAxisLabel AxisLabel = new UIAxisLabel(); - - public bool MaxAuto { get; set; } = true; - public bool MinAuto { get; set; } = true; - - public double Max { get; set; } = 100; - public double Min { get; set; } = 0; - - public List Data = new List(); - - public bool ShowGridLine { get; set; } = true; - - public void Clear() - { - Data.Clear(); - } - - public CustomLabels CustomLabels; - - public bool HaveCustomLabels - { - get => CustomLabels != null && CustomLabels.Count > 0; - } - - public void SetMinValue(double min) - { - Min = min; - MinAuto = false; - } - - public void SetMaxValue(double max) - { - Max = max; - MaxAuto = false; - } - - public void SetRange(double min, double max) - { - SetMinValue(min); - SetMaxValue(max); - } - - public void SetMinValue(DateTime min) - { - Min = new DateTimeInt64(min); - MinAuto = false; - } - - public void SetMaxValue(DateTime max) - { - Max = new DateTimeInt64(max); - MaxAuto = false; - } - - public void SetRange(DateTime min, DateTime max) - { - SetMinValue(min); - SetMaxValue(max); - } - } - - public class CustomLabels - { - public double Start { get; set; } - - public double Interval { get; set; } - - public int Count { get; set; } - - public double IntervalMilliseconds { get; set; } - - public UIAxisType AxisType { get; set; } - - public List Labels = new List(); - - public double[] LabelValues() - { - double[] values = new double[Count + 1]; - - for (int i = 0; i <= Count; i++) - { - if (AxisType == UIAxisType.DateTime) - { - DateTimeInt64 dateTime = new DateTimeInt64(Start); - dateTime.AddMilliseconds(IntervalMilliseconds * i); - values[i] = dateTime.DoubleValue; - } - else - { - values[i] = Start + Interval * i; - } - } - - return values; - } - - public double Stop - { - get - { - if (AxisType == UIAxisType.DateTime) - { - DateTimeInt64 dateTime = new DateTimeInt64(Start); - dateTime.AddMilliseconds(IntervalMilliseconds * Count); - return dateTime.DoubleValue; - } - else - { - return Start + Interval * Count; - } - } - } - - public CustomLabels(double start, double interval, int count) - { - Start = start; - Interval = Math.Abs(interval); - Count = Math.Max(2, count); - AxisType = UIAxisType.Value; - } - - public CustomLabels(DateTime start, int intervalMilliseconds, int count) - { - Start = new DateTimeInt64(start); - IntervalMilliseconds = Math.Abs(intervalMilliseconds); - Count = Math.Max(2, count); - AxisType = UIAxisType.DateTime; - } - - public void SetLabels(string[] labels) - { - Labels.Clear(); - Labels.AddRange(labels); - } - - public void AddLabel(string label) - { - Labels.Add(label); - } - - public void ClearLabels() - { - Labels.Clear(); - } - - public string GetLabel(int i) - { - if (i < Labels.Count) return Labels[i]; - else return string.Empty; - } - } - - public class UIAxisLabel - { - /// - /// 是否显示刻度标签。 - /// - public bool Show { get; set; } = true; - - /// - /// 坐标轴刻度的显示间隔,在类目轴中有效。默认同 axisLabel.interval 一样。 - /// 默认会采用标签不重叠的策略间隔显示标签。 - /// 可以设置成 0 强制显示所有标签。 - /// 如果设置为 1,表示『隔一个标签显示一个标签』,如果值为 2,表示隔两个标签显示一个标签,以此类推。 - /// - public int Interval { get; set; } = 0; - - public delegate string DoFormatter(double value, int index); - - public event DoFormatter Formatter; - - public int Angle { get; set; } = 0; - - public string GetLabel(double value, int index, UIAxisType axisType = UIAxisType.Value) - { - switch (axisType) - { - case UIAxisType.Value: - return Formatter != null ? Formatter?.Invoke(value, index) : value.ToString("F" + DecimalCount); - case UIAxisType.DateTime: - DateTimeInt64 dt = new DateTimeInt64((long)value); - return Formatter != null ? Formatter?.Invoke(dt, index) : (DateTimeFormat.IsNullOrEmpty() ? dt.ToString() : dt.ToString(DateTimeFormat)); - case UIAxisType.Category: - return Formatter != null ? Formatter?.Invoke(value, index) : value.ToString("F0"); - } - - return value.ToString("F2"); - } - - public string GetAutoLabel(double value, int decimalCount) - { - return value.ToString("F" + decimalCount); - } - - /// - /// 小数位个数,Formatter不为空时以Formatter为准 - /// - public int DecimalCount { get; set; } = 0; - - /// - /// 日期格式化字符串,Formatter不为空时以Formatter为准 - /// - public string DateTimeFormat { get; set; } = "HH:mm"; - - public bool AutoFormat { get; set; } = true; - } - - public class UIAxisTick - { - /// - /// 类目轴中在为 true 的时候有效,可以保证刻度线和标签对齐。 - /// - public bool AlignWithLabel { get; set; } - - /// - /// 是否显示坐标轴刻度。 - /// - public bool Show { get; set; } = true; - - /// - /// 坐标轴刻度的长度。 - /// - public int Length { get; set; } = 5; - - /// - /// 坐标轴刻度的显示间隔,在类目轴中有效。默认同 axisLabel.interval 一样。 - /// 默认会采用标签不重叠的策略间隔显示标签。 - /// 可以设置成 0 强制显示所有标签。 - /// 如果设置为 1,表示『隔一个标签显示一个标签』,如果值为 2,表示隔两个标签显示一个标签,以此类推。 - /// - public int Interval { get; set; } = 0; - - public int Distance { get; set; } = 0; - } - public class UIBarSeries : IDisposable { public UIBarSeries(int decimalPlaces = 0) diff --git a/SunnyUI/Charts/UILineChart.cs b/SunnyUI/Charts/UILineChart.cs index 3d8b0d12..f746a426 100644 --- a/SunnyUI/Charts/UILineChart.cs +++ b/SunnyUI/Charts/UILineChart.cs @@ -34,6 +34,7 @@ * 2022-04-19: V3.1.5 关闭Smooth绘制,数值差距大或者持续缩放会出错 * 2022-07-11: V3.2.1 修改两个点时可以不显示连接线 * 2022-07-26: V3.2.2 修复双Y轴数据点提示文字显示 + * 2022-07-30: V3.2.2 数据显示的小数位数重构调整至数据序列Series.XAxisDecimalPlaces,XAxisDateTimeFormat,YAxisDecimalPlaces ******************************************************************************/ using System; @@ -457,32 +458,15 @@ namespace Sunny.UI return; } - //if (series.Points.Count == 2) - //{ - // using (Pen pen = new Pen(color, series.Width)) - // { - // g.DrawTwoPoints(pen, series.Points[0], series.Points[1], DrawRect); - // } - // - // return; - //} - if (series.ShowLine || series.Symbol == UILinePointSymbol.None) { using (Pen pen = new Pen(color, series.Width)) { g.SetHighQuality(); - //if (series.ContainsNan || !series.Smooth) - //{ for (int i = 0; i < series.Points.Count - 1; i++) { g.DrawTwoPoints(pen, series.Points[i], series.Points[i + 1], DrawRect); } - //} - //else - //{ - // g.DrawCurve(pen, series.Points.ToArray()); - //} g.SetDefaultQuality(); } @@ -498,21 +482,6 @@ namespace Sunny.UI bmp?.Dispose(); bmp = new Bitmap(Width, Height); - //using (Graphics graphics = bmp.Graphics()) - //{ - // graphics.FillRectangle(FillColor, 0, 0, Width, Height); - //} - // - //using (Graphics graphics = bmpGreater.Graphics()) - //{ - // graphics.FillRectangle(FillColor, 0, 0, Width, Height); - //} - // - //using (Graphics graphics = bmpLess.Graphics()) - //{ - // graphics.FillRectangle(FillColor, 0, 0, Width, Height); - //} - int idx = 0; float wTop = Option.Grid.Top; float wBottom = Height - Option.Grid.Bottom; @@ -804,13 +773,11 @@ namespace Sunny.UI if (series.GetNearestPoint(e.Location, 4, out double x, out double y, out int index)) { UILineSelectPoint point = new UILineSelectPoint(); - point.SeriesIndex = series.Index; - point.Name = series.Name; + point.Series = series; point.Index = index; point.X = x; point.Y = y; point.Location = new Point((int)series.Points[index].X, (int)series.Points[index].Y); - point.IsY2 = series.IsY2; selectPointsTemp.Add(point); } } @@ -822,16 +789,16 @@ namespace Sunny.UI } else { - Dictionary points = selectPoints.ToDictionary(p => p.Name); + Dictionary points = selectPoints.ToDictionary(p => p.Series.Name); foreach (var point in selectPointsTemp) { - if (!points.ContainsKey(point.Name)) + if (!points.ContainsKey(point.Series.Name)) { isNew = true; break; } - if (points[point.Name].Index != point.Index) + if (points[point.Series.Name].Index != point.Index) { isNew = true; break; @@ -844,7 +811,7 @@ namespace Sunny.UI selectPoints.Clear(); StringBuilder sb = new StringBuilder(); int idx = 0; - Dictionary dictionary = selectPointsTemp.ToDictionary(p => p.SeriesIndex); + Dictionary dictionary = selectPointsTemp.ToDictionary(p => p.Series.Index); List points = dictionary.SortedValues(); foreach (var point in points) { @@ -852,19 +819,22 @@ namespace Sunny.UI if (idx > 0) sb.Append('\n'); - sb.Append(point.Name); + sb.Append(point.Series.Name); sb.Append('\n'); sb.Append(Option.XAxis.Name + ": "); + if (Option.XAxisType == UIAxisType.DateTime) - sb.Append(new DateTimeInt64(point.X).ToString(Option.XAxis.AxisLabel.DateTimeFormat)); + sb.Append(new DateTimeInt64(point.X).ToString(point.Series.XAxisDateTimeFormat.IsValid() ? point.Series.XAxisDateTimeFormat : XScale.Format)); else - sb.Append(point.X.ToString("F" + Option.XAxis.AxisLabel.DecimalCount)); + sb.Append(point.X.ToString(point.Series.XAxisDecimalPlaces >= 0 ? "F" + point.Series.XAxisDecimalPlaces : XScale.Format)); + sb.Append('\n'); - if (point.IsY2) - sb.Append(Option.Y2Axis.Name + ": " + point.Y.ToString("F" + Option.Y2Axis.AxisLabel.DecimalCount)); + if (point.Series.IsY2) + sb.Append(Option.Y2Axis.Name + ": " + point.Y.ToString(point.Series.YAxisDecimalPlaces >= 0 ? "F" + point.Series.YAxisDecimalPlaces : Y2Scale.Format)); else - sb.Append(Option.YAxis.Name + ": " + point.Y.ToString("F" + Option.YAxis.AxisLabel.DecimalCount)); + sb.Append(Option.YAxis.Name + ": " + point.Y.ToString(point.Series.YAxisDecimalPlaces >= 0 ? "F" + point.Series.YAxisDecimalPlaces : YScale.Format)); + idx++; } diff --git a/SunnyUI/Charts/UILineChartOption.cs b/SunnyUI/Charts/UILineChartOption.cs index 66a78333..6801e3fb 100644 --- a/SunnyUI/Charts/UILineChartOption.cs +++ b/SunnyUI/Charts/UILineChartOption.cs @@ -304,6 +304,66 @@ namespace Sunny.UI public class UILineSeries { + public UILineSeries(string name, bool isY2 = false) + { + Name = name; + Color = UIColor.Blue; + IsY2 = isY2; + } + + public UILineSeries(string name, Color color, bool isY2 = false) + { + Name = name; + Color = color; + CustomColor = true; + IsY2 = isY2; + } + + public void SetValueFormat(int xAxisDecimalPlaces, int yAxisDecimalPlaces) + { + XAxisDecimalPlaces = xAxisDecimalPlaces; + YAxisDecimalPlaces = yAxisDecimalPlaces; + } + + public void SetValueFormat(string xAxisDateTimeFormat, int yAxisDecimalPlaces) + { + XAxisDateTimeFormat = xAxisDateTimeFormat; + YAxisDecimalPlaces = yAxisDecimalPlaces; + } + + private int _xAxisDecimalPlaces = -1; + public int XAxisDecimalPlaces + { + get => _xAxisDecimalPlaces; + set => _xAxisDecimalPlaces = Math.Max(0, value); + } + + private int _yAxisDecimalPlaces = -1; + public int YAxisDecimalPlaces + { + get => _yAxisDecimalPlaces; + set => _yAxisDecimalPlaces = Math.Max(0, value); + } + + private string _dateTimeFormat = ""; + + public string XAxisDateTimeFormat + { + get => _dateTimeFormat; + set + { + try + { + DateTime.Now.ToString(value); + _dateTimeFormat = value; + } + catch + { + _dateTimeFormat = ""; + } + } + } + public int Index { get; set; } public string Name { get; private set; } @@ -336,21 +396,6 @@ namespace Sunny.UI public bool Visible { get; set; } = true; - public UILineSeries(string name, bool isY2 = false) - { - Name = name; - Color = UIColor.Blue; - IsY2 = isY2; - } - - public UILineSeries(string name, Color color, bool isY2 = false) - { - Name = name; - Color = color; - CustomColor = true; - IsY2 = isY2; - } - public readonly List XData = new List(); public readonly List YData = new List(); @@ -483,9 +528,6 @@ namespace Sunny.UI public struct UILineSelectPoint { - public int SeriesIndex { get; set; } - public string Name { get; set; } - public int Index { get; set; } public double X { get; set; } @@ -494,7 +536,7 @@ namespace Sunny.UI public Point Location { get; set; } - public bool IsY2 { get; set; } + public UILineSeries Series { get; set; } } public class UILineWarningArea diff --git a/SunnyUI/Charts/UIOption.cs b/SunnyUI/Charts/UIOption.cs index 9b11424d..dba999a2 100644 --- a/SunnyUI/Charts/UIOption.cs +++ b/SunnyUI/Charts/UIOption.cs @@ -38,6 +38,244 @@ namespace Sunny.UI public string Formatter { get; set; } } + public class UIAxis + { + public UIAxis() + { + + } + + public UIAxis(UIAxisType axisType) + { + Type = axisType; + } + + public string Name { get; set; } + + public UIAxisType Type { get; } + + /// + /// 坐标轴的分割段数,需要注意的是这个分割段数只是个预估值 + /// 最后实际显示的段数会在这个基础上根据分割后坐标轴刻度显示的易读程度作调整。 + /// 在类目轴中无效。 + /// + public int SplitNumber { get; set; } = 5; + + /// + /// 是否是脱离 0 值比例。设置成 true 后坐标刻度不会强制包含零刻度。在双数值轴的散点图中比较有用。 + /// + public bool Scale { get; set; } + + /// + /// 坐标轴刻度 + /// + public UIAxisTick AxisTick = new UIAxisTick(); + + /// + /// 坐标轴标签 + /// + public UIAxisLabel AxisLabel = new UIAxisLabel(); + + public bool MaxAuto { get; set; } = true; + public bool MinAuto { get; set; } = true; + + public double Max { get; set; } = 100; + public double Min { get; set; } = 0; + + public List Data = new List(); + + public bool ShowGridLine { get; set; } = true; + + public void Clear() + { + Data.Clear(); + } + + public CustomLabels CustomLabels; + + public bool HaveCustomLabels + { + get => CustomLabels != null && CustomLabels.Count > 0; + } + + public void SetMinValue(double min) + { + Min = min; + MinAuto = false; + } + + public void SetMaxValue(double max) + { + Max = max; + MaxAuto = false; + } + + public void SetRange(double min, double max) + { + SetMinValue(min); + SetMaxValue(max); + } + + public void SetMinValue(DateTime min) + { + Min = new DateTimeInt64(min); + MinAuto = false; + } + + public void SetMaxValue(DateTime max) + { + Max = new DateTimeInt64(max); + MaxAuto = false; + } + + public void SetRange(DateTime min, DateTime max) + { + SetMinValue(min); + SetMaxValue(max); + } + } + + public class CustomLabels + { + public double Start { get; set; } + + public double Interval { get; set; } + + public int Count { get; set; } + + public double IntervalMilliseconds { get; set; } + + public UIAxisType AxisType { get; set; } + + public List Labels = new List(); + + public double[] LabelValues() + { + double[] values = new double[Count + 1]; + + for (int i = 0; i <= Count; i++) + { + if (AxisType == UIAxisType.DateTime) + { + DateTimeInt64 dateTime = new DateTimeInt64(Start); + dateTime.AddMilliseconds(IntervalMilliseconds * i); + values[i] = dateTime.DoubleValue; + } + else + { + values[i] = Start + Interval * i; + } + } + + return values; + } + + public double Stop + { + get + { + if (AxisType == UIAxisType.DateTime) + { + DateTimeInt64 dateTime = new DateTimeInt64(Start); + dateTime.AddMilliseconds(IntervalMilliseconds * Count); + return dateTime.DoubleValue; + } + else + { + return Start + Interval * Count; + } + } + } + + public CustomLabels(double start, double interval, int count) + { + Start = start; + Interval = Math.Abs(interval); + Count = Math.Max(2, count); + AxisType = UIAxisType.Value; + } + + public CustomLabels(DateTime start, int intervalMilliseconds, int count) + { + Start = new DateTimeInt64(start); + IntervalMilliseconds = Math.Abs(intervalMilliseconds); + Count = Math.Max(2, count); + AxisType = UIAxisType.DateTime; + } + + public void SetLabels(string[] labels) + { + Labels.Clear(); + Labels.AddRange(labels); + } + + public void AddLabel(string label) + { + Labels.Add(label); + } + + public void ClearLabels() + { + Labels.Clear(); + } + + public string GetLabel(int i) + { + if (i < Labels.Count) return Labels[i]; + else return string.Empty; + } + } + + public class UIAxisLabel + { + /// + /// 是否显示刻度标签。 + /// + public bool Show { get; set; } = true; + + public int Angle { get; set; } = 0; + + /// + /// 小数位个数,Formatter不为空时以Formatter为准 + /// + public int DecimalCount { get; set; } = 0; + + /// + /// 日期格式化字符串,Formatter不为空时以Formatter为准 + /// + public string DateTimeFormat { get; set; } = "HH:mm"; + + public bool AutoFormat { get; set; } = true; + } + + public class UIAxisTick + { + /// + /// 类目轴中在为 true 的时候有效,可以保证刻度线和标签对齐。 + /// + public bool AlignWithLabel { get; set; } + + /// + /// 是否显示坐标轴刻度。 + /// + public bool Show { get; set; } = true; + + /// + /// 坐标轴刻度的长度。 + /// + public int Length { get; set; } = 5; + + /// + /// 坐标轴刻度的显示间隔,在类目轴中有效。默认同 axisLabel.interval 一样。 + /// 默认会采用标签不重叠的策略间隔显示标签。 + /// 可以设置成 0 强制显示所有标签。 + /// 如果设置为 1,表示『隔一个标签显示一个标签』,如果值为 2,表示隔两个标签显示一个标签,以此类推。 + /// + public int Interval { get; set; } = 0; + + public int Distance { get; set; } = 0; + } + public class UIScaleLine { public double Value { get; set; }