2952 lines
116 KiB
C#
2952 lines
116 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Data;
|
||
using System.Drawing;
|
||
using System.Drawing.Drawing2D;
|
||
using System.IO;
|
||
using System.Reflection;
|
||
using System.Text;
|
||
using System.Threading;
|
||
using System.Windows.Forms;
|
||
using System.Windows.Forms.DataVisualization.Charting;
|
||
using MySqlConnector;
|
||
|
||
namespace MysqlChart
|
||
{
|
||
static class Program
|
||
{
|
||
[STAThread]
|
||
static void Main()
|
||
{
|
||
AppDomain.CurrentDomain.AssemblyResolve += OnAssemblyResolve;
|
||
Application.EnableVisualStyles();
|
||
Application.SetCompatibleTextRenderingDefault(false);
|
||
if (HasArg("--selftest"))
|
||
{
|
||
SelfTest();
|
||
return;
|
||
}
|
||
if (HasArg("--drawtest"))
|
||
{
|
||
MainForm mf = new MainForm();
|
||
mf.Show();
|
||
Application.DoEvents();
|
||
mf.RunDrawTest();
|
||
Application.DoEvents();
|
||
Thread.Sleep(2000);
|
||
return;
|
||
}
|
||
Application.Run(new MainForm());
|
||
}
|
||
|
||
static bool HasArg(string name)
|
||
{
|
||
string[] args = Environment.GetCommandLineArgs();
|
||
foreach (string a in args)
|
||
if (a.Equals(name, StringComparison.OrdinalIgnoreCase)) return true;
|
||
return false;
|
||
}
|
||
|
||
static void SelfTest()
|
||
{
|
||
StringBuilder log = new StringBuilder();
|
||
try
|
||
{
|
||
string configPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "config.ini");
|
||
ConfigData config = ConfigData.Load(configPath);
|
||
log.AppendLine("config: host=" + config.Host + " db=" + config.Database);
|
||
MySqlConnectionStringBuilder b = new MySqlConnectionStringBuilder();
|
||
b.Server = config.Host;
|
||
b.Port = uint.Parse(config.Port);
|
||
b.UserID = config.User;
|
||
b.Password = config.Password;
|
||
b.Database = config.Database;
|
||
b.CharacterSet = config.Charset;
|
||
b.ConnectionTimeout = 10;
|
||
using (MySqlConnection conn = new MySqlConnection(b.ConnectionString))
|
||
{
|
||
conn.Open();
|
||
log.AppendLine("1. 连接成功");
|
||
List<string> tableList = new List<string>();
|
||
using (MySqlCommand cmd = new MySqlCommand("SHOW TABLES", conn))
|
||
using (IDataReader r = cmd.ExecuteReader())
|
||
{
|
||
while (r.Read()) tableList.Add(r.GetString(0));
|
||
log.AppendLine("2. 表列表: " + string.Join(", ", tableList.ToArray()));
|
||
}
|
||
string table = "mixer_runtime";
|
||
if (!tableList.Contains("mixer_runtime") && tableList.Count > 0) table = tableList[0];
|
||
List<string> cols = new List<string>();
|
||
using (MySqlCommand cmd = new MySqlCommand(
|
||
"SELECT * FROM `" + table + "` LIMIT 0", conn))
|
||
using (IDataReader r = cmd.ExecuteReader())
|
||
{
|
||
for (int i = 0; i < r.FieldCount; i++) cols.Add(r.GetName(i));
|
||
log.AppendLine("3. 列列表: " + string.Join(", ", cols.ToArray()));
|
||
}
|
||
string xc = cols.Count > 0 ? cols[0] : "1";
|
||
string yc = cols.Count > 1 ? cols[1] : xc;
|
||
string qx = "`" + xc.Replace("`", "``") + "`";
|
||
string qy = "`" + yc.Replace("`", "``") + "`";
|
||
using (MySqlCommand cmd = new MySqlCommand(
|
||
"SELECT " + qx + ", " + qy + " FROM `" + table + "` ORDER BY " + qx + " ASC LIMIT 100", conn))
|
||
using (IDataReader r = cmd.ExecuteReader())
|
||
{
|
||
int count = 0;
|
||
object[] first = null;
|
||
object[] last = null;
|
||
while (r.Read())
|
||
{
|
||
object[] vals = new object[r.FieldCount];
|
||
r.GetValues(vals);
|
||
if (first == null) first = vals;
|
||
last = vals;
|
||
count++;
|
||
}
|
||
log.AppendLine("4. 查询100行成功, 行数=" + count);
|
||
if (first != null)
|
||
log.AppendLine(" 首行: x=" + first[0] + " y=" + first[1]);
|
||
if (last != null)
|
||
log.AppendLine(" 末行: x=" + last[0] + " y=" + last[1]);
|
||
}
|
||
using (MySqlCommand cmd = new MySqlCommand(
|
||
"SELECT COUNT(*) FROM `" + table + "`", conn))
|
||
{
|
||
object c = cmd.ExecuteScalar();
|
||
log.AppendLine("5. 全表行数: " + c);
|
||
}
|
||
}
|
||
log.AppendLine("SELFTEST PASS");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
log.AppendLine("SELFTEST FAIL: " + ex.Message);
|
||
log.AppendLine(ex.StackTrace);
|
||
try
|
||
{
|
||
string configPath2 = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "config.ini");
|
||
ConfigData cfg2 = ConfigData.Load(configPath2);
|
||
MySqlConnectionStringBuilder b2 = new MySqlConnectionStringBuilder();
|
||
b2.Server = cfg2.Host;
|
||
b2.Port = uint.Parse(cfg2.Port);
|
||
b2.UserID = cfg2.User;
|
||
b2.Password = cfg2.Password;
|
||
b2.CharacterSet = cfg2.Charset;
|
||
b2.ConnectionTimeout = 10;
|
||
using (MySqlConnection conn2 = new MySqlConnection(b2.ConnectionString))
|
||
{
|
||
conn2.Open();
|
||
log.AppendLine("(无库连接成功, 服务器上数据库列表:)");
|
||
using (MySqlCommand cmd = new MySqlCommand("SHOW DATABASES", conn2))
|
||
using (IDataReader r = cmd.ExecuteReader())
|
||
{
|
||
while (r.Read()) log.AppendLine(" DB: " + r.GetString(0));
|
||
}
|
||
}
|
||
}
|
||
catch (Exception ex2)
|
||
{
|
||
log.AppendLine("(无法列出数据库: " + ex2.Message + ")");
|
||
}
|
||
}
|
||
string outFile = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "selftest_result.txt");
|
||
File.WriteAllText(outFile, log.ToString(), new UTF8Encoding(false));
|
||
}
|
||
|
||
static Assembly OnAssemblyResolve(object sender, ResolveEventArgs args)
|
||
{
|
||
string simpleName = new AssemblyName(args.Name).Name;
|
||
string resName = simpleName + ".dll";
|
||
Assembly asm = Assembly.GetExecutingAssembly();
|
||
string[] names = asm.GetManifestResourceNames();
|
||
foreach (string n in names)
|
||
{
|
||
if (n.EndsWith(resName, StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
using (Stream s = asm.GetManifestResourceStream(n))
|
||
{
|
||
if (s == null) return null;
|
||
byte[] buf = new byte[s.Length];
|
||
s.Read(buf, 0, buf.Length);
|
||
return Assembly.Load(buf);
|
||
}
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
}
|
||
|
||
class ConfigData
|
||
{
|
||
public string Host = "127.0.0.1";
|
||
public string Port = "3306";
|
||
public string User = "root";
|
||
public string Password = "";
|
||
public string Database = "";
|
||
public string Charset = "utf8mb4";
|
||
|
||
public static ConfigData Load(string path)
|
||
{
|
||
ConfigData c = new ConfigData();
|
||
if (!File.Exists(path)) return c;
|
||
string[] lines = File.ReadAllLines(path, Encoding.UTF8);
|
||
bool inMysql = false;
|
||
foreach (string line in lines)
|
||
{
|
||
string t = line.Trim();
|
||
if (t.Length == 0 || t.StartsWith(";") || t.StartsWith("#")) continue;
|
||
if (t.StartsWith("[") && t.EndsWith("]"))
|
||
{
|
||
inMysql = t.Equals("[mysql]", StringComparison.OrdinalIgnoreCase);
|
||
continue;
|
||
}
|
||
if (!inMysql) continue;
|
||
int idx = t.IndexOf('=');
|
||
if (idx <= 0) continue;
|
||
string key = t.Substring(0, idx).Trim().ToLowerInvariant();
|
||
string val = t.Substring(idx + 1).Trim();
|
||
switch (key)
|
||
{
|
||
case "host": c.Host = val; break;
|
||
case "port": c.Port = val; break;
|
||
case "user": c.User = val; break;
|
||
case "password": c.Password = val; break;
|
||
case "database": c.Database = val; break;
|
||
case "charset": c.Charset = val; break;
|
||
}
|
||
}
|
||
return c;
|
||
}
|
||
|
||
public void Save(string path)
|
||
{
|
||
StringBuilder sb = new StringBuilder();
|
||
sb.AppendLine("[mysql]");
|
||
sb.AppendLine("host = " + Host);
|
||
sb.AppendLine("port = " + Port);
|
||
sb.AppendLine("user = " + User);
|
||
sb.AppendLine("password = " + Password);
|
||
sb.AppendLine("database = " + Database);
|
||
sb.AppendLine("charset = " + Charset);
|
||
File.WriteAllText(path, sb.ToString(), new UTF8Encoding(false));
|
||
}
|
||
}
|
||
|
||
class MainForm : Form
|
||
{
|
||
ConfigData config;
|
||
string configPath;
|
||
|
||
TextBox txtHost, txtPort, txtUser, txtPassword;
|
||
ComboBox cmbDatabase, cmbTable, cmbX, cmbXType, cmbOrder, cmbCalc, cmbCalcTarget;
|
||
TextBox txtLimit, txtOffset, txtTailOffset, txtCalcParam;
|
||
Label lblCalcParam;
|
||
ListBox lbY;
|
||
DataGridView dgvFilter;
|
||
Button btnConnect, btnAddRule, btnDelRule, btnPlot, btnClear, btnSave;
|
||
Button btnCollapse, btnPin, btnExpand, btnApplyCalc, btnClearCalc, btnLegend, btnSettings, btnCross;
|
||
Label lblChartTitle, lblStatus;
|
||
Chart chart;
|
||
Panel left, right, splitterRef;
|
||
|
||
List<string> allColumns = new List<string>();
|
||
List<string> calcSeriesNames = new List<string>();
|
||
List<object[]> lastRows;
|
||
string lastXcol = "";
|
||
List<string> lastYcols = new List<string>();
|
||
string lastTable = "";
|
||
bool loadingDbs = false;
|
||
bool pinned = false;
|
||
int paletteCounter = 0;
|
||
bool draggingLegend = false;
|
||
Point dragStart = Point.Empty;
|
||
PointF crossPos = new PointF(-1, -1);
|
||
Form floatTip;
|
||
bool showCross = true;
|
||
bool legendHiddenByUser = false;
|
||
Panel legendBar;
|
||
FlowLayoutPanel legendFlow;
|
||
|
||
static readonly Color ThemeBlue = Color.FromArgb(41, 101, 173);
|
||
static readonly Color PanelBg = Color.FromArgb(246, 248, 251);
|
||
static readonly Color BorderLine = Color.FromArgb(214, 221, 230);
|
||
|
||
public MainForm()
|
||
{
|
||
configPath = Path.Combine(Path.GetDirectoryName(Application.ExecutablePath), "config.ini");
|
||
config = ConfigData.Load(configPath);
|
||
InitUI();
|
||
FillConnFields();
|
||
Shown += delegate { FitWindowToScreen(); };
|
||
}
|
||
|
||
void FitWindowToScreen()
|
||
{
|
||
try
|
||
{
|
||
Screen scr = Screen.FromControl(this);
|
||
int w = (int)(scr.WorkingArea.Width * 0.92);
|
||
int h = (int)(scr.WorkingArea.Height * 0.92);
|
||
if (w < 1000) w = 1000;
|
||
if (h < 700) h = 700;
|
||
this.Width = w;
|
||
this.Height = h;
|
||
this.StartPosition = FormStartPosition.CenterScreen;
|
||
}
|
||
catch { }
|
||
}
|
||
|
||
void InitUI()
|
||
{
|
||
Text = "趋势分析预测工具";
|
||
Size = new Size(1240, 820);
|
||
MinimumSize = new Size(1000, 700);
|
||
StartPosition = FormStartPosition.CenterScreen;
|
||
Font = new Font("Microsoft YaHei UI", 9F);
|
||
BackColor = PanelBg;
|
||
try
|
||
{
|
||
using (Stream s = Assembly.GetExecutingAssembly().GetManifestResourceStream("hamster.ico"))
|
||
{
|
||
if (s != null)
|
||
this.Icon = new Icon(s);
|
||
}
|
||
}
|
||
catch { }
|
||
|
||
left = new Panel();
|
||
left.Width = 380;
|
||
left.BackColor = PanelBg;
|
||
left.AutoScroll = true;
|
||
Controls.Add(left);
|
||
|
||
Panel right = new Panel();
|
||
right.BackColor = Color.White;
|
||
Controls.Add(right);
|
||
this.right = right;
|
||
|
||
Panel splitter = new Panel();
|
||
splitter.BackColor = BorderLine;
|
||
splitter.Cursor = Cursors.SizeWE;
|
||
Controls.Add(splitter);
|
||
splitterRef = splitter;
|
||
SetupSplitterDrag(splitter);
|
||
|
||
this.Resize += delegate { LayoutPanels(left, right, splitter); };
|
||
LayoutPanels(left, right, splitter);
|
||
left.Resize += delegate { LayoutLeftPanel(); };
|
||
|
||
int x2 = 8;
|
||
int iw = 406;
|
||
int y = 4;
|
||
|
||
btnCollapse = MakeIconButton("折叠", 0);
|
||
btnCollapse.Location = new Point(x2, y);
|
||
btnCollapse.Size = new Size(200, 30);
|
||
btnCollapse.Click += delegate
|
||
{
|
||
left.Visible = false;
|
||
LayoutPanels(left, right, splitterRef);
|
||
btnExpand.Visible = true;
|
||
btnExpand.BringToFront();
|
||
};
|
||
left.Controls.Add(btnCollapse);
|
||
|
||
btnPin = MakeIconButton("钉住", 1);
|
||
btnPin.Location = new Point(x2 + 208, y);
|
||
btnPin.Size = new Size(206, 30);
|
||
btnPin.Click += delegate { TogglePin(); };
|
||
left.Controls.Add(btnPin);
|
||
y += 40;
|
||
|
||
Label title1 = new Label();
|
||
title1.Text = "服务器连接";
|
||
title1.Font = new Font("Microsoft YaHei UI", 10F, FontStyle.Bold);
|
||
title1.ForeColor = ThemeBlue;
|
||
title1.Location = new Point(x2, y);
|
||
title1.AutoSize = true;
|
||
left.Controls.Add(title1);
|
||
y += 30;
|
||
|
||
AddLabel(left, "主机地址:", x2, y);
|
||
txtHost = AddText(left, config.Host, x2 + 88, y, 132);
|
||
AddLabel2(left, "端口:", x2 + 226, y);
|
||
txtPort = AddText(left, config.Port, x2 + 270, y, 144);
|
||
y += 36;
|
||
|
||
AddLabel(left, "用户名:", x2, y);
|
||
txtUser = AddText(left, config.User, x2 + 88, y, 132);
|
||
AddLabel2(left, "密码:", x2 + 226, y);
|
||
txtPassword = AddText(left, config.Password, x2 + 270, y, 144, true);
|
||
y += 36;
|
||
|
||
AddLabel(left, "DB:", x2, y);
|
||
cmbDatabase = MakeCombo();
|
||
cmbDatabase.DropDownStyle = ComboBoxStyle.DropDown;
|
||
cmbDatabase.AutoCompleteMode = AutoCompleteMode.SuggestAppend;
|
||
cmbDatabase.AutoCompleteSource = AutoCompleteSource.ListItems;
|
||
cmbDatabase.Location = new Point(x2 + 88, y);
|
||
cmbDatabase.Size = new Size(iw - 88, 28);
|
||
cmbDatabase.SelectedIndexChanged += delegate { DatabaseSelected(); };
|
||
cmbDatabase.KeyDown += delegate(object s, KeyEventArgs e)
|
||
{
|
||
if (e.KeyCode == Keys.Enter)
|
||
{
|
||
e.SuppressKeyPress = true;
|
||
DatabaseSelected();
|
||
}
|
||
};
|
||
left.Controls.Add(cmbDatabase);
|
||
y += 38;
|
||
|
||
btnConnect = new Button();
|
||
btnConnect.Text = "连 接";
|
||
btnConnect.Location = new Point(x2, y);
|
||
btnConnect.Size = new Size(iw, 34);
|
||
btnConnect.FlatStyle = FlatStyle.Flat;
|
||
btnConnect.FlatAppearance.BorderSize = 0;
|
||
btnConnect.BackColor = ThemeBlue;
|
||
btnConnect.ForeColor = Color.White;
|
||
btnConnect.Font = new Font("Microsoft YaHei UI", 10F, FontStyle.Bold);
|
||
btnConnect.Cursor = Cursors.Hand;
|
||
btnConnect.Click += delegate { ConnectAndLoad(); };
|
||
left.Controls.Add(btnConnect);
|
||
y += 46;
|
||
|
||
Label titleQ = new Label();
|
||
titleQ.Text = "排序规则";
|
||
titleQ.Font = new Font("Microsoft YaHei UI", 10F, FontStyle.Bold);
|
||
titleQ.ForeColor = ThemeBlue;
|
||
titleQ.Location = new Point(x2, y);
|
||
titleQ.AutoSize = true;
|
||
left.Controls.Add(titleQ);
|
||
y += 30;
|
||
|
||
AddLabel(left, "方向:", x2, y);
|
||
cmbOrder = MakeCombo();
|
||
cmbOrder.Location = new Point(x2 + 88, y);
|
||
cmbOrder.Size = new Size(156, 28);
|
||
cmbOrder.Items.Add("正序 (最早在前)");
|
||
cmbOrder.Items.Add("倒序 (最新在前)");
|
||
cmbOrder.SelectedIndex = 0;
|
||
left.Controls.Add(cmbOrder);
|
||
|
||
AddLabel2(left, "条数:", x2 + 246, y);
|
||
txtLimit = AddText(left, "2000", x2 + 288, y, 126);
|
||
y += 36;
|
||
|
||
AddLabel(left, "偏移:", x2, y);
|
||
txtOffset = AddText(left, "0", x2 + 88, y, 156);
|
||
Label tipL = new Label();
|
||
tipL.Text = "跳过前 N 行";
|
||
tipL.Font = new Font("Microsoft YaHei UI", 8.5F);
|
||
tipL.ForeColor = Color.FromArgb(140, 150, 160);
|
||
tipL.Location = new Point(x2 + 250, y + 6);
|
||
tipL.AutoSize = true;
|
||
left.Controls.Add(tipL);
|
||
y += 42;
|
||
|
||
AddLabel(left, "起点:", x2, y);
|
||
txtTailOffset = AddText(left, "0", x2 + 88, y, 156);
|
||
y += 22;
|
||
Label tipT = new Label();
|
||
tipT.Text = "从最新数据往回数 N 条作为查询起点";
|
||
tipT.Font = new Font("Microsoft YaHei UI", 8.5F);
|
||
tipT.ForeColor = Color.FromArgb(140, 150, 160);
|
||
tipT.Location = new Point(x2 + 88, y);
|
||
tipT.AutoSize = true;
|
||
left.Controls.Add(tipT);
|
||
y += 20;
|
||
|
||
Label title2 = new Label();
|
||
title2.Text = "存储表与坐标轴";
|
||
title2.Font = new Font("Microsoft YaHei UI", 10F, FontStyle.Bold);
|
||
title2.ForeColor = ThemeBlue;
|
||
title2.Location = new Point(x2, y);
|
||
title2.AutoSize = true;
|
||
left.Controls.Add(title2);
|
||
y += 30;
|
||
|
||
AddLabel(left, "存储表:", x2, y);
|
||
cmbTable = MakeCombo();
|
||
cmbTable.Location = new Point(x2 + 88, y);
|
||
cmbTable.Size = new Size(iw - 88, 28);
|
||
cmbTable.SelectedIndexChanged += delegate { TableSelected(); };
|
||
left.Controls.Add(cmbTable);
|
||
y += 36;
|
||
|
||
AddLabel(left, "横坐标:", x2, y);
|
||
cmbX = MakeCombo();
|
||
cmbX.Location = new Point(x2 + 88, y);
|
||
cmbX.Size = new Size(iw - 88, 28);
|
||
left.Controls.Add(cmbX);
|
||
y += 36;
|
||
|
||
AddLabel(left, "横轴类型:", x2, y);
|
||
cmbXType = MakeCombo();
|
||
cmbXType.Location = new Point(x2 + 88, y);
|
||
cmbXType.Size = new Size(iw - 88, 28);
|
||
cmbXType.Items.Add("自动识别");
|
||
cmbXType.Items.Add("时间");
|
||
cmbXType.Items.Add("数值");
|
||
cmbXType.Items.Add("文本");
|
||
cmbXType.SelectedIndex = 0;
|
||
left.Controls.Add(cmbXType);
|
||
y += 36;
|
||
|
||
AddLabel(left, "纵坐标:", x2, y);
|
||
lbY = new ListBox();
|
||
lbY.BorderStyle = BorderStyle.FixedSingle;
|
||
lbY.Location = new Point(x2 + 88, y);
|
||
lbY.Size = new Size(iw - 88, 96);
|
||
lbY.HorizontalScrollbar = true;
|
||
lbY.SelectionMode = SelectionMode.MultiExtended;
|
||
lbY.BackColor = Color.White;
|
||
lbY.IntegralHeight = false;
|
||
left.Controls.Add(lbY);
|
||
y += 104;
|
||
|
||
Label tipY = new Label();
|
||
tipY.Text = "提示: 按住 Ctrl 键可多选纵坐标";
|
||
tipY.Font = new Font("Microsoft YaHei UI", 8.5F);
|
||
tipY.ForeColor = Color.FromArgb(140, 150, 160);
|
||
tipY.Location = new Point(x2 + 88, y);
|
||
tipY.AutoSize = true;
|
||
left.Controls.Add(tipY);
|
||
y += 24;
|
||
|
||
Label title3 = new Label();
|
||
title3.Text = "过滤规则 (多行按 AND/OR 连接)";
|
||
title3.Font = new Font("Microsoft YaHei UI", 10F, FontStyle.Bold);
|
||
title3.ForeColor = ThemeBlue;
|
||
title3.Location = new Point(x2, y);
|
||
title3.AutoSize = true;
|
||
left.Controls.Add(title3);
|
||
y += 30;
|
||
|
||
dgvFilter = new DataGridView();
|
||
dgvFilter.Location = new Point(x2, y);
|
||
dgvFilter.Size = new Size(iw, 140);
|
||
dgvFilter.AllowUserToAddRows = false;
|
||
dgvFilter.AllowUserToDeleteRows = false;
|
||
dgvFilter.AllowUserToResizeRows = false;
|
||
dgvFilter.RowHeadersVisible = false;
|
||
dgvFilter.BackgroundColor = Color.White;
|
||
dgvFilter.BorderStyle = BorderStyle.FixedSingle;
|
||
dgvFilter.EnableHeadersVisualStyles = false;
|
||
dgvFilter.ColumnHeadersDefaultCellStyle.BackColor = Color.FromArgb(52, 73, 94);
|
||
dgvFilter.ColumnHeadersDefaultCellStyle.ForeColor = Color.White;
|
||
dgvFilter.ColumnHeadersDefaultCellStyle.Font = new Font("Microsoft YaHei UI", 8.5F, FontStyle.Bold);
|
||
dgvFilter.ColumnHeadersDefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleCenter;
|
||
dgvFilter.ColumnHeadersHeight = 24;
|
||
dgvFilter.DefaultCellStyle.Font = new Font("Microsoft YaHei UI", 8.5F);
|
||
dgvFilter.DefaultCellStyle.SelectionBackColor = Color.FromArgb(191, 215, 245);
|
||
dgvFilter.DefaultCellStyle.SelectionForeColor = Color.Black;
|
||
dgvFilter.AlternatingRowsDefaultCellStyle.BackColor = Color.FromArgb(244, 247, 251);
|
||
dgvFilter.GridColor = Color.FromArgb(221, 228, 236);
|
||
dgvFilter.RowTemplate.Height = 22;
|
||
|
||
DataGridViewComboBoxColumn colCol = new DataGridViewComboBoxColumn();
|
||
colCol.HeaderText = "列";
|
||
colCol.Width = 132;
|
||
colCol.FlatStyle = FlatStyle.Flat;
|
||
dgvFilter.Columns.Add(colCol);
|
||
|
||
DataGridViewComboBoxColumn colOp = new DataGridViewComboBoxColumn();
|
||
colOp.HeaderText = "操作符";
|
||
colOp.Width = 92;
|
||
colOp.FlatStyle = FlatStyle.Flat;
|
||
colOp.Items.Add("=");
|
||
colOp.Items.Add("!=");
|
||
colOp.Items.Add(">");
|
||
colOp.Items.Add(">=");
|
||
colOp.Items.Add("<");
|
||
colOp.Items.Add("<=");
|
||
colOp.Items.Add("LIKE");
|
||
colOp.Items.Add("BETWEEN");
|
||
colOp.Items.Add("IS NULL");
|
||
colOp.Items.Add("IS NOT NULL");
|
||
dgvFilter.Columns.Add(colOp);
|
||
|
||
DataGridViewTextBoxColumn colVal = new DataGridViewTextBoxColumn();
|
||
colVal.HeaderText = "值";
|
||
colVal.Width = 120;
|
||
colVal.AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
|
||
colVal.ToolTipText = "BETWEEN 用逗号分隔两个值";
|
||
dgvFilter.Columns.Add(colVal);
|
||
|
||
DataGridViewComboBoxColumn colLogic = new DataGridViewComboBoxColumn();
|
||
colLogic.HeaderText = "逻辑";
|
||
colLogic.Width = 60;
|
||
colLogic.FlatStyle = FlatStyle.Flat;
|
||
colLogic.Items.Add("AND");
|
||
colLogic.Items.Add("OR");
|
||
dgvFilter.Columns.Add(colLogic);
|
||
|
||
left.Controls.Add(dgvFilter);
|
||
y += 148;
|
||
|
||
btnAddRule = MakeSmallButton("+ 添加规则");
|
||
btnAddRule.Location = new Point(x2, y);
|
||
btnAddRule.Size = new Size(130, 28);
|
||
btnAddRule.BackColor = Color.FromArgb(39, 174, 96);
|
||
btnAddRule.Click += delegate { AddRule(); };
|
||
left.Controls.Add(btnAddRule);
|
||
|
||
btnDelRule = MakeSmallButton("- 删除");
|
||
btnDelRule.Location = new Point(x2 + 138, y);
|
||
btnDelRule.Size = new Size(130, 28);
|
||
btnDelRule.BackColor = Color.FromArgb(192, 57, 43);
|
||
btnDelRule.Click += delegate { DelRule(); };
|
||
left.Controls.Add(btnDelRule);
|
||
|
||
btnClear = MakeSmallButton("清空");
|
||
btnClear.Location = new Point(x2 + 276, y);
|
||
btnClear.Size = new Size(138, 28);
|
||
btnClear.BackColor = Color.FromArgb(127, 140, 141);
|
||
btnClear.Click += delegate { dgvFilter.Rows.Clear(); };
|
||
left.Controls.Add(btnClear);
|
||
y += 38;
|
||
|
||
btnPlot = new Button();
|
||
btnPlot.Text = "绘制曲线";
|
||
btnPlot.Location = new Point(x2, y);
|
||
btnPlot.Size = new Size(204, 42);
|
||
btnPlot.FlatStyle = FlatStyle.Flat;
|
||
btnPlot.FlatAppearance.BorderSize = 0;
|
||
btnPlot.BackColor = Color.FromArgb(39, 174, 96);
|
||
btnPlot.ForeColor = Color.White;
|
||
btnPlot.Font = new Font("Microsoft YaHei UI", 11F, FontStyle.Bold);
|
||
btnPlot.Cursor = Cursors.Hand;
|
||
btnPlot.Click += delegate { Plot(); };
|
||
left.Controls.Add(btnPlot);
|
||
|
||
btnSave = new Button();
|
||
btnSave.Text = "保存图片";
|
||
btnSave.Location = new Point(x2 + 210, y);
|
||
btnSave.Size = new Size(204, 42);
|
||
btnSave.FlatStyle = FlatStyle.Flat;
|
||
btnSave.FlatAppearance.BorderSize = 0;
|
||
btnSave.BackColor = ThemeBlue;
|
||
btnSave.ForeColor = Color.White;
|
||
btnSave.Font = new Font("Microsoft YaHei UI", 11F, FontStyle.Bold);
|
||
btnSave.Cursor = Cursors.Hand;
|
||
btnSave.Click += delegate { SaveImage(); };
|
||
left.Controls.Add(btnSave);
|
||
y += 52;
|
||
|
||
lblStatus = new Label();
|
||
lblStatus.Text = "请填写服务器连接信息后点击连接";
|
||
lblStatus.Location = new Point(x2, y);
|
||
lblStatus.Size = new Size(iw, 18);
|
||
lblStatus.ForeColor = Color.FromArgb(120, 130, 140);
|
||
left.Controls.Add(lblStatus);
|
||
|
||
lblChartTitle = new Label();
|
||
lblChartTitle.Dock = DockStyle.Top;
|
||
lblChartTitle.Height = 36;
|
||
lblChartTitle.TextAlign = ContentAlignment.MiddleCenter;
|
||
lblChartTitle.Font = new Font("Microsoft YaHei UI", 12F, FontStyle.Bold);
|
||
lblChartTitle.ForeColor = ThemeBlue;
|
||
lblChartTitle.Text = "";
|
||
right.Controls.Add(lblChartTitle);
|
||
|
||
legendBar = new Panel();
|
||
legendBar.Dock = DockStyle.Top;
|
||
legendBar.Height = 30;
|
||
legendBar.BackColor = Color.FromArgb(250, 250, 252);
|
||
legendBar.Visible = false;
|
||
right.Controls.Add(legendBar);
|
||
|
||
legendFlow = new FlowLayoutPanel();
|
||
legendFlow.Dock = DockStyle.Fill;
|
||
legendFlow.BackColor = Color.FromArgb(250, 250, 252);
|
||
legendFlow.Padding = new Padding(10, 4, 10, 4);
|
||
legendBar.Controls.Add(legendFlow);
|
||
|
||
Panel bar = new Panel();
|
||
bar.Dock = DockStyle.Top;
|
||
bar.Height = 44;
|
||
bar.BackColor = Color.FromArgb(240, 243, 247);
|
||
right.Controls.Add(bar);
|
||
|
||
Label lTarget = new Label();
|
||
lTarget.Text = "曲线:";
|
||
lTarget.Location = new Point(12, 14);
|
||
lTarget.AutoSize = true;
|
||
lTarget.ForeColor = Color.FromArgb(60, 70, 80);
|
||
bar.Controls.Add(lTarget);
|
||
|
||
cmbCalcTarget = new ComboBox();
|
||
cmbCalcTarget.DropDownStyle = ComboBoxStyle.DropDownList;
|
||
cmbCalcTarget.FlatStyle = FlatStyle.Flat;
|
||
cmbCalcTarget.Location = new Point(52, 8);
|
||
cmbCalcTarget.Size = new Size(86, 26);
|
||
bar.Controls.Add(cmbCalcTarget);
|
||
|
||
Label lCalc = new Label();
|
||
lCalc.Text = "运算:";
|
||
lCalc.Location = new Point(142, 14);
|
||
lCalc.AutoSize = true;
|
||
lCalc.ForeColor = Color.FromArgb(60, 70, 80);
|
||
bar.Controls.Add(lCalc);
|
||
|
||
cmbCalc = new ComboBox();
|
||
cmbCalc.DropDownStyle = ComboBoxStyle.DropDownList;
|
||
cmbCalc.FlatStyle = FlatStyle.Flat;
|
||
cmbCalc.Location = new Point(184, 8);
|
||
cmbCalc.Size = new Size(160, 26);
|
||
cmbCalc.Items.Add("贝叶斯回归 (AI)");
|
||
cmbCalc.Items.Add("常用对数 (log10)");
|
||
cmbCalc.Items.Add("对数 (ln)");
|
||
cmbCalc.Items.Add("多项式拟合 (Polyfit) (AI)");
|
||
cmbCalc.Items.Add("二次变换 (a*y²+b) (AI)");
|
||
cmbCalc.Items.Add("积分 (累积)");
|
||
cmbCalc.Items.Add("开方 (sqrt)");
|
||
cmbCalc.Items.Add("平滑曲线 (滑动平均)");
|
||
cmbCalc.Items.Add("梯度下降 (AI)");
|
||
cmbCalc.Items.Add("微分 (差分)");
|
||
cmbCalc.Items.Add("线性回归 (AI)");
|
||
cmbCalc.Items.Add("线性变换 (a*y+b) (AI)");
|
||
cmbCalc.Items.Add("指数 (exp)");
|
||
int defCalc = cmbCalc.Items.IndexOf("平滑曲线 (滑动平均)");
|
||
cmbCalc.SelectedIndex = defCalc >= 0 ? defCalc : 0;
|
||
cmbCalc.SelectedIndexChanged += delegate { UpdateCalcParamUI(); };
|
||
bar.Controls.Add(cmbCalc);
|
||
|
||
Label lParam = new Label();
|
||
lParam.Text = "无参数";
|
||
lParam.Location = new Point(350, 14);
|
||
lParam.AutoSize = true;
|
||
lParam.ForeColor = Color.FromArgb(60, 70, 80);
|
||
bar.Controls.Add(lParam);
|
||
this.lblCalcParam = lParam;
|
||
|
||
txtCalcParam = new TextBox();
|
||
txtCalcParam.Text = "5";
|
||
txtCalcParam.Location = new Point(398, 8);
|
||
txtCalcParam.Width = 50;
|
||
txtCalcParam.BorderStyle = BorderStyle.FixedSingle;
|
||
txtCalcParam.Enabled = false;
|
||
bar.Controls.Add(txtCalcParam);
|
||
UpdateCalcParamUI();
|
||
|
||
btnApplyCalc = MakeSmallButton("生成运算曲线");
|
||
btnApplyCalc.Location = new Point(456, 6);
|
||
btnApplyCalc.Size = new Size(104, 30);
|
||
btnApplyCalc.BackColor = ThemeBlue;
|
||
btnApplyCalc.Click += delegate { ApplyCalc(); };
|
||
bar.Controls.Add(btnApplyCalc);
|
||
|
||
btnClearCalc = MakeSmallButton("清除运算");
|
||
btnClearCalc.Location = new Point(490, 6);
|
||
btnClearCalc.Size = new Size(92, 30);
|
||
btnClearCalc.BackColor = Color.FromArgb(127, 140, 141);
|
||
btnClearCalc.Click += delegate { ClearCalcSeries(); };
|
||
bar.Controls.Add(btnClearCalc);
|
||
|
||
btnLegend = MakeSmallButton("隐藏图例");
|
||
btnLegend.Location = new Point(588, 6);
|
||
btnLegend.Size = new Size(92, 30);
|
||
btnLegend.BackColor = Color.FromArgb(52, 73, 94);
|
||
btnLegend.Click += delegate { ToggleLegend(); };
|
||
bar.Controls.Add(btnLegend);
|
||
|
||
btnSettings = MakeSmallButton("图表设置");
|
||
btnSettings.Location = new Point(686, 6);
|
||
btnSettings.Size = new Size(92, 30);
|
||
btnSettings.BackColor = Color.FromArgb(52, 73, 94);
|
||
btnSettings.Click += delegate { OpenChartSettings(); };
|
||
bar.Controls.Add(btnSettings);
|
||
|
||
btnCross = MakeSmallButton("隐藏十字线");
|
||
btnCross.Location = new Point(786, 6);
|
||
btnCross.Size = new Size(104, 30);
|
||
btnCross.BackColor = Color.FromArgb(52, 73, 94);
|
||
btnCross.Click += delegate { ToggleCross(); };
|
||
bar.Controls.Add(btnCross);
|
||
|
||
chart = new Chart();
|
||
chart.Dock = DockStyle.Fill;
|
||
chart.BackColor = Color.White;
|
||
chart.BorderlineColor = BorderLine;
|
||
chart.BorderlineWidth = 1;
|
||
ChartArea area = new ChartArea("main");
|
||
area.BackColor = Color.White;
|
||
area.AxisX.MajorGrid.LineColor = Color.FromArgb(235, 238, 242);
|
||
area.AxisY.MajorGrid.LineColor = Color.FromArgb(235, 238, 242);
|
||
area.AxisX.LineColor = BorderLine;
|
||
area.AxisY.LineColor = BorderLine;
|
||
area.AxisX.LabelStyle.Font = new Font("Microsoft YaHei UI", 9F);
|
||
area.AxisY.LabelStyle.Font = new Font("Microsoft YaHei UI", 9F);
|
||
area.AxisX.TitleFont = new Font("Microsoft YaHei UI", 9F, FontStyle.Bold);
|
||
area.AxisY.TitleFont = new Font("Microsoft YaHei UI", 9F, FontStyle.Bold);
|
||
area.AxisX.TitleForeColor = ThemeBlue;
|
||
area.AxisY.TitleForeColor = ThemeBlue;
|
||
chart.ChartAreas.Add(area);
|
||
chart.Legends.Add(new Legend("legend"));
|
||
chart.Legends["legend"].Enabled = false;
|
||
chart.Legends["legend"].Font = new Font("Microsoft YaHei UI", 9F);
|
||
chart.Legends["legend"].BackColor = Color.FromArgb(250, 250, 252);
|
||
chart.Legends["legend"].BorderColor = BorderLine;
|
||
chart.Legends["legend"].BorderWidth = 1;
|
||
chart.Legends["legend"].Alignment = StringAlignment.Near;
|
||
right.Controls.Add(chart);
|
||
|
||
SetupLegendDrag();
|
||
chart.MouseMove += Chart_MouseMove;
|
||
chart.MouseClick += Chart_MouseClick;
|
||
chart.MouseLeave += delegate { crossPos = new PointF(-1, -1); chart.Invalidate(); };
|
||
chart.PostPaint += Chart_PostPaint;
|
||
|
||
btnExpand = MakeIconButton("展开配置", 2);
|
||
btnExpand.Location = new Point(4, 50);
|
||
btnExpand.Size = new Size(110, 32);
|
||
btnExpand.FlatStyle = FlatStyle.Flat;
|
||
btnExpand.FlatAppearance.BorderSize = 0;
|
||
btnExpand.BackColor = ThemeBlue;
|
||
btnExpand.ForeColor = Color.White;
|
||
btnExpand.Font = new Font("Microsoft YaHei UI", 9F, FontStyle.Bold);
|
||
btnExpand.Cursor = Cursors.Hand;
|
||
btnExpand.Visible = false;
|
||
btnExpand.Click += delegate
|
||
{
|
||
left.Visible = true;
|
||
LayoutPanels(left, right, splitterRef);
|
||
btnExpand.Visible = false;
|
||
}; right.Controls.Add(btnExpand);
|
||
btnExpand.BringToFront();
|
||
LayoutLeftPanel();
|
||
}
|
||
|
||
void SetupLegendDrag()
|
||
{
|
||
chart.MouseDown += delegate(object s, MouseEventArgs e)
|
||
{
|
||
if (e.Button != MouseButtons.Left) return;
|
||
try
|
||
{
|
||
Legend lg = chart.Legends["legend"];
|
||
if (!lg.Enabled) return;
|
||
HitTestResult hr = chart.HitTest(e.X, e.Y);
|
||
if (hr != null && hr.ChartElementType == ChartElementType.LegendArea)
|
||
{
|
||
draggingLegend = true;
|
||
dragStart = e.Location;
|
||
chart.Cursor = Cursors.Hand;
|
||
}
|
||
}
|
||
catch { }
|
||
};
|
||
chart.MouseMove += delegate(object s, MouseEventArgs e)
|
||
{
|
||
if (!draggingLegend) return;
|
||
try
|
||
{
|
||
if (chart.Width <= 0 || chart.Height <= 0) return;
|
||
Legend lg = chart.Legends["legend"];
|
||
ElementPosition p = lg.Position;
|
||
p.Auto = false;
|
||
float dx = e.X - dragStart.X;
|
||
float dy = e.Y - dragStart.Y;
|
||
p.X += dx / chart.Width * 100f;
|
||
p.Y += dy / chart.Height * 100f;
|
||
if (p.X < -p.Width) p.X = -p.Width + 2;
|
||
if (p.X > 100) p.X = 98;
|
||
if (p.Y < 0) p.Y = 0;
|
||
if (p.Y > 100) p.Y = 98;
|
||
dragStart = e.Location;
|
||
chart.Invalidate();
|
||
}
|
||
catch { draggingLegend = false; }
|
||
};
|
||
chart.MouseUp += delegate(object s, MouseEventArgs e)
|
||
{
|
||
draggingLegend = false;
|
||
chart.Cursor = Cursors.Default;
|
||
};
|
||
}
|
||
|
||
void Chart_MouseMove(object sender, MouseEventArgs e)
|
||
{
|
||
if (draggingLegend) return;
|
||
try
|
||
{
|
||
ChartArea area = chart.ChartAreas["main"];
|
||
bool inside = IsInPlotArea(e.Location, area);
|
||
PointF np = inside ? new PointF(e.X, e.Y) : new PointF(-1, -1);
|
||
if (np.X != crossPos.X || np.Y != crossPos.Y)
|
||
{
|
||
crossPos = np;
|
||
chart.Invalidate();
|
||
}
|
||
}
|
||
catch { }
|
||
}
|
||
|
||
void Chart_MouseClick(object sender, MouseEventArgs e)
|
||
{
|
||
if (e.Button != MouseButtons.Left) return;
|
||
try
|
||
{
|
||
HitTestResult hr = chart.HitTest(e.X, e.Y);
|
||
if (hr != null && hr.ChartElementType == ChartElementType.DataPoint &&
|
||
hr.Series != null && hr.PointIndex >= 0 &&
|
||
hr.PointIndex < hr.Series.Points.Count)
|
||
{
|
||
ShowFloatTip(e.Location, hr.Series.Name, hr.Series.Points[hr.PointIndex], hr.PointIndex);
|
||
}
|
||
else
|
||
{
|
||
CloseFloatTip();
|
||
}
|
||
}
|
||
catch { }
|
||
}
|
||
|
||
void Chart_PostPaint(object sender, ChartPaintEventArgs e)
|
||
{
|
||
if (e.ChartElement != chart || !showCross || crossPos.X < 0) return;
|
||
try
|
||
{
|
||
ChartArea area = chart.ChartAreas["main"];
|
||
RectangleF pa = GetPlotAreaRect(area);
|
||
if (pa.Width <= 0 || pa.Height <= 0) return;
|
||
if (!pa.Contains(crossPos)) return;
|
||
using (Pen pen = new Pen(Color.FromArgb(150, 90, 90, 90)))
|
||
{
|
||
pen.DashStyle = DashStyle.Dash;
|
||
Graphics g = e.ChartGraphics.Graphics;
|
||
g.DrawLine(pen, pa.Left, crossPos.Y, pa.Right, crossPos.Y);
|
||
g.DrawLine(pen, crossPos.X, pa.Top, crossPos.X, pa.Bottom);
|
||
}
|
||
}
|
||
catch { }
|
||
}
|
||
|
||
RectangleF GetPlotAreaRect(ChartArea area)
|
||
{
|
||
ElementPosition areaPos = area.Position;
|
||
ElementPosition inner = area.InnerPlotPosition;
|
||
if (areaPos == null || inner == null || inner.Width <= 0 || inner.Height <= 0)
|
||
return RectangleF.Empty;
|
||
float ax = chart.Width * areaPos.X / 100f;
|
||
float ay = chart.Height * areaPos.Y / 100f;
|
||
float aw = chart.Width * areaPos.Width / 100f;
|
||
float ah = chart.Height * areaPos.Height / 100f;
|
||
return new RectangleF(
|
||
ax + aw * inner.X / 100f,
|
||
ay + ah * inner.Y / 100f,
|
||
aw * inner.Width / 100f,
|
||
ah * inner.Height / 100f);
|
||
}
|
||
|
||
bool IsInPlotArea(Point p, ChartArea area)
|
||
{
|
||
RectangleF pa = GetPlotAreaRect(area);
|
||
return pa.Width > 0 && pa.Height > 0 && pa.Contains(p);
|
||
}
|
||
|
||
void ShowFloatTip(Point chartPoint, string seriesName, DataPoint dp, int pointIndex)
|
||
{
|
||
CloseFloatTip();
|
||
string xStr;
|
||
if (!string.IsNullOrEmpty(dp.AxisLabel))
|
||
xStr = dp.AxisLabel;
|
||
else if (dp.XValue > 1000 && dp.XValue < 400000)
|
||
{
|
||
try { xStr = DateTime.FromOADate(dp.XValue).ToString("yyyy-MM-dd HH:mm:ss"); }
|
||
catch { xStr = dp.XValue.ToString("0.####"); }
|
||
}
|
||
else
|
||
xStr = dp.XValue.ToString("0.####");
|
||
string yStr = dp.YValues.Length > 0 ? dp.YValues[0].ToString("0.####") : "N/A";
|
||
string idxStr = pointIndex.ToString();
|
||
|
||
Form f = new Form();
|
||
f.FormBorderStyle = FormBorderStyle.None;
|
||
f.ShowInTaskbar = false;
|
||
f.TopMost = true;
|
||
f.StartPosition = FormStartPosition.Manual;
|
||
f.BackColor = Color.FromArgb(48, 56, 66);
|
||
f.AutoSize = true;
|
||
f.Padding = new Padding(10, 8, 10, 8);
|
||
f.Font = new Font("Microsoft YaHei UI", 9F);
|
||
|
||
TableLayoutPanel tbl = new TableLayoutPanel();
|
||
tbl.AutoSize = true;
|
||
tbl.AutoSizeMode = AutoSizeMode.GrowAndShrink;
|
||
tbl.ColumnCount = 2;
|
||
tbl.RowCount = 4;
|
||
tbl.Dock = DockStyle.Fill;
|
||
f.Controls.Add(tbl);
|
||
|
||
AddTipCell(tbl, 0, 0, "曲线:", true, Color.White);
|
||
AddTipCell(tbl, 1, 0, seriesName, false, Color.FromArgb(110, 190, 255));
|
||
AddTipCell(tbl, 0, 1, "X:", true, Color.White);
|
||
AddTipCell(tbl, 1, 1, xStr, false, Color.White);
|
||
AddTipCell(tbl, 0, 2, "Y:", true, Color.White);
|
||
AddTipCell(tbl, 1, 2, yStr, false, Color.FromArgb(255, 220, 120));
|
||
AddTipCell(tbl, 0, 3, "序号:", true, Color.White);
|
||
AddTipCell(tbl, 1, 3, idxStr, false, Color.White);
|
||
|
||
Point sp = chart.PointToScreen(chartPoint);
|
||
f.Location = new Point(sp.X + 16, sp.Y + 16);
|
||
f.Deactivate += delegate { CloseFloatTip(); };
|
||
f.Show();
|
||
floatTip = f;
|
||
Screen sc = Screen.FromPoint(sp);
|
||
if (f.Location.X + f.Width > sc.WorkingArea.Right)
|
||
f.Location = new Point(sp.X - f.Width - 16, f.Location.Y);
|
||
if (f.Location.Y + f.Height > sc.WorkingArea.Bottom)
|
||
f.Location = new Point(f.Location.X, sp.Y - f.Height - 16);
|
||
}
|
||
|
||
void AddTipCell(TableLayoutPanel tbl, int col, int row, string text, bool bold, Color color)
|
||
{
|
||
Label l = new Label();
|
||
l.Text = text;
|
||
l.AutoSize = true;
|
||
l.Margin = new Padding(2);
|
||
l.ForeColor = color;
|
||
l.Font = new Font("Microsoft YaHei UI", 9F, bold ? FontStyle.Bold : FontStyle.Regular);
|
||
l.BackColor = Color.Transparent;
|
||
tbl.Controls.Add(l, col, row);
|
||
}
|
||
|
||
void CloseFloatTip()
|
||
{
|
||
if (floatTip != null)
|
||
{
|
||
Form f = floatTip;
|
||
floatTip = null;
|
||
try { f.Close(); }
|
||
catch { }
|
||
}
|
||
}
|
||
|
||
Button MakeToolButton(string text)
|
||
{
|
||
Button b = new Button();
|
||
b.Text = text;
|
||
b.FlatStyle = FlatStyle.Flat;
|
||
b.FlatAppearance.BorderColor = BorderLine;
|
||
b.FlatAppearance.BorderSize = 1;
|
||
b.BackColor = Color.White;
|
||
b.ForeColor = Color.FromArgb(60, 70, 80);
|
||
b.Font = new Font("Microsoft YaHei UI", 9F);
|
||
b.Cursor = Cursors.Hand;
|
||
return b;
|
||
}
|
||
|
||
Button MakeIconButton(string text, int iconType)
|
||
{
|
||
Button b = new Button();
|
||
b.Text = "";
|
||
b.Tag = text;
|
||
b.FlatStyle = FlatStyle.Flat;
|
||
b.FlatAppearance.BorderColor = BorderLine;
|
||
b.FlatAppearance.BorderSize = 1;
|
||
b.BackColor = Color.White;
|
||
b.ForeColor = Color.FromArgb(60, 70, 80);
|
||
b.Font = new Font("Microsoft YaHei UI", 9F);
|
||
b.Cursor = Cursors.Hand;
|
||
b.Paint += delegate(object s, PaintEventArgs e)
|
||
{
|
||
Button bb = (Button)s;
|
||
Graphics g = e.Graphics;
|
||
g.SmoothingMode = SmoothingMode.AntiAlias;
|
||
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
|
||
string t = bb.Tag as string;
|
||
if (string.IsNullOrEmpty(t)) return;
|
||
SizeF ts = g.MeasureString(t, bb.Font);
|
||
int totalW = 16 + 6 + (int)Math.Ceiling(ts.Width);
|
||
int x0 = (bb.ClientSize.Width - totalW) / 2;
|
||
if (x0 < 2) x0 = 2;
|
||
int y0 = (bb.ClientSize.Height - 16) / 2;
|
||
using (Pen p = new Pen(bb.ForeColor, 2))
|
||
{
|
||
p.StartCap = LineCap.Round;
|
||
p.EndCap = LineCap.Round;
|
||
if (iconType == 0)
|
||
{
|
||
g.DrawLine(p, x0 + 13, y0 + 8, x0 + 3, y0 + 8);
|
||
g.DrawLine(p, x0 + 3, y0 + 8, x0 + 8, y0 + 3);
|
||
g.DrawLine(p, x0 + 3, y0 + 8, x0 + 8, y0 + 13);
|
||
}
|
||
else if (iconType == 1)
|
||
{
|
||
g.DrawEllipse(p, x0 + 5, y0 + 1, 6, 6);
|
||
g.DrawLine(p, x0 + 8, y0 + 7, x0 + 8, y0 + 13);
|
||
g.DrawLine(p, x0 + 4, y0 + 13, x0 + 12, y0 + 13);
|
||
}
|
||
else
|
||
{
|
||
g.DrawLine(p, x0 + 3, y0 + 8, x0 + 13, y0 + 8);
|
||
g.DrawLine(p, x0 + 13, y0 + 8, x0 + 8, y0 + 3);
|
||
g.DrawLine(p, x0 + 13, y0 + 8, x0 + 8, y0 + 13);
|
||
}
|
||
}
|
||
using (SolidBrush br = new SolidBrush(bb.ForeColor))
|
||
{
|
||
g.DrawString(t, bb.Font, br, x0 + 22, (bb.ClientSize.Height - ts.Height) / 2f);
|
||
}
|
||
};
|
||
return b;
|
||
}
|
||
|
||
ComboBox MakeCombo()
|
||
{
|
||
ComboBox c = new ComboBox();
|
||
c.DropDownStyle = ComboBoxStyle.DropDownList;
|
||
c.FlatStyle = FlatStyle.Flat;
|
||
c.BackColor = Color.White;
|
||
return c;
|
||
}
|
||
|
||
Button MakeSmallButton(string text)
|
||
{
|
||
Button b = new Button();
|
||
b.Text = text;
|
||
b.FlatStyle = FlatStyle.Flat;
|
||
b.FlatAppearance.BorderSize = 0;
|
||
b.ForeColor = Color.White;
|
||
b.Font = new Font("Microsoft YaHei UI", 9F, FontStyle.Bold);
|
||
b.Cursor = Cursors.Hand;
|
||
return b;
|
||
}
|
||
|
||
void AddLabel(Control parent, string text, int x, int y)
|
||
{
|
||
Label l = new Label();
|
||
l.Text = text;
|
||
l.Location = new Point(x, y + 4);
|
||
l.AutoSize = false;
|
||
l.Width = 84;
|
||
l.Height = 24;
|
||
l.TextAlign = ContentAlignment.MiddleRight;
|
||
l.ForeColor = Color.FromArgb(60, 70, 80);
|
||
parent.Controls.Add(l);
|
||
}
|
||
|
||
void AddLabel2(Control parent, string text, int x, int y)
|
||
{
|
||
Label l = new Label();
|
||
l.Text = text;
|
||
l.Location = new Point(x, y + 4);
|
||
l.AutoSize = false;
|
||
l.Width = 38;
|
||
l.Height = 24;
|
||
l.TextAlign = ContentAlignment.MiddleRight;
|
||
l.ForeColor = Color.FromArgb(60, 70, 80);
|
||
parent.Controls.Add(l);
|
||
}
|
||
|
||
void AddLabel3(Control parent, string text, int x, int y)
|
||
{
|
||
Label l = new Label();
|
||
l.Text = text;
|
||
l.Location = new Point(x, y + 4);
|
||
l.AutoSize = false;
|
||
l.Width = 90;
|
||
l.Height = 24;
|
||
l.TextAlign = ContentAlignment.MiddleRight;
|
||
l.ForeColor = Color.FromArgb(60, 70, 80);
|
||
parent.Controls.Add(l);
|
||
}
|
||
|
||
TextBox AddText(Control parent, string text, int x, int y, int width)
|
||
{
|
||
return AddText(parent, text, x, y, width, false);
|
||
}
|
||
|
||
TextBox AddText(Control parent, string text, int x, int y, int width, bool password)
|
||
{
|
||
TextBox t = new TextBox();
|
||
t.Text = text;
|
||
t.Location = new Point(x, y);
|
||
t.Width = width;
|
||
t.BorderStyle = BorderStyle.FixedSingle;
|
||
t.UseSystemPasswordChar = password;
|
||
parent.Controls.Add(t);
|
||
return t;
|
||
}
|
||
|
||
void LayoutPanels(Panel leftP, Panel rightP, Panel splitterP)
|
||
{
|
||
int w = ClientSize.Width;
|
||
int h = ClientSize.Height;
|
||
if (w < 100 || h < 100) return;
|
||
int lw = leftP.Visible ? leftP.Width : 0;
|
||
if (lw > 0)
|
||
{
|
||
leftP.Location = new Point(0, 0);
|
||
leftP.Size = new Size(lw, h);
|
||
leftP.Padding = new Padding(12, 8, 12, 8);
|
||
splitterP.Location = new Point(lw, 0);
|
||
splitterP.Size = new Size(5, h);
|
||
splitterP.Visible = true;
|
||
rightP.Location = new Point(lw + 5, 0);
|
||
}
|
||
else
|
||
{
|
||
splitterP.Visible = false;
|
||
rightP.Location = new Point(0, 0);
|
||
}
|
||
rightP.Size = new Size(w - rightP.Location.X, h);
|
||
}
|
||
|
||
const int MinLeftLayoutW = 380;
|
||
|
||
void LayoutLeftPanel()
|
||
{
|
||
try
|
||
{
|
||
int layW = Math.Max(left.ClientSize.Width, MinLeftLayoutW);
|
||
int x2 = 8;
|
||
int availW = layW - 12 - x2;
|
||
int inputX = x2 + 88;
|
||
int inputW = availW - 88;
|
||
int gap = 12;
|
||
|
||
int leftColW = (int)(availW * 0.54);
|
||
int w1 = leftColW - 88 - gap;
|
||
if (w1 < 100) w1 = 100;
|
||
int rightLabelX = x2 + leftColW;
|
||
int rightInputX = rightLabelX + 38 + gap;
|
||
int w2 = availW - leftColW - 38 - gap;
|
||
if (w2 < 90) w2 = 90;
|
||
|
||
int bw2 = (availW - 8) / 2;
|
||
|
||
btnCollapse.Location = new Point(x2, 4);
|
||
btnCollapse.Size = new Size(bw2, 30);
|
||
btnPin.Location = new Point(x2 + bw2 + 8, 4);
|
||
btnPin.Size = new Size(availW - bw2 - 8, 30);
|
||
|
||
txtHost.Location = new Point(inputX, 74);
|
||
txtHost.Width = w1;
|
||
txtPort.Location = new Point(rightInputX, 74);
|
||
txtPort.Width = w2;
|
||
txtUser.Location = new Point(inputX, 110);
|
||
txtUser.Width = w1;
|
||
txtPassword.Location = new Point(rightInputX, 110);
|
||
txtPassword.Width = w2;
|
||
|
||
cmbDatabase.Location = new Point(inputX, 146);
|
||
cmbDatabase.Width = inputW;
|
||
btnConnect.Location = new Point(x2, 184);
|
||
btnConnect.Width = availW;
|
||
|
||
cmbOrder.Location = new Point(inputX, 260);
|
||
cmbOrder.Width = w1;
|
||
txtLimit.Location = new Point(rightInputX, 260);
|
||
txtLimit.Width = w2;
|
||
|
||
txtOffset.Location = new Point(inputX, 296);
|
||
txtOffset.Width = inputW;
|
||
txtTailOffset.Location = new Point(inputX, 338);
|
||
txtTailOffset.Width = inputW;
|
||
|
||
cmbTable.Location = new Point(inputX, 410);
|
||
cmbTable.Width = inputW;
|
||
cmbX.Location = new Point(inputX, 446);
|
||
cmbX.Width = inputW;
|
||
cmbXType.Location = new Point(inputX, 482);
|
||
cmbXType.Width = inputW;
|
||
lbY.Location = new Point(inputX, 518);
|
||
lbY.Width = inputW;
|
||
|
||
dgvFilter.Location = new Point(x2, 676);
|
||
dgvFilter.Width = availW;
|
||
|
||
int bw3 = (availW - 16) / 3;
|
||
btnAddRule.Location = new Point(x2, 824);
|
||
btnAddRule.Width = bw3;
|
||
btnDelRule.Location = new Point(x2 + bw3 + 8, 824);
|
||
btnDelRule.Width = bw3;
|
||
btnClear.Location = new Point(x2 + bw3 * 2 + 16, 824);
|
||
btnClear.Width = availW - bw3 * 2 - 16;
|
||
|
||
btnPlot.Location = new Point(x2, 862);
|
||
btnPlot.Width = bw2;
|
||
btnSave.Location = new Point(x2 + bw2 + 8, 862);
|
||
btnSave.Width = availW - bw2 - 8;
|
||
|
||
lblStatus.Location = new Point(x2, 914);
|
||
lblStatus.Width = availW;
|
||
|
||
SetLabelX("端口:", rightLabelX);
|
||
SetLabelX("密码:", rightLabelX);
|
||
SetLabelX("条数:", rightLabelX);
|
||
SetLabelX("跳过前 N 行", inputX + inputW + 8);
|
||
SetLabelX("从最新数据往回数 N 条作为查询起点", inputX);
|
||
}
|
||
catch { }
|
||
}
|
||
|
||
Label FindLabel(string text)
|
||
{
|
||
foreach (Control c in left.Controls)
|
||
{
|
||
if (c is Label && c.Text == text) return (Label)c;
|
||
}
|
||
return null;
|
||
}
|
||
|
||
void SetLabelX(string text, int x)
|
||
{
|
||
Label l = FindLabel(text);
|
||
if (l != null) l.Location = new Point(x, l.Location.Y);
|
||
}
|
||
|
||
void TogglePin()
|
||
{
|
||
pinned = !pinned;
|
||
if (pinned)
|
||
{
|
||
btnPin.BackColor = ThemeBlue;
|
||
btnPin.ForeColor = Color.White;
|
||
btnPin.Tag = "已钉住";
|
||
}
|
||
else
|
||
{
|
||
btnPin.BackColor = Color.White;
|
||
btnPin.ForeColor = Color.FromArgb(60, 70, 80);
|
||
btnPin.Tag = "钉住";
|
||
}
|
||
}
|
||
|
||
void SetupSplitterDrag(Panel splitterP)
|
||
{
|
||
bool dragging = false;
|
||
int startX = 0;
|
||
int startWidth = 0;
|
||
splitterP.MouseDown += delegate(object s, MouseEventArgs e)
|
||
{
|
||
if (pinned) return;
|
||
dragging = true;
|
||
startX = e.X;
|
||
startWidth = left.Width;
|
||
splitterP.Capture = true;
|
||
};
|
||
splitterP.MouseMove += delegate(object s, MouseEventArgs e)
|
||
{
|
||
if (!dragging) return;
|
||
int newWidth = startWidth + (e.X - startX);
|
||
if (newWidth < 380) newWidth = 380;
|
||
if (newWidth > 800) newWidth = 800;
|
||
left.Width = newWidth;
|
||
LayoutPanels(left, (Panel)right, splitterP);
|
||
};
|
||
splitterP.MouseUp += delegate(object s, MouseEventArgs e)
|
||
{
|
||
dragging = false;
|
||
splitterP.Capture = false;
|
||
};
|
||
}
|
||
|
||
void FillConnFields()
|
||
{
|
||
txtHost.Text = config.Host;
|
||
txtPort.Text = config.Port;
|
||
txtUser.Text = config.User;
|
||
txtPassword.Text = config.Password;
|
||
cmbDatabase.Items.Clear();
|
||
if (!string.IsNullOrEmpty(config.Database))
|
||
{
|
||
cmbDatabase.Items.Add(config.Database);
|
||
cmbDatabase.SelectedIndex = 0;
|
||
}
|
||
}
|
||
|
||
void SaveConfigFromFields()
|
||
{
|
||
config.Host = txtHost.Text.Trim();
|
||
config.Port = txtPort.Text.Trim();
|
||
config.User = txtUser.Text.Trim();
|
||
config.Password = txtPassword.Text;
|
||
string db = cmbDatabase.SelectedItem as string;
|
||
if (string.IsNullOrEmpty(db)) db = cmbDatabase.Text.Trim();
|
||
config.Database = db == null ? "" : db;
|
||
try { config.Save(configPath); }
|
||
catch { }
|
||
}
|
||
|
||
void SetStatus(string text, bool error)
|
||
{
|
||
lblStatus.Text = text;
|
||
lblStatus.ForeColor = error ? Color.FromArgb(192, 57, 43) : Color.FromArgb(39, 120, 60);
|
||
}
|
||
|
||
MySqlConnection OpenConn()
|
||
{
|
||
SaveConfigFromFields();
|
||
return OpenConn(config.Database);
|
||
}
|
||
|
||
MySqlConnection OpenConn(string db)
|
||
{
|
||
try
|
||
{
|
||
MySqlConnectionStringBuilder b = new MySqlConnectionStringBuilder();
|
||
b.Server = config.Host;
|
||
b.Port = uint.Parse(config.Port);
|
||
b.UserID = config.User;
|
||
b.Password = config.Password;
|
||
if (!string.IsNullOrEmpty(db)) b.Database = db;
|
||
b.CharacterSet = config.Charset;
|
||
b.ConnectionTimeout = 10;
|
||
MySqlConnection conn = new MySqlConnection(b.ConnectionString);
|
||
conn.Open();
|
||
return conn;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
SetStatus("连接失败: " + ex.Message, true);
|
||
MessageBox.Show(this, "无法连接数据库:\n" + ex.Message, "连接失败",
|
||
MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||
return null;
|
||
}
|
||
}
|
||
|
||
void ConnectAndLoad()
|
||
{
|
||
SaveConfigFromFields();
|
||
MySqlConnection conn = OpenConn("");
|
||
if (conn == null) return;
|
||
try
|
||
{
|
||
List<string> dbs = new List<string>();
|
||
using (MySqlCommand cmd = new MySqlCommand("SHOW DATABASES", conn))
|
||
using (IDataReader r = cmd.ExecuteReader())
|
||
{
|
||
while (r.Read()) dbs.Add(r.GetString(0));
|
||
}
|
||
string prevDb = config.Database;
|
||
loadingDbs = true;
|
||
cmbDatabase.Items.Clear();
|
||
foreach (string d in dbs) cmbDatabase.Items.Add(d);
|
||
int sel = dbs.IndexOf(prevDb);
|
||
if (sel >= 0) cmbDatabase.SelectedIndex = sel;
|
||
else if (dbs.Count > 0) cmbDatabase.SelectedIndex = 0;
|
||
loadingDbs = false;
|
||
config.Database = cmbDatabase.SelectedItem as string;
|
||
if (string.IsNullOrEmpty(config.Database))
|
||
{
|
||
SetStatus("连接成功,但没有可用数据库", true);
|
||
return;
|
||
}
|
||
try { config.Save(configPath); } catch { }
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
SetStatus("读取数据库列表失败: " + ex.Message, true);
|
||
return;
|
||
}
|
||
finally
|
||
{
|
||
conn.Close();
|
||
}
|
||
MySqlConnection conn2 = OpenConn(config.Database);
|
||
if (conn2 == null) return;
|
||
try { LoadTables(conn2); }
|
||
finally { conn2.Close(); }
|
||
}
|
||
|
||
void DatabaseSelected()
|
||
{
|
||
if (loadingDbs) return;
|
||
string db = cmbDatabase.SelectedItem as string;
|
||
if (string.IsNullOrEmpty(db)) db = cmbDatabase.Text.Trim();
|
||
if (string.IsNullOrEmpty(db)) return;
|
||
SaveConfigFromFields();
|
||
config.Database = db;
|
||
try { config.Save(configPath); } catch { }
|
||
MySqlConnection conn = OpenConn(db);
|
||
if (conn == null) return;
|
||
try { LoadTables(conn); }
|
||
finally { conn.Close(); }
|
||
}
|
||
|
||
void LoadTables(MySqlConnection conn)
|
||
{
|
||
try
|
||
{
|
||
List<string> tables = new List<string>();
|
||
using (MySqlCommand cmd = new MySqlCommand("SHOW TABLES", conn))
|
||
using (IDataReader r = cmd.ExecuteReader())
|
||
{
|
||
while (r.Read()) tables.Add(r.GetString(0));
|
||
}
|
||
cmbTable.Items.Clear();
|
||
foreach (string t in tables) cmbTable.Items.Add(t);
|
||
if (tables.Count > 0) cmbTable.SelectedIndex = 0;
|
||
SetStatus("已连接,共 " + tables.Count + " 张表", false);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
SetStatus("读取表列表失败: " + ex.Message, true);
|
||
}
|
||
}
|
||
|
||
void TableSelected()
|
||
{
|
||
string table = cmbTable.SelectedItem as string;
|
||
if (string.IsNullOrEmpty(table)) return;
|
||
MySqlConnection conn = OpenConn();
|
||
if (conn == null) return;
|
||
try
|
||
{
|
||
List<string> cols = new List<string>();
|
||
using (MySqlCommand cmd = new MySqlCommand(
|
||
"SELECT * FROM `" + EscapeIdent(table) + "` LIMIT 0", conn))
|
||
using (IDataReader r = cmd.ExecuteReader())
|
||
{
|
||
for (int i = 0; i < r.FieldCount; i++)
|
||
cols.Add(r.GetName(i));
|
||
}
|
||
allColumns = cols;
|
||
cmbX.Items.Clear();
|
||
foreach (string c in cols) cmbX.Items.Add(c);
|
||
if (cols.Count > 0) cmbX.SelectedIndex = 0;
|
||
lbY.Items.Clear();
|
||
foreach (string c in cols) lbY.Items.Add(c);
|
||
DataGridViewComboBoxColumn colCol = (DataGridViewComboBoxColumn)dgvFilter.Columns[0];
|
||
colCol.Items.Clear();
|
||
foreach (string c in cols) colCol.Items.Add(c);
|
||
dgvFilter.Rows.Clear();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
SetStatus("读取列失败: " + ex.Message, true);
|
||
}
|
||
finally
|
||
{
|
||
conn.Close();
|
||
}
|
||
}
|
||
|
||
static string EscapeIdent(string name)
|
||
{
|
||
return name.Replace("`", "``");
|
||
}
|
||
|
||
void AddRule()
|
||
{
|
||
if (allColumns.Count == 0)
|
||
{
|
||
MessageBox.Show(this, "请先连接并选择存储表", "提示");
|
||
return;
|
||
}
|
||
int idx = dgvFilter.Rows.Add();
|
||
dgvFilter.Rows[idx].Cells[0].Value = allColumns[0];
|
||
dgvFilter.Rows[idx].Cells[1].Value = "=";
|
||
dgvFilter.Rows[idx].Cells[3].Value = "AND";
|
||
}
|
||
|
||
void DelRule()
|
||
{
|
||
if (dgvFilter.SelectedRows.Count > 0)
|
||
dgvFilter.Rows.Remove(dgvFilter.SelectedRows[0]);
|
||
else if (dgvFilter.Rows.Count > 0)
|
||
dgvFilter.Rows.RemoveAt(dgvFilter.Rows.Count - 1);
|
||
}
|
||
|
||
string BuildFilterCondition(DataGridViewRow row)
|
||
{
|
||
string col = (row.Cells[0].Value == null ? "" : row.Cells[0].Value.ToString().Trim());
|
||
string op = (row.Cells[1].Value == null ? "" : row.Cells[1].Value.ToString().Trim());
|
||
string val = (row.Cells[2].Value == null ? "" : row.Cells[2].Value.ToString().Trim());
|
||
if (col.Length == 0 || op.Length == 0) return null;
|
||
string colId = "`" + EscapeIdent(col) + "`";
|
||
string esc = val.Replace("'", "''");
|
||
switch (op)
|
||
{
|
||
case "IS NULL": return colId + " IS NULL";
|
||
case "IS NOT NULL": return colId + " IS NOT NULL";
|
||
case "BETWEEN":
|
||
string[] parts = val.Split(',');
|
||
if (parts.Length != 2) return null;
|
||
return colId + " BETWEEN '" + parts[0].Trim().Replace("'", "''")
|
||
+ "' AND '" + parts[1].Trim().Replace("'", "''") + "'";
|
||
case "LIKE":
|
||
if (val.Length == 0) return null;
|
||
return colId + " LIKE '" + esc + "'";
|
||
default:
|
||
if (val.Length == 0) return null;
|
||
return colId + " " + op + " '" + esc + "'";
|
||
}
|
||
}
|
||
|
||
class FilterItem
|
||
{
|
||
public string Cond;
|
||
public bool IsOr;
|
||
}
|
||
|
||
List<FilterItem> CollectFilterItems()
|
||
{
|
||
List<FilterItem> items = new List<FilterItem>();
|
||
foreach (DataGridViewRow row in dgvFilter.Rows)
|
||
{
|
||
if (row.IsNewRow) continue;
|
||
string cond = BuildFilterCondition(row);
|
||
if (cond == null) continue;
|
||
FilterItem it = new FilterItem();
|
||
it.Cond = cond;
|
||
object lv = row.Cells[3].Value;
|
||
it.IsOr = (lv != null && lv.ToString().Trim().Equals("OR", StringComparison.OrdinalIgnoreCase));
|
||
items.Add(it);
|
||
}
|
||
return items;
|
||
}
|
||
|
||
string BuildWhere(List<FilterItem> items)
|
||
{
|
||
if (items.Count == 0) return "";
|
||
StringBuilder sb = new StringBuilder(" WHERE ");
|
||
for (int i = 0; i < items.Count; i++)
|
||
{
|
||
if (i > 0) sb.Append(items[i - 1].IsOr ? " OR " : " AND ");
|
||
sb.Append(items[i].Cond);
|
||
}
|
||
return sb.ToString();
|
||
}
|
||
|
||
string BuildOrderLimit(string xcol)
|
||
{
|
||
bool desc = cmbOrder.SelectedIndex == 1;
|
||
int limit = 0, offset = 0, tail = 0;
|
||
int.TryParse(txtLimit.Text.Trim(), out limit);
|
||
int.TryParse(txtOffset.Text.Trim(), out offset);
|
||
int.TryParse(txtTailOffset.Text.Trim(), out tail);
|
||
StringBuilder sb = new StringBuilder();
|
||
if (tail > 0)
|
||
{
|
||
sb.Append(" ORDER BY `").Append(EscapeIdent(xcol)).Append("` DESC");
|
||
if (limit > 0)
|
||
{
|
||
sb.Append(" LIMIT ").Append(limit);
|
||
sb.Append(" OFFSET ").Append(tail);
|
||
}
|
||
return sb.ToString();
|
||
}
|
||
sb.Append(" ORDER BY `").Append(EscapeIdent(xcol)).Append("` ");
|
||
sb.Append(desc ? "DESC" : "ASC");
|
||
if (limit > 0)
|
||
{
|
||
sb.Append(" LIMIT ").Append(limit);
|
||
if (offset > 0)
|
||
sb.Append(" OFFSET ").Append(offset);
|
||
}
|
||
return sb.ToString();
|
||
}
|
||
|
||
void Plot()
|
||
{
|
||
string table = cmbTable.SelectedItem as string;
|
||
string xcol = cmbX.SelectedItem as string;
|
||
List<string> ycols = new List<string>();
|
||
foreach (object o in lbY.SelectedItems) ycols.Add(o.ToString());
|
||
if (string.IsNullOrEmpty(table) || string.IsNullOrEmpty(xcol))
|
||
{
|
||
MessageBox.Show(this, "请先选择存储表和横坐标", "提示");
|
||
return;
|
||
}
|
||
if (ycols.Count == 0)
|
||
{
|
||
MessageBox.Show(this, "请至少选择一条 Y 轴曲线列", "提示");
|
||
return;
|
||
}
|
||
List<FilterItem> filters = CollectFilterItems();
|
||
btnPlot.Enabled = false;
|
||
Thread t = new Thread(delegate () { DoPlot(table, xcol, ycols, filters); });
|
||
t.IsBackground = true;
|
||
t.Start();
|
||
}
|
||
|
||
void DoPlot(string table, string xcol, List<string> ycols, List<FilterItem> filters)
|
||
{
|
||
MySqlConnection conn = null;
|
||
try
|
||
{
|
||
conn = OpenConn();
|
||
if (conn == null) return;
|
||
StringBuilder sb = new StringBuilder();
|
||
sb.Append("SELECT `").Append(EscapeIdent(xcol)).Append("`");
|
||
foreach (string y in ycols)
|
||
sb.Append(", `").Append(EscapeIdent(y)).Append("`");
|
||
sb.Append(" FROM `").Append(EscapeIdent(table)).Append("`");
|
||
sb.Append(BuildWhere(filters));
|
||
sb.Append(BuildOrderLimit(xcol));
|
||
List<object[]> rows = new List<object[]>();
|
||
using (MySqlCommand cmd = new MySqlCommand(sb.ToString(), conn))
|
||
using (IDataReader r = cmd.ExecuteReader())
|
||
{
|
||
while (r.Read())
|
||
{
|
||
object[] vals = new object[r.FieldCount];
|
||
r.GetValues(vals);
|
||
rows.Add(vals);
|
||
}
|
||
}
|
||
BeginInvoke(new Action(delegate ()
|
||
{
|
||
lastRows = rows;
|
||
lastXcol = xcol;
|
||
lastYcols = ycols;
|
||
lastTable = table;
|
||
int tailOfs = 0;
|
||
int.TryParse(txtTailOffset.Text.Trim(), out tailOfs);
|
||
if (cmbOrder.SelectedIndex == 1 || tailOfs > 0)
|
||
rows.Reverse();
|
||
RenderPlot(rows, xcol, ycols, table);
|
||
}));
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
BeginInvoke(new Action(delegate ()
|
||
{
|
||
SetStatus("查询失败: " + ex.Message, true);
|
||
MessageBox.Show(this, "查询失败:\n" + ex.Message, "错误",
|
||
MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||
}));
|
||
}
|
||
finally
|
||
{
|
||
if (conn != null) conn.Close();
|
||
BeginInvoke(new Action(delegate () { btnPlot.Enabled = true; }));
|
||
}
|
||
}
|
||
|
||
void RenderPlot(List<object[]> rows, string xcol, List<string> ycols, string table)
|
||
{
|
||
CloseFloatTip();
|
||
chart.Series.Clear();
|
||
calcSeriesNames.Clear();
|
||
paletteCounter = 0;
|
||
lblChartTitle.Text = table + " 曲线图";
|
||
if (rows.Count == 0)
|
||
{
|
||
SetStatus("没有符合条件的数据", true);
|
||
return;
|
||
}
|
||
cmbCalcTarget.Items.Clear();
|
||
foreach (string y in ycols) cmbCalcTarget.Items.Add(y);
|
||
if (cmbCalcTarget.Items.Count > 0) cmbCalcTarget.SelectedIndex = 0;
|
||
XAxisInfo xa = AnalyzeXAxis(rows);
|
||
bool xNumeric = xa.Numeric;
|
||
bool xIsTime = xa.IsTime;
|
||
bool xSparseTime = xa.Sparse;
|
||
bool hasTime = xa.HasTime;
|
||
DateTime[] xTimes = xa.Times;
|
||
bool[] xTimeOk = xa.Ok;
|
||
foreach (string y in ycols)
|
||
{
|
||
Series s = new Series(y);
|
||
s.ChartType = SeriesChartType.Line;
|
||
s.BorderWidth = 2;
|
||
s.Color = NextColor();
|
||
for (int i = 0; i < rows.Count; i++)
|
||
{
|
||
object[] row = rows[i];
|
||
object xv = row[0];
|
||
object yv = row[1 + ycols.IndexOf(y)];
|
||
double yNum;
|
||
if (yv == null || yv == DBNull.Value || !TryToDouble(yv, out yNum))
|
||
continue;
|
||
if (xIsTime)
|
||
{
|
||
if (xSparseTime)
|
||
{
|
||
string xStr = xv == null ? "" : xv.ToString();
|
||
s.Points.AddXY(xStr, yNum);
|
||
}
|
||
else
|
||
{
|
||
if (!xTimeOk[i]) continue;
|
||
s.Points.AddXY(xTimes[i], yNum);
|
||
}
|
||
}
|
||
else if (xNumeric)
|
||
{
|
||
double xNum;
|
||
if (xv == null || xv == DBNull.Value || !TryToDouble(xv, out xNum))
|
||
continue;
|
||
s.Points.AddXY(xNum, yNum);
|
||
}
|
||
else
|
||
{
|
||
string xStr = xv == null ? "" : xv.ToString();
|
||
s.Points.AddXY(xStr, yNum);
|
||
}
|
||
}
|
||
chart.Series.Add(s);
|
||
}
|
||
ChartArea area = chart.ChartAreas["main"];
|
||
area.AxisX.Title = xcol;
|
||
area.AxisY.Title = "数值";
|
||
area.AxisX.LabelStyle.Angle = xNumeric ? 0 : -45;
|
||
if (xIsTime)
|
||
{
|
||
if (xSparseTime)
|
||
{
|
||
area.AxisX.LabelStyle.Format = "";
|
||
area.AxisX.LabelStyle.Angle = -45;
|
||
area.AxisX.IntervalAutoMode = IntervalAutoMode.VariableCount;
|
||
SetStatus("绘制完成: 表 " + table + ", X 轴 " + xcol + " (批次时间轴)", false);
|
||
}
|
||
else
|
||
{
|
||
area.AxisX.LabelStyle.Format = hasTime ? "yyyy-MM-dd HH:mm:ss" : "yyyy-MM-dd";
|
||
area.AxisX.IntervalAutoMode = IntervalAutoMode.VariableCount;
|
||
SetStatus("绘制完成: 表 " + table + ", X 轴 " + xcol + ", 曲线 " + ycols.Count + " 条", false);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
area.AxisX.LabelStyle.Format = "";
|
||
SetStatus("绘制完成: 表 " + table + ", X 轴 " + xcol + ", 曲线 " + ycols.Count + " 条", false);
|
||
}
|
||
RenderLegend();
|
||
}
|
||
|
||
class XAxisInfo
|
||
{
|
||
public bool Numeric;
|
||
public bool IsTime;
|
||
public bool Sparse;
|
||
public bool HasTime;
|
||
public DateTime[] Times;
|
||
public bool[] Ok;
|
||
}
|
||
|
||
XAxisInfo AnalyzeXAxis(List<object[]> rows)
|
||
{
|
||
XAxisInfo info = new XAxisInfo();
|
||
int n = rows.Count;
|
||
info.Times = new DateTime[n];
|
||
info.Ok = new bool[n];
|
||
int mode = cmbXType.SelectedIndex;
|
||
if (mode < 0) mode = 0;
|
||
if (mode == 2)
|
||
{
|
||
info.Numeric = true;
|
||
return info;
|
||
}
|
||
if (mode == 3)
|
||
{
|
||
info.Numeric = false;
|
||
return info;
|
||
}
|
||
bool forceTime = (mode == 1);
|
||
bool xNumeric = true;
|
||
int timeCount = 0, badCount = 0;
|
||
for (int i = 0; i < n; i++)
|
||
{
|
||
object xv = rows[i][0];
|
||
if (xv == null || xv == DBNull.Value) continue;
|
||
if (xv is DateTime)
|
||
{
|
||
timeCount++;
|
||
info.Times[i] = (DateTime)xv;
|
||
info.Ok[i] = true;
|
||
if (((DateTime)xv).TimeOfDay != TimeSpan.Zero) info.HasTime = true;
|
||
}
|
||
else
|
||
{
|
||
string s = xv as string;
|
||
if (s != null)
|
||
{
|
||
DateTime dt;
|
||
if (TryParseTime(s, out dt))
|
||
{
|
||
timeCount++;
|
||
info.Times[i] = dt;
|
||
info.Ok[i] = true;
|
||
if (dt.TimeOfDay != TimeSpan.Zero) info.HasTime = true;
|
||
continue;
|
||
}
|
||
badCount++;
|
||
}
|
||
else if (!IsNumeric(xv))
|
||
{
|
||
badCount++;
|
||
}
|
||
if (!forceTime && !IsNumeric(xv)) xNumeric = false;
|
||
}
|
||
}
|
||
if (timeCount > 0)
|
||
{
|
||
if (!forceTime && badCount > 0 && (double)timeCount / (timeCount + badCount) < 0.8)
|
||
{
|
||
info.Numeric = false;
|
||
return info;
|
||
}
|
||
info.IsTime = true;
|
||
info.Numeric = true;
|
||
info.Sparse = IsTimeSparse(info.Times, info.Ok, n);
|
||
return info;
|
||
}
|
||
info.Numeric = xNumeric;
|
||
return info;
|
||
}
|
||
|
||
bool IsTimeSparse(DateTime[] times, bool[] ok, int count)
|
||
{
|
||
List<double> gaps = new List<double>();
|
||
bool hasPrev = false;
|
||
DateTime prev = DateTime.MinValue;
|
||
for (int i = 0; i < count; i++)
|
||
{
|
||
if (!ok[i]) continue;
|
||
if (hasPrev) gaps.Add((times[i] - prev).TotalSeconds);
|
||
prev = times[i];
|
||
hasPrev = true;
|
||
}
|
||
if (gaps.Count < 5) return false;
|
||
double[] sorted = new double[gaps.Count];
|
||
gaps.CopyTo(sorted);
|
||
Array.Sort(sorted);
|
||
double median = sorted[sorted.Length / 2];
|
||
double maxGap = sorted[sorted.Length - 1];
|
||
if (median <= 0) return false;
|
||
return maxGap > median * 30 && maxGap > 3600;
|
||
}
|
||
|
||
void RenderLegend()
|
||
{ try
|
||
{
|
||
legendFlow.Controls.Clear();
|
||
foreach (Series s in chart.Series)
|
||
{
|
||
Panel item = new Panel();
|
||
item.AutoSize = true;
|
||
item.BackColor = Color.Transparent;
|
||
item.Margin = new Padding(0, 0, 12, 0);
|
||
|
||
Label sw = new Label();
|
||
sw.AutoSize = false;
|
||
sw.Width = 14;
|
||
sw.Height = 14;
|
||
sw.BackColor = s.Color;
|
||
sw.Location = new Point(0, 3);
|
||
|
||
Label nm = new Label();
|
||
nm.Text = s.Name;
|
||
nm.AutoSize = true;
|
||
nm.Font = new Font("Microsoft YaHei UI", 9F);
|
||
nm.ForeColor = Color.FromArgb(60, 70, 80);
|
||
nm.Location = new Point(18, 1);
|
||
|
||
item.Controls.Add(sw);
|
||
item.Controls.Add(nm);
|
||
legendFlow.Controls.Add(item);
|
||
}
|
||
int lines = 1;
|
||
float totalW = 0;
|
||
float availW = Math.Max(200, right.ClientSize.Width - 30);
|
||
foreach (Control c in legendFlow.Controls)
|
||
{
|
||
Label nm = (Label)c.Controls[1];
|
||
totalW += 18 + nm.PreferredSize.Width + 12;
|
||
}
|
||
if (totalW > availW && chart.Series.Count > 0)
|
||
lines = Math.Max(1, (int)Math.Ceiling(totalW / availW));
|
||
legendBar.Height = lines * 26 + 8;
|
||
legendBar.Visible = chart.Series.Count > 0 && !legendHiddenByUser;
|
||
}
|
||
catch { }
|
||
}
|
||
|
||
Color NextColor()
|
||
{
|
||
Color[] palette = {
|
||
Color.RoyalBlue, Color.OrangeRed, Color.ForestGreen, Color.Purple,
|
||
Color.DarkOrange, Color.Teal, Color.Crimson, Color.DarkGreen,
|
||
Color.SteelBlue, Color.IndianRed, Color.Olive, Color.MediumVioletRed,
|
||
Color.SlateBlue, Color.SeaGreen, Color.Chocolate, Color.CadetBlue
|
||
};
|
||
Color c = palette[paletteCounter % palette.Length];
|
||
paletteCounter++;
|
||
return c;
|
||
}
|
||
|
||
public void RunDrawTest()
|
||
{
|
||
try
|
||
{
|
||
StringBuilder log = new StringBuilder();
|
||
MySqlConnection conn = OpenConn();
|
||
if (conn == null) return;
|
||
string table = "";
|
||
using (MySqlCommand cmd = new MySqlCommand("SHOW TABLES", conn))
|
||
using (IDataReader r = cmd.ExecuteReader())
|
||
{
|
||
List<string> tables = new List<string>();
|
||
while (r.Read()) tables.Add(r.GetString(0));
|
||
if (tables.Count == 0) throw new Exception("数据库中没有表");
|
||
table = tables[0];
|
||
}
|
||
List<string> cols = new List<string>();
|
||
using (MySqlCommand cmd = new MySqlCommand("SELECT * FROM `" + EscapeIdent(table) + "` LIMIT 0", conn))
|
||
using (IDataReader r = cmd.ExecuteReader())
|
||
{
|
||
for (int i = 0; i < r.FieldCount; i++) cols.Add(r.GetName(i));
|
||
}
|
||
string xcol = cols.IndexOf("Time") >= 0 ? "Time" : (cols.Count > 0 ? cols[0] : "");
|
||
List<string> ycols = new List<string>();
|
||
if (cols.IndexOf("DataValue") >= 0) ycols.Add("DataValue");
|
||
if (ycols.Count == 0 && cols.Count > 1) ycols.Add(cols[1]);
|
||
if (xcol.Length == 0 || ycols.Count == 0) throw new Exception("无法确定横/纵坐标列");
|
||
StringBuilder sb = new StringBuilder();
|
||
sb.Append("SELECT `").Append(EscapeIdent(xcol)).Append("`");
|
||
foreach (string y in ycols)
|
||
sb.Append(", `").Append(EscapeIdent(y)).Append("`");
|
||
sb.Append(" FROM `").Append(EscapeIdent(table)).Append("`");
|
||
sb.Append(" ORDER BY `").Append(EscapeIdent(xcol)).Append("` ASC");
|
||
sb.Append(" LIMIT 300");
|
||
List<object[]> rows = new List<object[]>();
|
||
using (MySqlCommand cmd = new MySqlCommand(sb.ToString(), conn))
|
||
using (IDataReader r = cmd.ExecuteReader())
|
||
{
|
||
while (r.Read())
|
||
{
|
||
object[] vals = new object[r.FieldCount];
|
||
r.GetValues(vals);
|
||
rows.Add(vals);
|
||
}
|
||
}
|
||
conn.Close();
|
||
log.AppendLine("查询行数: " + rows.Count);
|
||
lastRows = rows;
|
||
lastXcol = xcol;
|
||
lastYcols = ycols;
|
||
lastTable = table;
|
||
RenderPlot(rows, xcol, ycols, table);
|
||
Application.DoEvents();
|
||
Thread.Sleep(500);
|
||
lblChartTitle.Text = table + " 曲线图 (drawtest)";
|
||
chart.SaveImage(Path.Combine(Path.GetDirectoryName(Application.ExecutablePath), "drawtest.png"), ChartImageFormat.Png);
|
||
log.AppendLine("图表已保存 drawtest.png, 系列数=" + chart.Series.Count + ", 图例启用=" + chart.Legends["legend"].Enabled);
|
||
log.AppendLine("DRAWTEST PASS");
|
||
File.WriteAllText(Path.Combine(Path.GetDirectoryName(Application.ExecutablePath), "selftest_result.txt"), log.ToString(), new UTF8Encoding(false));
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
try
|
||
{
|
||
string outFile = Path.Combine(Path.GetDirectoryName(Application.ExecutablePath), "selftest_result.txt");
|
||
File.WriteAllText(outFile, "DRAWTEST FAIL: " + ex.Message + "\n" + ex.StackTrace, new UTF8Encoding(false));
|
||
}
|
||
catch { }
|
||
}
|
||
Application.Exit();
|
||
}
|
||
|
||
void UpdateCalcParamUI()
|
||
{
|
||
string sel = cmbCalc.SelectedItem as string;
|
||
if (sel == "平滑曲线 (滑动平均)")
|
||
{
|
||
lblCalcParam.Text = "窗口:";
|
||
txtCalcParam.Enabled = true;
|
||
txtCalcParam.Text = "5";
|
||
}
|
||
else if (sel == "多项式拟合 (Polyfit) (AI)")
|
||
{
|
||
lblCalcParam.Text = "阶数:";
|
||
txtCalcParam.Enabled = true;
|
||
txtCalcParam.Text = "3";
|
||
}
|
||
else if (sel == "梯度下降 (AI)")
|
||
{
|
||
lblCalcParam.Text = "迭代:";
|
||
txtCalcParam.Enabled = true;
|
||
txtCalcParam.Text = "200";
|
||
}
|
||
else if (sel == "贝叶斯回归 (AI)")
|
||
{
|
||
lblCalcParam.Text = "α:";
|
||
txtCalcParam.Enabled = true;
|
||
txtCalcParam.Text = "1";
|
||
}
|
||
else if (sel == "线性变换 (a*y+b) (AI)" || sel == "二次变换 (a*y²+b) (AI)")
|
||
{
|
||
lblCalcParam.Text = "a,b:";
|
||
txtCalcParam.Enabled = true;
|
||
txtCalcParam.Text = "1,0";
|
||
}
|
||
else
|
||
{
|
||
lblCalcParam.Text = "无参数";
|
||
txtCalcParam.Enabled = false;
|
||
}
|
||
cmbCalcTarget.Enabled = (sel == "线性变换 (a*y+b) (AI)" || sel == "二次变换 (a*y²+b) (AI)");
|
||
}
|
||
|
||
void ToggleCross()
|
||
{
|
||
showCross = !showCross;
|
||
btnCross.Text = showCross ? "隐藏十字线" : "显示十字线";
|
||
btnCross.BackColor = showCross ? Color.FromArgb(52, 73, 94) : Color.FromArgb(127, 140, 141);
|
||
if (!showCross) crossPos = new PointF(-1, -1);
|
||
chart.Invalidate();
|
||
}
|
||
|
||
void ToggleLegend()
|
||
{
|
||
legendHiddenByUser = !legendHiddenByUser;
|
||
legendBar.Visible = chart.Series.Count > 0 && !legendHiddenByUser;
|
||
btnLegend.Text = legendBar.Visible ? "隐藏图例" : "显示图例";
|
||
btnLegend.BackColor = legendBar.Visible ? Color.FromArgb(52, 73, 94) : Color.FromArgb(127, 140, 141);
|
||
}
|
||
|
||
void OpenChartSettings()
|
||
{
|
||
ChartSettingsForm f = new ChartSettingsForm(chart, lblChartTitle, this);
|
||
f.ShowDialog(this);
|
||
}
|
||
|
||
void ApplyCalc()
|
||
{
|
||
if (lastRows == null || lastRows.Count == 0)
|
||
{
|
||
MessageBox.Show(this, "请先绘制曲线", "提示");
|
||
return;
|
||
}
|
||
int param = 5;
|
||
int.TryParse(txtCalcParam.Text.Trim(), out param);
|
||
string calcName = cmbCalc.SelectedItem as string;
|
||
bool singleCurve = (calcName == "线性变换 (a*y+b) (AI)" || calcName == "二次变换 (a*y²+b) (AI)");
|
||
double a = 1, b = 0;
|
||
string target = "";
|
||
if (singleCurve)
|
||
{
|
||
target = cmbCalcTarget.SelectedItem as string;
|
||
if (string.IsNullOrEmpty(target))
|
||
{
|
||
MessageBox.Show(this, "请先在\"曲线\"下拉框选择目标曲线", "提示");
|
||
return;
|
||
}
|
||
string[] ab = txtCalcParam.Text.Replace(",", ",").Split(',');
|
||
if (ab.Length >= 2)
|
||
{
|
||
double.TryParse(ab[0].Trim(), System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out a);
|
||
double.TryParse(ab[1].Trim(), System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out b);
|
||
}
|
||
}
|
||
if (calcName == "平滑曲线 (滑动平均)")
|
||
{
|
||
if (param < 3) param = 3;
|
||
if (param > 101) param = 101;
|
||
}
|
||
else if (calcName == "多项式拟合 (Polyfit) (AI)")
|
||
{
|
||
if (param < 1) param = 1;
|
||
if (param > 10) param = 10;
|
||
}
|
||
else if (calcName == "梯度下降 (AI)")
|
||
{
|
||
if (param < 1) param = 1;
|
||
if (param > 5000) param = 5000;
|
||
}
|
||
else if (calcName == "贝叶斯回归 (AI)")
|
||
{
|
||
if (param < 1) param = 1;
|
||
if (param > 1000) param = 1000;
|
||
}
|
||
else
|
||
{
|
||
param = 1;
|
||
}
|
||
XAxisInfo xa = AnalyzeXAxis(lastRows);
|
||
bool xNumeric = xa.Numeric;
|
||
bool xSparseTime = xa.Sparse;
|
||
foreach (string y in lastYcols)
|
||
{
|
||
if (singleCurve && y != target) continue;
|
||
double[] xs = new double[lastRows.Count];
|
||
double[] ys = new double[lastRows.Count];
|
||
int[] srcIdx = new int[lastRows.Count];
|
||
int n = 0;
|
||
for (int i = 0; i < lastRows.Count; i++)
|
||
{
|
||
object[] row = lastRows[i];
|
||
double xv = n;
|
||
double yv;
|
||
if (!TryToDouble(row[1 + lastYcols.IndexOf(y)], out yv)) continue;
|
||
if (xNumeric && !TryToDouble(row[0], out xv))
|
||
{
|
||
string sx = row[0] as string;
|
||
if (sx == null) continue;
|
||
DateTime dt;
|
||
if (!TryParseTime(sx, out dt)) continue;
|
||
xv = dt.ToOADate();
|
||
}
|
||
xs[n] = xv;
|
||
ys[n] = yv;
|
||
srcIdx[n] = i;
|
||
n++;
|
||
}
|
||
double[] nx = new double[n];
|
||
double[] ny = new double[n];
|
||
Array.Copy(xs, nx, n);
|
||
Array.Copy(ys, ny, n);
|
||
if (n == 0) continue;
|
||
string seriesName = "";
|
||
double[] outY = ComputeCalc(calcName, nx, ny, param, a, b, out seriesName);
|
||
if (outY == null) continue;
|
||
Series s = new Series(seriesName + "(" + y + ")");
|
||
s.ChartType = SeriesChartType.Line;
|
||
s.BorderWidth = 2;
|
||
s.Color = NextColor();
|
||
for (int i = 0; i < n; i++)
|
||
{
|
||
if (double.IsNaN(outY[i])) continue;
|
||
if (xSparseTime || !xNumeric)
|
||
s.Points.AddXY(lastRows[srcIdx[i]][0] == null ? "" : lastRows[srcIdx[i]][0].ToString(), outY[i]);
|
||
else s.Points.AddXY(nx[i], outY[i]);
|
||
}
|
||
chart.Series.Add(s);
|
||
calcSeriesNames.Add(s.Name);
|
||
}
|
||
if (calcSeriesNames.Count > 0)
|
||
SetStatus("已生成运算曲线: " + calcName, false);
|
||
}
|
||
|
||
double[] ComputeCalc(string calcName, double[] xs, double[] ys, int param, double a, double b, out string seriesName)
|
||
{
|
||
int n = ys.Length;
|
||
double[] outY = new double[n];
|
||
for (int i = 0; i < n; i++) outY[i] = double.NaN;
|
||
if (calcName == "对数 (ln)")
|
||
{
|
||
seriesName = "ln";
|
||
for (int i = 0; i < n; i++)
|
||
if (ys[i] > 0) outY[i] = Math.Log(ys[i]);
|
||
}
|
||
else if (calcName == "常用对数 (log10)")
|
||
{
|
||
seriesName = "log10";
|
||
for (int i = 0; i < n; i++)
|
||
if (ys[i] > 0) outY[i] = Math.Log10(ys[i]);
|
||
}
|
||
else if (calcName == "指数 (exp)")
|
||
{
|
||
seriesName = "exp";
|
||
for (int i = 0; i < n; i++)
|
||
outY[i] = Math.Exp(ys[i]);
|
||
}
|
||
else if (calcName == "开方 (sqrt)")
|
||
{
|
||
seriesName = "sqrt";
|
||
for (int i = 0; i < n; i++)
|
||
if (ys[i] >= 0) outY[i] = Math.Sqrt(ys[i]);
|
||
}
|
||
else if (calcName == "微分 (差分)")
|
||
{
|
||
seriesName = "dy/dx";
|
||
for (int i = 1; i < n; i++)
|
||
{
|
||
double dx = xs[i] - xs[i - 1];
|
||
if (dx != 0) outY[i - 1] = (ys[i] - ys[i - 1]) / dx;
|
||
}
|
||
}
|
||
else if (calcName == "积分 (累积)")
|
||
{
|
||
seriesName = "∫ydx";
|
||
double sum = 0;
|
||
for (int i = 1; i < n; i++)
|
||
{
|
||
double dx = xs[i] - xs[i - 1];
|
||
sum += (ys[i] + ys[i - 1]) / 2.0 * dx;
|
||
outY[i] = sum;
|
||
}
|
||
outY[0] = 0;
|
||
}
|
||
else if (calcName == "线性回归 (AI)")
|
||
{
|
||
seriesName = "reg";
|
||
double sx = 0, sy = 0, sxx = 0, sxy = 0;
|
||
for (int i = 0; i < n; i++)
|
||
{
|
||
sx += xs[i]; sy += ys[i]; sxx += xs[i] * xs[i]; sxy += xs[i] * ys[i];
|
||
}
|
||
double denom = n * sxx - sx * sx;
|
||
double coefA = 0, coefB = 0;
|
||
if (Math.Abs(denom) > 1e-12)
|
||
{
|
||
coefA = (n * sxy - sx * sy) / denom;
|
||
coefB = (sy - coefA * sx) / n;
|
||
}
|
||
for (int i = 0; i < n; i++)
|
||
outY[i] = coefA * xs[i] + coefB;
|
||
}
|
||
else if (calcName == "梯度下降 (AI)")
|
||
{
|
||
seriesName = "gd";
|
||
int iters = Math.Max(1, param);
|
||
double mx = 0, my = 0;
|
||
for (int i = 0; i < n; i++) { mx += xs[i]; my += ys[i]; }
|
||
mx /= n; my /= n;
|
||
double xstd = 0, ystd = 0;
|
||
for (int i = 0; i < n; i++)
|
||
{
|
||
xstd += (xs[i] - mx) * (xs[i] - mx);
|
||
ystd += (ys[i] - my) * (ys[i] - my);
|
||
}
|
||
xstd = Math.Sqrt(xstd / n);
|
||
ystd = Math.Sqrt(ystd / n);
|
||
if (xstd < 1e-12) xstd = 1e-12;
|
||
if (ystd < 1e-12) ystd = 1e-12;
|
||
double w = 0, bias = 0;
|
||
double lr = 0.1;
|
||
for (int it = 0; it < iters; it++)
|
||
{
|
||
double gw = 0, gb = 0;
|
||
for (int i = 0; i < n; i++)
|
||
{
|
||
double xn = (xs[i] - mx) / xstd;
|
||
double yn = (ys[i] - my) / ystd;
|
||
double pred = w * xn + bias;
|
||
gw += (pred - yn) * xn;
|
||
gb += (pred - yn);
|
||
}
|
||
gw = 2.0 * gw / n;
|
||
gb = 2.0 * gb / n;
|
||
w -= lr * gw;
|
||
bias -= lr * gb;
|
||
}
|
||
for (int i = 0; i < n; i++)
|
||
{
|
||
double xn = (xs[i] - mx) / xstd;
|
||
outY[i] = (w * xn + bias) * ystd + my;
|
||
}
|
||
}
|
||
else if (calcName == "贝叶斯回归 (AI)")
|
||
{
|
||
seriesName = "bayes";
|
||
double alpha = Math.Max(1e-6, param);
|
||
double a00 = alpha + n, a01 = 0, a11 = alpha, b0 = 0, b1 = 0;
|
||
for (int i = 0; i < n; i++)
|
||
{
|
||
a01 += xs[i];
|
||
a11 += xs[i] * xs[i];
|
||
b0 += ys[i];
|
||
b1 += xs[i] * ys[i];
|
||
}
|
||
double det = a00 * a11 - a01 * a01;
|
||
double w0 = 0, w1 = 0;
|
||
if (Math.Abs(det) > 1e-15)
|
||
{
|
||
w0 = (b0 * a11 - a01 * b1) / det;
|
||
w1 = (a00 * b1 - a01 * b0) / det;
|
||
}
|
||
for (int i = 0; i < n; i++)
|
||
outY[i] = w0 + w1 * xs[i];
|
||
}
|
||
else if (calcName == "多项式拟合 (Polyfit) (AI)")
|
||
{
|
||
int degree = param;
|
||
if (degree < 1) degree = 1;
|
||
if (degree > 10) degree = 10;
|
||
seriesName = "poly" + degree;
|
||
double[] coeff = PolyFit(xs, ys, n, degree);
|
||
for (int i = 0; i < n; i++)
|
||
{
|
||
double v = 0;
|
||
for (int k = 0; k <= degree; k++)
|
||
v += coeff[k] * Math.Pow(xs[i], k);
|
||
outY[i] = v;
|
||
}
|
||
}
|
||
else if (calcName == "平滑曲线 (滑动平均)")
|
||
{
|
||
seriesName = "smooth";
|
||
for (int i = 0; i < n; i++)
|
||
{
|
||
int half = (param - 1) / 2;
|
||
int start = Math.Max(0, i - half);
|
||
int end = Math.Min(n - 1, i + half);
|
||
double sum = 0;
|
||
int cnt = 0;
|
||
for (int k = start; k <= end; k++)
|
||
{
|
||
if (!double.IsNaN(ys[k])) { sum += ys[k]; cnt++; }
|
||
}
|
||
if (cnt > 0) outY[i] = sum / cnt;
|
||
}
|
||
}
|
||
else if (calcName == "线性变换 (a*y+b) (AI)")
|
||
{
|
||
seriesName = "a*y+b";
|
||
for (int i = 0; i < n; i++)
|
||
if (!double.IsNaN(ys[i])) outY[i] = a * ys[i] + b;
|
||
}
|
||
else if (calcName == "二次变换 (a*y²+b) (AI)")
|
||
{
|
||
seriesName = "a*y²+b";
|
||
for (int i = 0; i < n; i++)
|
||
if (!double.IsNaN(ys[i])) outY[i] = a * ys[i] * ys[i] + b;
|
||
}
|
||
else
|
||
{
|
||
seriesName = "";
|
||
return null;
|
||
}
|
||
return outY;
|
||
}
|
||
|
||
double[] PolyFit(double[] xs, double[] ys, int n, int degree)
|
||
{
|
||
int m = degree + 1;
|
||
double[,] a = new double[m, m];
|
||
double[] bvec = new double[m];
|
||
for (int i = 0; i < n; i++)
|
||
{
|
||
double xp = 1;
|
||
for (int j = 0; j < m; j++)
|
||
{
|
||
for (int k = 0; k < m; k++)
|
||
a[j, k] += xp * Math.Pow(xs[i], k);
|
||
bvec[j] += xp * ys[i];
|
||
xp *= xs[i];
|
||
}
|
||
}
|
||
return GaussSolve(a, bvec, m);
|
||
}
|
||
|
||
double[] GaussSolve(double[,] a, double[] b, int m)
|
||
{
|
||
for (int col = 0; col < m; col++)
|
||
{
|
||
int pivot = col;
|
||
double maxv = Math.Abs(a[col, col]);
|
||
for (int r = col + 1; r < m; r++)
|
||
{
|
||
if (Math.Abs(a[r, col]) > maxv)
|
||
{
|
||
maxv = Math.Abs(a[r, col]);
|
||
pivot = r;
|
||
}
|
||
}
|
||
if (maxv < 1e-15)
|
||
{
|
||
double[] zero = new double[m];
|
||
return zero;
|
||
}
|
||
if (pivot != col)
|
||
{
|
||
for (int k = col; k < m; k++)
|
||
{
|
||
double t = a[col, k]; a[col, k] = a[pivot, k]; a[pivot, k] = t;
|
||
}
|
||
double tb = b[col]; b[col] = b[pivot]; b[pivot] = tb;
|
||
}
|
||
double diag = a[col, col];
|
||
for (int k = col; k < m; k++) a[col, k] /= diag;
|
||
b[col] /= diag;
|
||
for (int r = 0; r < m; r++)
|
||
{
|
||
if (r == col) continue;
|
||
double f = a[r, col];
|
||
if (Math.Abs(f) < 1e-15) continue;
|
||
for (int k = col; k < m; k++) a[r, k] -= f * a[col, k];
|
||
b[r] -= f * b[col];
|
||
}
|
||
}
|
||
return b;
|
||
}
|
||
|
||
void ClearCalcSeries()
|
||
{
|
||
foreach (string name in calcSeriesNames)
|
||
{
|
||
for (int i = chart.Series.Count - 1; i >= 0; i--)
|
||
{
|
||
if (chart.Series[i].Name == name)
|
||
{
|
||
chart.Series.RemoveAt(i);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
calcSeriesNames.Clear();
|
||
SetStatus("已清除运算曲线", false);
|
||
}
|
||
|
||
static readonly string[] TimeFormats = {
|
||
"yyyy-MM-dd HH:mm:ss.fff", "yyyy/MM/dd HH:mm:ss.fff",
|
||
"yyyy-MM-dd HH:mm:ss", "yyyy/MM/dd HH:mm:ss",
|
||
"yyyy-M-d H:m:s", "yyyy/M/d H:m:s",
|
||
"yyyy-MM-dd HH:mm", "yyyy/MM/dd HH:mm",
|
||
"yy-MM-dd HH:mm:ss", "yy/MM/dd HH:mm:ss",
|
||
"yy-M-d H:m:s", "yy/M/d H:m:s",
|
||
"yy-MM-dd HH:mm", "yy/MM/dd HH:mm",
|
||
"yyyy-MM-dd", "yyyy/MM/dd", "yy-MM-dd", "yy/MM/dd",
|
||
"yyyyMMddHHmmss", "yyMMddHHmmss", "yyyyMMddHHmm", "yyMMddHHmm",
|
||
"yyyyMMdd", "yyMMdd"
|
||
};
|
||
|
||
static bool TryParseTime(string s, out DateTime dt)
|
||
{
|
||
dt = DateTime.MinValue;
|
||
string t = s.Trim();
|
||
if (t.Length < 6) return false;
|
||
return DateTime.TryParseExact(t, TimeFormats,
|
||
System.Globalization.CultureInfo.InvariantCulture,
|
||
System.Globalization.DateTimeStyles.None, out dt);
|
||
}
|
||
|
||
static bool IsNumeric(object o)
|
||
{
|
||
double d;
|
||
return TryToDouble(o, out d);
|
||
}
|
||
|
||
static bool TryToDouble(object o, out double val)
|
||
{
|
||
val = 0;
|
||
if (o is double) { val = (double)o; return true; }
|
||
if (o is float) { val = (float)o; return true; }
|
||
if (o is int) { val = (int)o; return true; }
|
||
if (o is long) { val = (long)o; return true; }
|
||
if (o is decimal) { val = (double)(decimal)o; return true; }
|
||
if (o is sbyte) { val = (sbyte)o; return true; }
|
||
if (o is byte) { val = (byte)o; return true; }
|
||
if (o is short) { val = (short)o; return true; }
|
||
if (o is ushort) { val = (ushort)o; return true; }
|
||
if (o is uint) { val = (uint)o; return true; }
|
||
if (o is ulong) { val = (ulong)o; return true; }
|
||
if (o is DateTime) { val = ((DateTime)o).ToOADate(); return true; }
|
||
if (o is TimeSpan) { val = ((TimeSpan)o).TotalSeconds; return true; }
|
||
return double.TryParse(o.ToString(), System.Globalization.NumberStyles.Any,
|
||
System.Globalization.CultureInfo.InvariantCulture, out val);
|
||
}
|
||
|
||
void SaveImage()
|
||
{
|
||
if (chart.Series.Count == 0)
|
||
{
|
||
MessageBox.Show(this, "请先绘制曲线", "提示");
|
||
return;
|
||
}
|
||
SaveFileDialog dlg = new SaveFileDialog();
|
||
dlg.Filter = "PNG 图片|*.png|JPEG 图片|*.jpg|BMP 图片|*.bmp";
|
||
dlg.FileName = "chart.png";
|
||
if (dlg.ShowDialog(this) == DialogResult.OK)
|
||
{
|
||
try
|
||
{
|
||
chart.SaveImage(dlg.FileName, ChartImageFormat.Png);
|
||
MessageBox.Show(this, "图片已保存到:\n" + dlg.FileName, "完成");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
MessageBox.Show(this, "保存失败: " + ex.Message, "错误",
|
||
MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
class ChartSettingsForm : Form
|
||
{
|
||
Chart chart;
|
||
Label lblTitleTarget;
|
||
MainForm owner;
|
||
|
||
TextBox txtTitle, txtXTitle, txtYTitle;
|
||
TextBox txtXMin, txtXMax, txtYMin, txtYMax;
|
||
TextBox txtXGrid, txtYGrid, txtMinorDiv;
|
||
CheckBox chkXLog, chkYLog, chkMinor, chkGridX, chkGridY;
|
||
DataGridView dgvSeries;
|
||
|
||
public ChartSettingsForm(Chart ch, Label lblTitle, MainForm ownerForm)
|
||
{
|
||
chart = ch;
|
||
lblTitleTarget = lblTitle;
|
||
owner = ownerForm;
|
||
Text = "图表设置";
|
||
FormBorderStyle = FormBorderStyle.FixedDialog;
|
||
MaximizeBox = false;
|
||
MinimizeBox = false;
|
||
StartPosition = FormStartPosition.CenterParent;
|
||
Font = new Font("Microsoft YaHei UI", 9F);
|
||
ClientSize = new Size(560, 480);
|
||
|
||
TabControl tabs = new TabControl();
|
||
tabs.Location = new Point(10, 10);
|
||
tabs.Size = new Size(540, 410);
|
||
Controls.Add(tabs);
|
||
|
||
TabPage pgGeneral = new TabPage("常规");
|
||
tabs.TabPages.Add(pgGeneral);
|
||
TabPage pgAxis = new TabPage("坐标轴");
|
||
tabs.TabPages.Add(pgAxis);
|
||
TabPage pgSeries = new TabPage("曲线样式");
|
||
tabs.TabPages.Add(pgSeries);
|
||
|
||
ChartArea area = chart.ChartAreas["main"];
|
||
|
||
AddRow(pgGeneral, "标题:", 16, lblTitle.Text, out txtTitle);
|
||
AddRow(pgGeneral, "横轴名称:", 52, area.AxisX.Title, out txtXTitle);
|
||
AddRow(pgGeneral, "纵轴名称:", 88, area.AxisY.Title, out txtYTitle);
|
||
|
||
AddLabelTo(pgGeneral, "图例位置:", 16, 128);
|
||
Label lLegendFixed = new Label();
|
||
lLegendFixed.Text = "固定于标题下方 (不可调)";
|
||
lLegendFixed.Location = new Point(110, 124);
|
||
lLegendFixed.AutoSize = true;
|
||
lLegendFixed.ForeColor = Color.FromArgb(140, 150, 160);
|
||
pgGeneral.Controls.Add(lLegendFixed);
|
||
|
||
AddLabelTo(pgGeneral, "横轴网格:", 16, 164);
|
||
chkGridX = new CheckBox();
|
||
chkGridX.Text = "显示";
|
||
chkGridX.Location = new Point(110, 160);
|
||
chkGridX.Checked = area.AxisX.MajorGrid.Enabled;
|
||
pgGeneral.Controls.Add(chkGridX);
|
||
|
||
AddLabelTo(pgGeneral, "纵轴网格:", 16, 196);
|
||
chkGridY = new CheckBox();
|
||
chkGridY.Text = "显示";
|
||
chkGridY.Location = new Point(110, 192);
|
||
chkGridY.Checked = area.AxisY.MajorGrid.Enabled;
|
||
pgGeneral.Controls.Add(chkGridY);
|
||
|
||
AddLabelTo(pgGeneral, "横轴网格间距:", 16, 232);
|
||
txtXGrid = AddSmallText(pgGeneral, 110, 228, 220);
|
||
txtXGrid.Text = area.AxisX.MajorGrid.Interval == 0 ? "" : area.AxisX.MajorGrid.Interval.ToString();
|
||
|
||
AddLabelTo(pgGeneral, "纵轴网格间距:", 16, 268);
|
||
txtYGrid = AddSmallText(pgGeneral, 110, 264, 220);
|
||
txtYGrid.Text = area.AxisY.MajorGrid.Interval == 0 ? "" : area.AxisY.MajorGrid.Interval.ToString();
|
||
|
||
AddLabelTo(pgGeneral, "次网格细分数:", 16, 304);
|
||
txtMinorDiv = AddSmallText(pgGeneral, 110, 300, 220);
|
||
double minorDiv = 4;
|
||
if (area.AxisX.MinorGrid.Enabled && area.AxisX.MajorGrid.Interval > 0)
|
||
minorDiv = area.AxisX.MajorGrid.Interval / area.AxisX.MinorGrid.Interval;
|
||
txtMinorDiv.Text = minorDiv.ToString("0.#");
|
||
|
||
chkMinor = new CheckBox();
|
||
chkMinor.Text = "加密网格线 (启用次网格线)";
|
||
chkMinor.Location = new Point(16, 334);
|
||
chkMinor.AutoSize = true;
|
||
chkMinor.Checked = area.AxisX.MinorGrid.Enabled;
|
||
pgGeneral.Controls.Add(chkMinor);
|
||
|
||
AddLabelTo(pgAxis, "横轴最小值 (留空=自动):", 16, 24);
|
||
txtXMin = AddSmallText(pgAxis, 190, 20, 150);
|
||
if (!double.IsNaN(area.AxisX.Minimum) && area.AxisX.Minimum != double.NegativeInfinity)
|
||
txtXMin.Text = area.AxisX.Minimum.ToString();
|
||
|
||
AddLabelTo(pgAxis, "横轴最大值 (留空=自动):", 16, 60);
|
||
txtXMax = AddSmallText(pgAxis, 190, 56, 150);
|
||
if (!double.IsNaN(area.AxisX.Maximum) && area.AxisX.Maximum != double.PositiveInfinity)
|
||
txtXMax.Text = area.AxisX.Maximum.ToString();
|
||
|
||
AddLabelTo(pgAxis, "纵轴最小值 (留空=自动):", 16, 96);
|
||
txtYMin = AddSmallText(pgAxis, 190, 92, 150);
|
||
if (!double.IsNaN(area.AxisY.Minimum) && area.AxisY.Minimum != double.NegativeInfinity)
|
||
txtYMin.Text = area.AxisY.Minimum.ToString();
|
||
|
||
AddLabelTo(pgAxis, "纵轴最大值 (留空=自动):", 16, 132);
|
||
txtYMax = AddSmallText(pgAxis, 190, 128, 150);
|
||
if (!double.IsNaN(area.AxisY.Maximum) && area.AxisY.Maximum != double.PositiveInfinity)
|
||
txtYMax.Text = area.AxisY.Maximum.ToString();
|
||
|
||
chkXLog = new CheckBox();
|
||
chkXLog.Text = "横轴使用对数刻度 (Log)";
|
||
chkXLog.Location = new Point(16, 176);
|
||
chkXLog.AutoSize = true;
|
||
chkXLog.Checked = area.AxisX.IsLogarithmic;
|
||
pgAxis.Controls.Add(chkXLog);
|
||
|
||
chkYLog = new CheckBox();
|
||
chkYLog.Text = "纵轴使用对数刻度 (Log)";
|
||
chkYLog.Location = new Point(16, 208);
|
||
chkYLog.AutoSize = true;
|
||
chkYLog.Checked = area.AxisY.IsLogarithmic;
|
||
pgAxis.Controls.Add(chkYLog);
|
||
|
||
Label lTip = new Label();
|
||
lTip.Text = "提示: 对数刻度要求数据均为正值";
|
||
lTip.Font = new Font("Microsoft YaHei UI", 8.5F);
|
||
lTip.ForeColor = Color.FromArgb(140, 150, 160);
|
||
lTip.Location = new Point(16, 240);
|
||
lTip.AutoSize = true;
|
||
pgAxis.Controls.Add(lTip);
|
||
|
||
dgvSeries = new DataGridView();
|
||
dgvSeries.Dock = DockStyle.Fill;
|
||
dgvSeries.Padding = new Padding(6);
|
||
dgvSeries.AllowUserToAddRows = false;
|
||
dgvSeries.AllowUserToDeleteRows = false;
|
||
dgvSeries.RowHeadersVisible = false;
|
||
dgvSeries.BackgroundColor = Color.White;
|
||
dgvSeries.BorderStyle = BorderStyle.FixedSingle;
|
||
dgvSeries.EnableHeadersVisualStyles = false;
|
||
dgvSeries.ColumnHeadersDefaultCellStyle.BackColor = Color.FromArgb(52, 73, 94);
|
||
dgvSeries.ColumnHeadersDefaultCellStyle.ForeColor = Color.White;
|
||
dgvSeries.ColumnHeadersDefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleCenter;
|
||
dgvSeries.ColumnHeadersHeight = 26;
|
||
dgvSeries.DefaultCellStyle.Font = new Font("Microsoft YaHei UI", 8.5F);
|
||
dgvSeries.GridColor = Color.FromArgb(221, 228, 236);
|
||
dgvSeries.RowTemplate.Height = 26;
|
||
pgSeries.Controls.Add(dgvSeries);
|
||
|
||
DataGridViewTextBoxColumn cName = new DataGridViewTextBoxColumn();
|
||
cName.HeaderText = "曲线名称";
|
||
cName.Width = 130;
|
||
cName.ReadOnly = true;
|
||
dgvSeries.Columns.Add(cName);
|
||
|
||
DataGridViewComboBoxColumn cColor = new DataGridViewComboBoxColumn();
|
||
cColor.HeaderText = "颜色";
|
||
cColor.Width = 90;
|
||
cColor.FlatStyle = FlatStyle.Flat;
|
||
foreach (KnownColor kc in Enum.GetValues(typeof(KnownColor)))
|
||
{
|
||
Color cc = Color.FromKnownColor(kc);
|
||
if (cc.IsSystemColor || cc.A != 255) continue;
|
||
cColor.Items.Add(kc.ToString());
|
||
}
|
||
dgvSeries.Columns.Add(cColor);
|
||
|
||
DataGridViewComboBoxColumn cWidth = new DataGridViewComboBoxColumn();
|
||
cWidth.HeaderText = "线宽";
|
||
cWidth.Width = 60;
|
||
cWidth.FlatStyle = FlatStyle.Flat;
|
||
for (int i = 1; i <= 8; i++) cWidth.Items.Add(i.ToString());
|
||
dgvSeries.Columns.Add(cWidth);
|
||
|
||
DataGridViewComboBoxColumn cStyle = new DataGridViewComboBoxColumn();
|
||
cStyle.HeaderText = "线型";
|
||
cStyle.Width = 80;
|
||
cStyle.FlatStyle = FlatStyle.Flat;
|
||
cStyle.Items.Add("实线");
|
||
cStyle.Items.Add("虚线");
|
||
cStyle.Items.Add("点线");
|
||
cStyle.Items.Add("点划线");
|
||
dgvSeries.Columns.Add(cStyle);
|
||
|
||
DataGridViewComboBoxColumn cMarker = new DataGridViewComboBoxColumn();
|
||
cMarker.HeaderText = "标记";
|
||
cMarker.Width = 80;
|
||
cMarker.FlatStyle = FlatStyle.Flat;
|
||
cMarker.Items.Add("无");
|
||
cMarker.Items.Add("圆点");
|
||
cMarker.Items.Add("方块");
|
||
cMarker.Items.Add("三角");
|
||
cMarker.Items.Add("菱形");
|
||
dgvSeries.Columns.Add(cMarker);
|
||
|
||
RefreshSeriesGrid();
|
||
|
||
Button ok = new Button();
|
||
ok.Text = "应用";
|
||
ok.Location = new Point(250, 432);
|
||
ok.Size = new Size(120, 34);
|
||
ok.FlatStyle = FlatStyle.Flat;
|
||
ok.FlatAppearance.BorderSize = 0;
|
||
ok.BackColor = Color.FromArgb(41, 101, 173);
|
||
ok.ForeColor = Color.White;
|
||
ok.Font = new Font("Microsoft YaHei UI", 10F, FontStyle.Bold);
|
||
ok.Click += delegate { ApplyAndClose(); };
|
||
Controls.Add(ok);
|
||
|
||
Button cancel = new Button();
|
||
cancel.Text = "取消";
|
||
cancel.Location = new Point(385, 432);
|
||
cancel.Size = new Size(120, 34);
|
||
cancel.DialogResult = DialogResult.Cancel;
|
||
Controls.Add(cancel);
|
||
AcceptButton = ok;
|
||
CancelButton = cancel;
|
||
}
|
||
|
||
void RefreshSeriesGrid()
|
||
{
|
||
dgvSeries.Rows.Clear();
|
||
foreach (Series s in chart.Series)
|
||
{
|
||
int idx = dgvSeries.Rows.Add();
|
||
dgvSeries.Rows[idx].Cells[0].Value = s.Name;
|
||
dgvSeries.Rows[idx].Cells[1].Value = s.Color.Name;
|
||
dgvSeries.Rows[idx].Cells[2].Value = s.BorderWidth.ToString();
|
||
dgvSeries.Rows[idx].Cells[3].Value = StyleToCn(s.BorderDashStyle);
|
||
dgvSeries.Rows[idx].Cells[4].Value = MarkerToCn(s.MarkerStyle);
|
||
}
|
||
}
|
||
|
||
static string StyleToCn(ChartDashStyle st)
|
||
{
|
||
switch (st)
|
||
{
|
||
case ChartDashStyle.Dash: return "虚线";
|
||
case ChartDashStyle.Dot: return "点线";
|
||
case ChartDashStyle.DashDot: return "点划线";
|
||
default: return "实线";
|
||
}
|
||
}
|
||
|
||
static string MarkerToCn(MarkerStyle m)
|
||
{
|
||
switch (m)
|
||
{
|
||
case MarkerStyle.Circle: return "圆点";
|
||
case MarkerStyle.Square: return "方块";
|
||
case MarkerStyle.Triangle: return "三角";
|
||
case MarkerStyle.Diamond: return "菱形";
|
||
default: return "无";
|
||
}
|
||
}
|
||
|
||
static ChartDashStyle CnToStyle(string s)
|
||
{
|
||
if (s == "虚线") return ChartDashStyle.Dash;
|
||
if (s == "点线") return ChartDashStyle.Dot;
|
||
if (s == "点划线") return ChartDashStyle.DashDot;
|
||
return ChartDashStyle.Solid;
|
||
}
|
||
|
||
static MarkerStyle CnToMarker(string s)
|
||
{
|
||
if (s == "圆点") return MarkerStyle.Circle;
|
||
if (s == "方块") return MarkerStyle.Square;
|
||
if (s == "三角") return MarkerStyle.Triangle;
|
||
if (s == "菱形") return MarkerStyle.Diamond;
|
||
return MarkerStyle.None;
|
||
}
|
||
|
||
void AddLabelTo(Control parent, string text, int x, int y)
|
||
{
|
||
Label l = new Label();
|
||
l.Text = text;
|
||
l.Location = new Point(x, y + 4);
|
||
l.AutoSize = true;
|
||
l.ForeColor = Color.FromArgb(60, 70, 80);
|
||
parent.Controls.Add(l);
|
||
}
|
||
|
||
void AddRow(Control parent, string label, int y, string value, out TextBox box)
|
||
{
|
||
AddLabelTo(parent, label, 16, y);
|
||
box = new TextBox();
|
||
box.Location = new Point(110, y);
|
||
box.Width = 220;
|
||
box.BorderStyle = BorderStyle.FixedSingle;
|
||
box.Text = value;
|
||
parent.Controls.Add(box);
|
||
}
|
||
|
||
TextBox AddSmallText(Control parent, int x, int y, int width)
|
||
{
|
||
TextBox t = new TextBox();
|
||
t.Location = new Point(x, y);
|
||
t.Width = width;
|
||
t.BorderStyle = BorderStyle.FixedSingle;
|
||
parent.Controls.Add(t);
|
||
return t;
|
||
}
|
||
|
||
void ApplyAndClose()
|
||
{
|
||
ChartArea area = chart.ChartAreas["main"];
|
||
lblTitleTarget.Text = txtTitle.Text.Trim();
|
||
area.AxisX.Title = txtXTitle.Text.Trim();
|
||
area.AxisY.Title = txtYTitle.Text.Trim();
|
||
|
||
double xGrid = 0, yGrid = 0;
|
||
if (double.TryParse(txtXGrid.Text.Trim(), out xGrid) && xGrid > 0)
|
||
area.AxisX.MajorGrid.Interval = xGrid;
|
||
else
|
||
area.AxisX.MajorGrid.Interval = 0;
|
||
if (double.TryParse(txtYGrid.Text.Trim(), out yGrid) && yGrid > 0)
|
||
area.AxisY.MajorGrid.Interval = yGrid;
|
||
else
|
||
area.AxisY.MajorGrid.Interval = 0;
|
||
|
||
area.AxisX.MajorGrid.Enabled = chkGridX.Checked;
|
||
area.AxisY.MajorGrid.Enabled = chkGridY.Checked;
|
||
|
||
double xMin, xMax, yMin, yMax;
|
||
if (double.TryParse(txtXMin.Text.Trim(), out xMin))
|
||
area.AxisX.Minimum = xMin;
|
||
else
|
||
area.AxisX.Minimum = double.NaN;
|
||
if (double.TryParse(txtXMax.Text.Trim(), out xMax))
|
||
area.AxisX.Maximum = xMax;
|
||
else
|
||
area.AxisX.Maximum = double.NaN;
|
||
if (double.TryParse(txtYMin.Text.Trim(), out yMin))
|
||
area.AxisY.Minimum = yMin;
|
||
else
|
||
area.AxisY.Minimum = double.NaN;
|
||
if (double.TryParse(txtYMax.Text.Trim(), out yMax))
|
||
area.AxisY.Maximum = yMax;
|
||
else
|
||
area.AxisY.Maximum = double.NaN;
|
||
|
||
area.AxisX.IsLogarithmic = chkXLog.Checked;
|
||
area.AxisY.IsLogarithmic = chkYLog.Checked;
|
||
|
||
bool minor = chkMinor.Checked;
|
||
double div = 4;
|
||
double.TryParse(txtMinorDiv.Text.Trim(), out div);
|
||
if (div < 2) div = 2;
|
||
area.AxisX.MinorGrid.Enabled = minor;
|
||
area.AxisY.MinorGrid.Enabled = minor;
|
||
if (minor)
|
||
{
|
||
Color minorColor = Color.FromArgb(215, 220, 228);
|
||
if (xGrid > 0)
|
||
area.AxisX.MinorGrid.Interval = xGrid / div;
|
||
else
|
||
area.AxisX.MinorGrid.Interval = 0;
|
||
if (yGrid > 0)
|
||
area.AxisY.MinorGrid.Interval = yGrid / div;
|
||
else
|
||
area.AxisY.MinorGrid.Interval = 0;
|
||
area.AxisX.MinorGrid.LineColor = minorColor;
|
||
area.AxisY.MinorGrid.LineColor = minorColor;
|
||
area.AxisX.MinorGrid.LineDashStyle = ChartDashStyle.Dash;
|
||
area.AxisY.MinorGrid.LineDashStyle = ChartDashStyle.Dash;
|
||
}
|
||
|
||
foreach (DataGridViewRow row in dgvSeries.Rows)
|
||
{
|
||
if (row.IsNewRow) continue;
|
||
string sname = row.Cells[0].Value == null ? "" : row.Cells[0].Value.ToString();
|
||
if (chart.Series.IndexOf(sname) < 0) continue;
|
||
Series s = chart.Series[sname];
|
||
string colorName = row.Cells[1].Value == null ? "" : row.Cells[1].Value.ToString();
|
||
if (colorName.Length > 0)
|
||
{
|
||
try { s.Color = Color.FromName(colorName); }
|
||
catch { }
|
||
}
|
||
int bw = 2;
|
||
int.TryParse(row.Cells[2].Value == null ? "2" : row.Cells[2].Value.ToString(), out bw);
|
||
s.BorderWidth = bw;
|
||
string style = row.Cells[3].Value == null ? "" : row.Cells[3].Value.ToString();
|
||
if (style.Length > 0) s.BorderDashStyle = CnToStyle(style);
|
||
string marker = row.Cells[4].Value == null ? "" : row.Cells[4].Value.ToString();
|
||
if (marker.Length > 0)
|
||
{
|
||
s.MarkerStyle = CnToMarker(marker);
|
||
s.MarkerSize = 6;
|
||
s.MarkerColor = s.Color;
|
||
}
|
||
}
|
||
chart.Invalidate();
|
||
DialogResult = DialogResult.OK;
|
||
Close();
|
||
}
|
||
}
|
||
}
|