* UILineChart:增加双Y坐标轴

This commit is contained in:
Sunny 2021-12-30 23:17:31 +08:00
parent ea5599fe7d
commit f8a500a65a
8 changed files with 250 additions and 39 deletions

Binary file not shown.

Binary file not shown.

View File

@ -38,6 +38,7 @@
this.timer1 = new System.Windows.Forms.Timer(this.components);
this.cbPoints = new Sunny.UI.UICheckBox();
this.cbContainsNan = new Sunny.UI.UICheckBox();
this.uiSymbolButton3 = new Sunny.UI.UISymbolButton();
((System.ComponentModel.ISupportInitialize)(this.uiImageButton3)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.uiImageButton2)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.uiImageButton1)).BeginInit();
@ -47,14 +48,15 @@
//
this.uiSymbolButton1.Cursor = System.Windows.Forms.Cursors.Hand;
this.uiSymbolButton1.Font = new System.Drawing.Font("微软雅黑", 12F);
this.uiSymbolButton1.IsScaled = false;
this.uiSymbolButton1.Location = new System.Drawing.Point(348, 505);
this.uiSymbolButton1.MinimumSize = new System.Drawing.Size(1, 1);
this.uiSymbolButton1.Name = "uiSymbolButton1";
this.uiSymbolButton1.Padding = new System.Windows.Forms.Padding(28, 0, 0, 0);
this.uiSymbolButton1.Size = new System.Drawing.Size(100, 27);
this.uiSymbolButton1.Symbol = 61952;
this.uiSymbolButton1.Symbol = 61953;
this.uiSymbolButton1.TabIndex = 34;
this.uiSymbolButton1.Text = "数据";
this.uiSymbolButton1.Text = "单Y轴";
this.uiSymbolButton1.Click += new System.EventHandler(this.uiSymbolButton1_Click);
//
// uiImageButton3
@ -104,19 +106,26 @@
this.LineChart.FillColor = System.Drawing.Color.FromArgb(((int)(((byte)(244)))), ((int)(((byte)(244)))), ((int)(((byte)(244)))));
this.LineChart.Font = new System.Drawing.Font("微软雅黑", 12F);
this.LineChart.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(54)))), ((int)(((byte)(54)))), ((int)(((byte)(54)))));
this.LineChart.IsScaled = false;
this.LineChart.LegendFont = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
this.LineChart.LegendFontSize = 9F;
this.LineChart.Location = new System.Drawing.Point(30, 55);
this.LineChart.MinimumSize = new System.Drawing.Size(1, 1);
this.LineChart.Name = "LineChart";
this.LineChart.Size = new System.Drawing.Size(670, 430);
this.LineChart.SubFont = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
this.LineChart.SubTextFontSize = 9F;
this.LineChart.TabIndex = 35;
this.LineChart.Text = "uiLineChart1";
this.LineChart.TipsFontSize = 9F;
this.LineChart.PointValue += new Sunny.UI.UILineChart.OnPointValue(this.LineChart_PointValue);
//
// uiSymbolButton2
//
this.uiSymbolButton2.Cursor = System.Windows.Forms.Cursors.Hand;
this.uiSymbolButton2.Font = new System.Drawing.Font("微软雅黑", 12F);
this.uiSymbolButton2.Location = new System.Drawing.Point(454, 505);
this.uiSymbolButton2.IsScaled = false;
this.uiSymbolButton2.Location = new System.Drawing.Point(560, 505);
this.uiSymbolButton2.MinimumSize = new System.Drawing.Size(1, 1);
this.uiSymbolButton2.Name = "uiSymbolButton2";
this.uiSymbolButton2.Padding = new System.Windows.Forms.Padding(28, 0, 0, 0);
@ -134,7 +143,8 @@
//
this.cbPoints.Cursor = System.Windows.Forms.Cursors.Hand;
this.cbPoints.Font = new System.Drawing.Font("微软雅黑", 12F);
this.cbPoints.Location = new System.Drawing.Point(571, 505);
this.cbPoints.IsScaled = false;
this.cbPoints.Location = new System.Drawing.Point(348, 538);
this.cbPoints.MinimumSize = new System.Drawing.Size(1, 1);
this.cbPoints.Name = "cbPoints";
this.cbPoints.Padding = new System.Windows.Forms.Padding(22, 0, 0, 0);
@ -147,6 +157,7 @@
//
this.cbContainsNan.Cursor = System.Windows.Forms.Cursors.Hand;
this.cbContainsNan.Font = new System.Drawing.Font("微软雅黑", 12F);
this.cbContainsNan.IsScaled = false;
this.cbContainsNan.Location = new System.Drawing.Point(571, 535);
this.cbContainsNan.MinimumSize = new System.Drawing.Size(1, 1);
this.cbContainsNan.Name = "cbContainsNan";
@ -154,13 +165,30 @@
this.cbContainsNan.Size = new System.Drawing.Size(95, 27);
this.cbContainsNan.TabIndex = 38;
this.cbContainsNan.Text = "包含Nan";
this.cbContainsNan.Visible = false;
this.cbContainsNan.CheckedChanged += new System.EventHandler(this.uiCheckBox2_CheckedChanged);
//
// uiSymbolButton3
//
this.uiSymbolButton3.Cursor = System.Windows.Forms.Cursors.Hand;
this.uiSymbolButton3.Font = new System.Drawing.Font("微软雅黑", 12F);
this.uiSymbolButton3.IsScaled = false;
this.uiSymbolButton3.Location = new System.Drawing.Point(454, 505);
this.uiSymbolButton3.MinimumSize = new System.Drawing.Size(1, 1);
this.uiSymbolButton3.Name = "uiSymbolButton3";
this.uiSymbolButton3.Padding = new System.Windows.Forms.Padding(28, 0, 0, 0);
this.uiSymbolButton3.Size = new System.Drawing.Size(100, 27);
this.uiSymbolButton3.Symbol = 61953;
this.uiSymbolButton3.TabIndex = 39;
this.uiSymbolButton3.Text = "双Y轴";
this.uiSymbolButton3.Click += new System.EventHandler(this.uiSymbolButton3_Click_1);
//
// FLineChart
//
this.AllowShowTitle = true;
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.None;
this.ClientSize = new System.Drawing.Size(800, 574);
this.Controls.Add(this.uiSymbolButton3);
this.Controls.Add(this.cbContainsNan);
this.Controls.Add(this.cbPoints);
this.Controls.Add(this.uiSymbolButton2);
@ -192,5 +220,6 @@
private System.Windows.Forms.Timer timer1;
private UICheckBox cbPoints;
private UICheckBox cbContainsNan;
private UISymbolButton uiSymbolButton3;
}
}

View File

@ -175,5 +175,51 @@ namespace Sunny.UI.Demo
{
uiSymbolButton1.PerformClick();
}
private void uiSymbolButton3_Click_1(object sender, EventArgs e)
{
timer1.Stop();
UILineOption option = new UILineOption();
option.ToolTip.Visible = true;
option.Title = new UITitle();
option.Title.Text = "SunnyUI";
option.Title.SubText = "LineChart";
var series = option.AddSeries(new UILineSeries("Line1"));
series.Add(0, 1.2);
series.Add(1, 2.2);
series.Add(2, 3.2);
series.Add(3, 4.2);
series.Add(4, 3.2);
series.Add(5, 2.2);
series.Symbol = UILinePointSymbol.Square;
series.SymbolSize = 4;
series.SymbolLineWidth = 2;
series.SymbolColor = Color.Red;
series = option.AddSeries(new UILineSeries("Line2", Color.Lime, true));
series.Add(3, 13.3);
series.Add(4, 12.3);
series.Add(5, 12.3);
series.Add(6, 11.3);
series.Add(7, 12.3);
series.Add(8, 14.3);
series.Symbol = UILinePointSymbol.Star;
series.SymbolSize = 4;
series.SymbolLineWidth = 2;
series.Smooth = true;
option.XAxis.Name = "日期";
option.YAxis.Name = "数值";
option.Y2Axis.Name = "数值";
option.YAxis.AxisLabel.DecimalCount = 1;
option.YAxis.AxisLabel.AutoFormat = false;
option.Y2Axis.AxisLabel.DecimalCount = 1;
option.Y2Axis.AxisLabel.AutoFormat = false;
LineChart.SetOption(option);
}
}
}

View File

@ -39,6 +39,9 @@ namespace Sunny.UI
Width = 400;
Height = 300;
SubFont = UIFontColor.CreateSubFont();
LegendFont = UIFontColor.CreateSubFont();
tip.Parent = this;
tip.Height = 32;
tip.Width = 200;
@ -46,7 +49,7 @@ namespace Sunny.UI
tip.Top = 1;
tip.StyleCustomMode = true;
tip.Style = UIStyle.Custom;
tip.Font = UIFontColor.SubFont;
tip.Font = UIFontColor.CreateSubFont();
tip.RadiusSides = UICornerRadiusSides.None;
tip.Visible = false;
@ -55,6 +58,13 @@ namespace Sunny.UI
tip.ForeColor = UIChartStyles.Plain.ForeColor;
tip.Visible = false;
tip.MouseEnter += Tip_MouseEnter;
tip.VisibleChanged += Tip_VisibleChanged;
}
private void Tip_VisibleChanged(object sender, EventArgs e)
{
tip.IsScaled = true;
tip.Font = this.DPIScaleFont(Font, SubFont.Size);
}
protected override void Dispose(bool disposing)
@ -180,27 +190,16 @@ namespace Sunny.UI
}
[Category("SunnyUI")]
[DefaultValue(9)]
public float LegendFontSize { get; set; } = 9.0f;
[DefaultValue(9), Browsable(false)]
public float LegendFontSize { get; set; } = 9;
[Category("SunnyUI")]
[DefaultValue(9)]
public float SubTextFontSize { get; set; } = 9.0f;
[DefaultValue(9), Browsable(false)]
public float SubTextFontSize { get; set; } = 9;
[Category("SunnyUI")]
[DefaultValue(9)]
public float TipsFontSize { get; set; } = 9.0f;
protected override void OnFontChanged(EventArgs e)
{
base.OnFontChanged(e);
if (tip != null)
{
tip.IsScaled = true;
tip.Font = this.DPIScaleFont(Font, TipsFontSize);
}
}
[DefaultValue(9), Browsable(false)]
public float TipsFontSize { get; set; } = 9;
Font tmpFont;
@ -208,10 +207,12 @@ namespace Sunny.UI
{
get
{
if (tmpFont == null || !tmpFont.Size.EqualsFloat(SubTextFontSize / this.DPIScale()))
float size = SubFont != null ? SubFont.Size : SubTextFontSize;
if (tmpFont == null || !tmpFont.Size.EqualsFloat(size / this.DPIScale()))
{
tmpFont?.Dispose();
tmpFont = this.DPIScaleFont(Font, SubTextFontSize);
tmpFont = this.DPIScaleFont(Font, size);
}
return tmpFont;
@ -224,10 +225,12 @@ namespace Sunny.UI
{
get
{
if (tmpLegendFont == null || !tmpLegendFont.Size.EqualsFloat(LegendFontSize / this.DPIScale()))
float size = LegendFont != null ? LegendFont.Size : LegendFontSize;
if (tmpLegendFont == null || !tmpLegendFont.Size.EqualsFloat(size / this.DPIScale()))
{
tmpLegendFont?.Dispose();
tmpLegendFont = this.DPIScaleFont(Font, SubTextFontSize);
tmpLegendFont = this.DPIScaleFont(Font, size);
}
return tmpLegendFont;
@ -254,10 +257,10 @@ namespace Sunny.UI
[DefaultValue(8)]
public int TextInterval { get; set; } = 8;
[Browsable(false), Obsolete("已过时用SubTextFontSize代替")]
[Browsable(true)]
public Font SubFont { get; set; }
[Browsable(false), Obsolete("已过时用LegendFontSize代替")]
[Browsable(true)]
public Font LegendFont { get; set; }
protected void DrawTitle(Graphics g, UITitle title)
{

View File

@ -24,6 +24,7 @@
* 2021-08-23: V3.0.6
* 2021-10-02: V3.0.8 Nan
* 2021-10-14: V3.0.8 线
* 2021-12-30: V3.0.9 Y坐标轴
******************************************************************************/
using System;
@ -66,7 +67,10 @@ namespace Sunny.UI
foreach (var series in Option.Series.Values)
{
series.CalcData(this, XScale, YScale);
if (series.IsY2)
series.CalcData(this, XScale, Y2Scale);
else
series.CalcData(this, XScale, YScale);
}
NeedDraw = true;
@ -85,7 +89,9 @@ namespace Sunny.UI
protected UIScale XScale;
protected UIScale YScale;
protected UIScale Y2Scale;
private double[] YLabels;
private double[] Y2Labels;
private double[] XLabels;
protected void CalcAxises()
@ -96,6 +102,7 @@ namespace Sunny.UI
XScale = new UILinearScale();
YScale = new UILinearScale();
Y2Scale = new UILinearScale();
//Y轴
{
@ -116,6 +123,26 @@ namespace Sunny.UI
YLabels = YScale.CalcLabels();
}
//Y2轴
if (Option.HaveY2)
{
Option.GetAllDataY2Range(out double min, out double max);
if (min > 0 && max > 0 && !Option.Y2Axis.Scale) min = 0;
if (min < 0 && max < 0 && !Option.Y2Axis.Scale) max = 0;
Y2Scale.SetRange(min, max);
if (!Option.Y2Axis.MaxAuto) Y2Scale.Max = Option.Y2Axis.Max;
if (!Option.Y2Axis.MinAuto) Y2Scale.Min = Option.Y2Axis.Min;
if (Y2Scale.Max.IsNanOrInfinity() || Y2Scale.Min.IsNanOrInfinity())
{
Y2Scale.Max = max;
Y2Scale.Min = min;
}
Y2Scale.AxisChange();
Y2Labels = Y2Scale.CalcLabels();
}
//X轴
{
Option.GetAllDataXRange(out double min, out double max);
@ -216,7 +243,7 @@ namespace Sunny.UI
g.DrawLine(ForeColor, DrawOrigin.X, zeroPos, DrawOrigin.X + DrawSize.Width, zeroPos);
}
if (XScale == null || YScale == null) return;
if (XScale == null || YScale == null || Y2Scale == null) return;
//X Tick
if (Option.XAxis.AxisTick.Show)
@ -225,7 +252,7 @@ namespace Sunny.UI
for (int i = 0; i < labels.Length; i++)
{
float x = labels[i];
if (x <= Option.Grid.Left || x >= Width - Option.Grid.Right) continue;
if (x < Option.Grid.Left || x > Width - Option.Grid.Right) continue;
if (Option.XAxis.AxisLabel.Show)
{
@ -247,8 +274,10 @@ namespace Sunny.UI
SizeF sf = g.MeasureString(label, TempFont);
g.DrawString(label, TempFont, ForeColor, x - sf.Width / 2.0f, DrawOrigin.Y + Option.XAxis.AxisTick.Length);
}
g.DrawLine(ForeColor, x, DrawOrigin.Y, x, DrawOrigin.Y + Option.XAxis.AxisTick.Length);
if (x.Equals(DrawOrigin.X)) continue;
if (x.Equals(DrawOrigin.X + DrawSize.Width)) continue;
@ -274,7 +303,7 @@ namespace Sunny.UI
for (int i = 0; i < labels.Length; i++)
{
float y = labels[i];
if (y <= Option.Grid.Top || y >= Height - Option.Grid.Bottom) continue;
if (y < Option.Grid.Top || y > Height - Option.Grid.Bottom) continue;
if (Option.YAxis.AxisLabel.Show)
{
@ -284,6 +313,7 @@ namespace Sunny.UI
g.DrawString(label, TempFont, ForeColor, DrawOrigin.X - Option.YAxis.AxisTick.Length - sf.Width, y - sf.Height / 2.0f);
}
g.DrawLine(ForeColor, DrawOrigin.X, y, DrawOrigin.X - Option.YAxis.AxisTick.Length, y);
if (y.Equals(DrawOrigin.Y)) continue;
if (y.Equals(DrawOrigin.X - DrawSize.Height)) continue;
@ -300,6 +330,42 @@ namespace Sunny.UI
float yy = Option.Grid.Top + DrawSize.Height / 2.0f;
g.DrawStringRotateAtCenter(Option.YAxis.Name, TempFont, ForeColor, new PointF(xx, yy), 270);
}
//Y2 Tick
if (Option.HaveY2 && Option.Y2Axis.AxisTick.Show)
{
float[] labels = Y2Scale.CalcYPixels(Y2Labels, DrawOrigin.Y, DrawSize.Height);
float widthMax = 0;
for (int i = 0; i < labels.Length; i++)
{
float y = labels[i];
if (y < Option.Grid.Top || y > Height - Option.Grid.Bottom) continue;
if (Option.Y2Axis.AxisLabel.Show)
{
string label = Y2Labels[i].ToString(Y2Scale.Format);
SizeF sf = g.MeasureString(label, TempFont);
widthMax = Math.Max(widthMax, sf.Width);
g.DrawString(label, TempFont, ForeColor, Width - Option.Grid.Right + Option.Y2Axis.AxisTick.Length, y - sf.Height / 2.0f);
}
g.DrawLine(ForeColor, Width - Option.Grid.Right, y, Width - Option.Grid.Right + Option.YAxis.AxisTick.Length, y);
if (y.Equals(DrawOrigin.Y)) continue;
if (y.Equals(DrawOrigin.X - DrawSize.Height)) continue;
using (Pen pn = new Pen(ForeColor))
{
pn.DashStyle = DashStyle.Dash;
pn.DashPattern = new float[] { 3, 3 };
//g.DrawLine(pn, DrawOrigin.X, y, Width - Option.Grid.Right, y);
}
}
SizeF sfName = g.MeasureString(Option.Y2Axis.Name, TempFont);
float xx = Width - Option.Grid.Right + Option.Y2Axis.AxisTick.Length + widthMax + sfName.Height / 2.0f;
float yy = Option.Grid.Top + DrawSize.Height / 2.0f;
g.DrawStringRotateAtCenter(Option.Y2Axis.Name, TempFont, ForeColor, new PointF(xx, yy), 90);
}
}
protected virtual void DrawSeries(Graphics g, Color color, UILineSeries series)
@ -374,7 +440,7 @@ namespace Sunny.UI
float wLeft = Option.Grid.Left;
float wRight = Width - Option.Grid.Right;
if (Option.GreaterWarningArea == null && Option.LessWarningArea == null)
if ((Option.GreaterWarningArea == null && Option.LessWarningArea == null) || Option.HaveY2)
{
foreach (var series in Option.Series.Values)
{
@ -534,6 +600,7 @@ namespace Sunny.UI
private void DrawAxisScales(Graphics g)
{
if (YScale == null) return;
if (Option.HaveY2) return;
foreach (var line in Option.YAxisScaleLines)
{

View File

@ -33,6 +33,8 @@ namespace Sunny.UI
public UIAxis YAxis { get; set; } = new UIAxis(UIAxisType.Value);
public UIAxis Y2Axis { get; set; } = new UIAxis(UIAxisType.Value);
public UILineToolTip ToolTip { get; set; } = new UILineToolTip();
public void Dispose()
@ -44,14 +46,14 @@ namespace Sunny.UI
public UIAxisType XAxisType { get; set; } = UIAxisType.Value;
public UIAxisType YAxisType { get; set; } = UIAxisType.Value;
public ConcurrentDictionary<string, UILineSeries> Series = new ConcurrentDictionary<string, UILineSeries>();
public readonly List<UIScaleLine> XAxisScaleLines = new List<UIScaleLine>();
public readonly List<UIScaleLine> YAxisScaleLines = new List<UIScaleLine>();
public readonly List<UIScaleLine> Y2AxisScaleLines = new List<UIScaleLine>();
public UILineWarningArea GreaterWarningArea { get; set; }
public UILineWarningArea LessWarningArea { get; set; }
@ -63,10 +65,10 @@ namespace Sunny.UI
return series;
}
public UILineSeries AddSeries(string name)
public UILineSeries AddSeries(string name, bool isY2 = false)
{
if (name.IsNullOrEmpty()) return null;
UILineSeries series = new UILineSeries(name);
UILineSeries series = new UILineSeries(name, isY2);
AddSeries(series);
return series;
}
@ -192,6 +194,20 @@ namespace Sunny.UI
return cnt;
}
public bool HaveY2
{
get
{
if (AllDataCount() == 0) return false;
foreach (var series in Series.Values)
{
if (series.IsY2) return true;
}
return false;
}
}
public void GetAllDataYRange(out double min, out double max)
{
if (AllDataCount() == 0)
@ -205,6 +221,42 @@ namespace Sunny.UI
max = double.MinValue;
foreach (var series in Series.Values)
{
if (series.IsY2) continue;
if (series.DataCount > 0)
{
if (series.ContainsNan)
{
foreach (var d in series.YData)
{
if (d.IsNan() || d.IsInfinity()) continue;
min = Math.Min(min, d);
max = Math.Max(max, d);
}
}
else
{
min = Math.Min(min, series.YData.Min());
max = Math.Max(max, series.YData.Max());
}
}
}
}
}
public void GetAllDataY2Range(out double min, out double max)
{
if (!HaveY2)
{
min = 0;
max = 1;
}
else
{
min = double.MaxValue;
max = double.MinValue;
foreach (var series in Series.Values)
{
if (!series.IsY2) continue;
if (series.DataCount > 0)
{
if (series.ContainsNan)
@ -290,17 +342,21 @@ namespace Sunny.UI
public bool ContainsNan { get; private set; }
public UILineSeries(string name)
public bool IsY2 { get; private set; }
public UILineSeries(string name, bool isY2 = false)
{
Name = name;
Color = UIColor.Blue;
IsY2 = isY2;
}
public UILineSeries(string name, Color color)
public UILineSeries(string name, Color color, bool isY2 = false)
{
Name = name;
Color = color;
CustomColor = true;
IsY2 = isY2;
}
public readonly List<double> XData = new List<double>();

View File

@ -707,11 +707,21 @@ namespace Sunny.UI
/// </summary>
public static readonly Font Font = new Font("微软雅黑", 12, FontStyle.Regular, GraphicsUnit.Point, GdiCharSet);
public static Font CreateFont()
{
return new Font("微软雅黑", 12, FontStyle.Regular, GraphicsUnit.Point, GdiCharSet);
}
/// <summary>
/// 默认字体
/// </summary>
public static readonly Font SubFont = new Font("微软雅黑", 9, FontStyle.Regular, GraphicsUnit.Point, GdiCharSet);
public static Font CreateSubFont()
{
return new Font("微软雅黑", 9, FontStyle.Regular, GraphicsUnit.Point, GdiCharSet);
}
/// <summary>
/// 主要颜色
/// </summary>