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();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|