UserCurve.cs 41 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148
  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel;
  4. using System.Drawing;
  5. using System.Data;
  6. using System.Linq;
  7. using System.Text;
  8. using System.Windows.Forms;
  9. /*******************************************************************************
  10. *
  11. * 文件名:UserCurve.cs
  12. * 程序功能:显示曲线信息,包含了曲线,坐标轴,鼠标移动信息
  13. *
  14. * 创建人:Richard.Hu
  15. * 时间:2018年1月21日 18:36:08
  16. *
  17. *******************************************************************************/
  18. namespace HslCommunication.Controls
  19. {
  20. /// <summary>
  21. /// 曲线控件对象
  22. /// </summary>
  23. /// <remarks>
  24. /// 详细参照如下的博客:
  25. /// </remarks>
  26. public partial class UserCurve : UserControl
  27. {
  28. #region Constructor
  29. /// <summary>
  30. /// 实例化一个曲线显示的控件
  31. /// </summary>
  32. public UserCurve( )
  33. {
  34. InitializeComponent( );
  35. DoubleBuffered = true;
  36. random = new Random( );
  37. data_list = new Dictionary<string, HslCurveItem>( );
  38. auxiliary_lines = new List<AuxiliaryLine>( );
  39. format_left = new StringFormat
  40. {
  41. LineAlignment = StringAlignment.Center,
  42. Alignment = StringAlignment.Near
  43. };
  44. format_right = new StringFormat
  45. {
  46. LineAlignment = StringAlignment.Center,
  47. Alignment = StringAlignment.Far,
  48. };
  49. format_center = new StringFormat
  50. {
  51. LineAlignment = StringAlignment.Center,
  52. Alignment = StringAlignment.Center,
  53. };
  54. font_size9 = new Font( "宋体", 9 );
  55. font_size12 = new Font( "宋体", 12 );
  56. InitializationColor( );
  57. pen_dash = new Pen( color_deep );
  58. pen_dash.DashStyle = System.Drawing.Drawing2D.DashStyle.Custom;
  59. pen_dash.DashPattern = new float[] { 5, 5 };
  60. }
  61. #endregion
  62. #region Const Data
  63. private const int value_count_max = 2048; // 缓存的数据的最大量
  64. #endregion
  65. #region Private Member
  66. private float value_max_left = 100; // 左坐标的最大值
  67. private float value_min_left = 0; // 左坐标的最小值
  68. private float value_max_right = 100; // 右坐标的最大值
  69. private float value_min_right = 0; // 右坐标的最小值
  70. private int value_Segment = 5; // 纵轴的片段分割
  71. private bool value_IsAbscissaStrech = false; // 指示横坐标是否填充满整个坐标系
  72. private int value_StrechDataCountMax = 300; // 拉伸模式下的最大数据量
  73. private bool value_IsRenderDashLine = true; // 是否显示虚线的信息
  74. private bool value_IsRenderAbscissaText = false; // 指示是否显示横轴的文本信息
  75. private string textFormat = "HH:mm"; // 时间文本的信息
  76. private int value_IntervalAbscissaText = 100; // 指示显示横轴文本的间隔数据
  77. private Random random = null; // 获取随机颜色使用
  78. private string value_title = ""; // 图表的标题
  79. private int leftRight = 50;
  80. private int upDowm = 25;
  81. #endregion
  82. #region Data Member
  83. Dictionary<string, HslCurveItem> data_list = null; // 等待显示的实际数据
  84. private string[] data_text = null; // 等待显示的横轴信息
  85. #endregion
  86. #region Public Properties
  87. /// <summary>
  88. /// 获取或设置图形的纵坐标的最大值,该值必须大于最小值
  89. /// </summary>
  90. [Category( "外观" )]
  91. [Description( "获取或设置图形的左纵坐标的最大值,该值必须大于最小值" )]
  92. [Browsable( true )]
  93. [DefaultValue( 100f )]
  94. public float ValueMaxLeft
  95. {
  96. get { return value_max_left; }
  97. set
  98. {
  99. if (value > value_min_left)
  100. {
  101. value_max_left = value;
  102. Invalidate( );
  103. }
  104. }
  105. }
  106. /// <summary>
  107. /// 获取或设置图形的纵坐标的最小值,该值必须小于最大值
  108. /// </summary>
  109. [Category( "外观" )]
  110. [Description( "获取或设置图形的左纵坐标的最小值,该值必须小于最大值" )]
  111. [Browsable( true )]
  112. [DefaultValue( 0f )]
  113. public float ValueMinLeft
  114. {
  115. get { return value_min_left; }
  116. set
  117. {
  118. if (value < value_max_left)
  119. {
  120. value_min_left = value;
  121. Invalidate( );
  122. }
  123. }
  124. }
  125. /// <summary>
  126. /// 获取或设置图形的纵坐标的最大值,该值必须大于最小值
  127. /// </summary>
  128. [Category( "外观" )]
  129. [Description( "获取或设置图形的右纵坐标的最大值,该值必须大于最小值" )]
  130. [Browsable( true )]
  131. [DefaultValue( 100f )]
  132. public float ValueMaxRight
  133. {
  134. get { return value_max_right; }
  135. set
  136. {
  137. if (value > value_min_right)
  138. {
  139. value_max_right = value;
  140. Invalidate( );
  141. }
  142. }
  143. }
  144. /// <summary>
  145. /// 获取或设置图形的纵坐标的最小值,该值必须小于最大值
  146. /// </summary>
  147. [Category( "外观" )]
  148. [Description( "获取或设置图形的右纵坐标的最小值,该值必须小于最大值" )]
  149. [Browsable( true )]
  150. [DefaultValue( 0f )]
  151. public float ValueMinRight
  152. {
  153. get { return value_min_right; }
  154. set
  155. {
  156. if (value < value_max_right)
  157. {
  158. value_min_right = value;
  159. Invalidate( );
  160. }
  161. }
  162. }
  163. /// <summary>
  164. /// 获取或设置图形的纵轴分段数
  165. /// </summary>
  166. [Category( "外观" )]
  167. [Description( "获取或设置图形的纵轴分段数" )]
  168. [Browsable( true )]
  169. [DefaultValue( 5 )]
  170. public int ValueSegment
  171. {
  172. get { return value_Segment; }
  173. set
  174. {
  175. value_Segment = value;
  176. Invalidate( );
  177. }
  178. }
  179. /// <summary>
  180. /// 获取或设置所有的数据是否强制在一个界面里显示
  181. /// </summary>
  182. [Category( "外观" )]
  183. [Description( "获取或设置所有的数据是否强制在一个界面里显示" )]
  184. [Browsable( true )]
  185. [DefaultValue( false )]
  186. public bool IsAbscissaStrech
  187. {
  188. get { return value_IsAbscissaStrech; }
  189. set
  190. {
  191. value_IsAbscissaStrech = value;
  192. Invalidate( );
  193. }
  194. }
  195. /// <summary>
  196. /// 获取或设置拉伸模式下的最大数据量
  197. /// </summary>
  198. [Category( "外观" )]
  199. [Description( "获取或设置拉伸模式下的最大数据量" )]
  200. [Browsable( true )]
  201. [DefaultValue( 300 )]
  202. public int StrechDataCountMax
  203. {
  204. get { return value_StrechDataCountMax; }
  205. set
  206. {
  207. value_StrechDataCountMax = value;
  208. Invalidate( );
  209. }
  210. }
  211. /// <summary>
  212. /// 获取或设置虚线是否进行显示
  213. /// </summary>
  214. [Category( "外观" )]
  215. [Description( "获取或设置虚线是否进行显示" )]
  216. [Browsable( true )]
  217. [DefaultValue( true )]
  218. public bool IsRenderDashLine
  219. {
  220. get { return value_IsRenderDashLine; }
  221. set
  222. {
  223. value_IsRenderDashLine = value;
  224. Invalidate( );
  225. }
  226. }
  227. /// <summary>
  228. /// 获取或设置坐标轴及相关信息文本的颜色
  229. /// </summary>
  230. [Category( "外观" )]
  231. [Description( "获取或设置坐标轴及相关信息文本的颜色" )]
  232. [Browsable( true )]
  233. [DefaultValue( typeof( Color ), "DimGray" )]
  234. public Color ColorLinesAndText
  235. {
  236. get { return color_deep; }
  237. set
  238. {
  239. color_deep = value;
  240. InitializationColor( );
  241. Invalidate( );
  242. }
  243. }
  244. /// <summary>
  245. /// 获取或设置虚线的颜色
  246. /// </summary>
  247. [Category( "外观" )]
  248. [Description( "获取或设置虚线的颜色" )]
  249. [Browsable( true )]
  250. [DefaultValue( typeof( Color ), "Gray" )]
  251. public Color ColorDashLines
  252. {
  253. get { return color_dash; }
  254. set
  255. {
  256. color_dash = value;
  257. pen_dash?.Dispose( );
  258. pen_dash = new Pen( color_dash );
  259. pen_dash.DashStyle = System.Drawing.Drawing2D.DashStyle.Custom;
  260. pen_dash.DashPattern = new float[] { 5, 5 };
  261. Invalidate( );
  262. }
  263. }
  264. /// <summary>
  265. /// 获取或设置纵向虚线的分隔情况,单位为多少个数据
  266. /// </summary>
  267. [Category( "外观" )]
  268. [Description( "获取或设置纵向虚线的分隔情况,单位为多少个数据" )]
  269. [Browsable( true )]
  270. [DefaultValue( 100 )]
  271. public int IntervalAbscissaText
  272. {
  273. get { return value_IntervalAbscissaText; }
  274. set
  275. {
  276. value_IntervalAbscissaText = value;
  277. Invalidate( );
  278. }
  279. }
  280. /// <summary>
  281. /// 获取或设置实时数据新增时文本相对应于时间的格式化字符串,默认HH:mm
  282. /// </summary>
  283. [Category( "外观" )]
  284. [Description( "获取或设置实时数据新增时文本相对应于时间的格式化字符串,默认HH:mm" )]
  285. [Browsable( true )]
  286. [DefaultValue( "HH:mm" )]
  287. public string TextAddFormat
  288. {
  289. get { return textFormat; }
  290. set { textFormat = value; Invalidate(); }
  291. }
  292. /// <summary>
  293. /// 获取或设置图标的标题信息
  294. /// </summary>
  295. [Category("外观")]
  296. [Description("获取或设置图标的标题信息")]
  297. [Browsable(true)]
  298. [DefaultValue("")]
  299. public string Title
  300. {
  301. get { return value_title; }
  302. set { value_title = value; Invalidate(); }
  303. }
  304. private void InitializationColor( )
  305. {
  306. pen_normal?.Dispose( );
  307. brush_deep?.Dispose( );
  308. pen_normal = new Pen( color_deep );
  309. brush_deep = new SolidBrush( color_deep );
  310. }
  311. #endregion
  312. #region Public Method
  313. /// <summary>
  314. /// 设置曲线的横坐标文本,适用于显示一些固定的曲线信息
  315. /// </summary>
  316. /// <param name="descriptions">应该和曲线的点数一致</param>
  317. public void SetCurveText(string[] descriptions)
  318. {
  319. data_text = descriptions;
  320. // 重绘图形
  321. Invalidate( );
  322. }
  323. /// <summary>
  324. /// 新增或修改一条指定关键字的左参考系曲线数据,需要指定数据,颜色随机,没有数据上限,线条宽度为1
  325. /// </summary>
  326. /// <param name="key">曲线关键字</param>
  327. /// <param name="data">曲线的具体数据</param>
  328. public void SetLeftCurve( string key, float[] data )
  329. {
  330. SetLeftCurve( key, data, Color.FromArgb( random.Next( 256 ), random.Next( 256 ), random.Next( 256 ) ) );
  331. }
  332. /// <summary>
  333. /// 新增或修改一条指定关键字的左参考系曲线数据,需要指定数据,颜色,没有数据上限,线条宽度为1
  334. /// </summary>
  335. /// <param name="key">曲线关键字</param>
  336. /// <param name="data"></param>
  337. /// <param name="lineColor"></param>
  338. public void SetLeftCurve( string key, float[] data, Color lineColor )
  339. {
  340. SetCurve( key, true, data, lineColor, 1f );
  341. }
  342. /// <summary>
  343. /// 新增或修改一条指定关键字的右参考系曲线数据,需要指定数据,颜色随机,没有数据上限,线条宽度为1
  344. /// </summary>
  345. /// <param name="key">曲线关键字</param>
  346. /// <param name="data"></param>
  347. public void SetRightCurve( string key, float[] data )
  348. {
  349. SetRightCurve( key, data, Color.FromArgb( random.Next( 256 ), random.Next( 256 ), random.Next( 256 ) ) );
  350. }
  351. /// <summary>
  352. /// 新增或修改一条指定关键字的右参考系曲线数据,需要指定数据,颜色,没有数据上限,线条宽度为1
  353. /// </summary>
  354. /// <param name="key">曲线关键字</param>
  355. /// <param name="data"></param>
  356. /// <param name="lineColor"></param>
  357. public void SetRightCurve( string key, float[] data, Color lineColor )
  358. {
  359. SetCurve( key, false, data, lineColor, 1f );
  360. }
  361. /// <summary>
  362. /// 新增或修改一条指定关键字的曲线数据,需要指定参考系及数据,颜色,线条宽度
  363. /// </summary>
  364. /// <param name="key">曲线关键字</param>
  365. /// <param name="isLeft">是否以左侧坐标轴为参照系</param>
  366. /// <param name="data">数据</param>
  367. /// <param name="lineColor">线条颜色</param>
  368. /// <param name="thickness">线条宽度</param>
  369. public void SetCurve( string key, bool isLeft, float[] data, Color lineColor, float thickness )
  370. {
  371. if (data_list.ContainsKey( key ))
  372. {
  373. if (data == null) data = new float[] { };
  374. data_list[key].Data = data;
  375. }
  376. else
  377. {
  378. if (data == null) data = new float[] { };
  379. data_list.Add( key, new HslCurveItem( )
  380. {
  381. Data = data,
  382. LineThickness = thickness,
  383. LineColor = lineColor,
  384. IsLeftFrame = isLeft,
  385. } );
  386. if (data_text == null) data_text = new string[data.Length];
  387. }
  388. // 重绘图形
  389. Invalidate( );
  390. }
  391. /// <summary>
  392. /// 移除指定关键字的曲线
  393. /// </summary>
  394. /// <param name="key">曲线关键字</param>
  395. public void RemoveCurve( string key )
  396. {
  397. if (data_list.ContainsKey( key ))
  398. {
  399. data_list.Remove( key );
  400. }
  401. // 重绘图形
  402. Invalidate( );
  403. }
  404. /// <summary>
  405. /// 移除指定关键字的曲线
  406. /// </summary>
  407. public void RemoveAllCurve( )
  408. {
  409. int count = data_list.Count;
  410. data_list.Clear( );
  411. // 重绘图形
  412. if (count > 0) Invalidate( );
  413. }
  414. // ======================================================================================================
  415. /// <summary>
  416. /// 新增指定关键字曲线的一个数据,注意该关键字的曲线必须存在,否则无效
  417. /// </summary>
  418. /// <param name="key">新增曲线的关键字</param>
  419. /// <param name="values"></param>
  420. /// <param name="isUpdateUI">是否刷新界面</param>
  421. private void AddCurveData( string key, float[] values, bool isUpdateUI )
  422. {
  423. if (values?.Length < 1) return; // 没有传入数据
  424. if (data_list.ContainsKey( key ))
  425. {
  426. HslCurveItem curve = data_list[key];
  427. if (curve.Data != null)
  428. {
  429. if (value_IsAbscissaStrech)
  430. {
  431. // 填充玩整个图形的情况
  432. BasicFramework.SoftBasic.AddArrayData( ref curve.Data, values, value_StrechDataCountMax );
  433. }
  434. else
  435. {
  436. // 指定点的情况,必然存在最大值情况
  437. BasicFramework.SoftBasic.AddArrayData( ref curve.Data, values, value_count_max );
  438. }
  439. if (isUpdateUI) Invalidate( );
  440. }
  441. }
  442. }
  443. // 新增曲线的时间节点
  444. private void AddCurveTime( int count )
  445. {
  446. if (data_text == null) return;
  447. string[] values = new string[count];
  448. for (int i = 0; i < values.Length; i++)
  449. {
  450. values[i] = DateTime.Now.ToString( textFormat );
  451. }
  452. if(value_IsAbscissaStrech)
  453. {
  454. BasicFramework.SoftBasic.AddArrayData( ref data_text, values, value_StrechDataCountMax );
  455. }
  456. else
  457. {
  458. BasicFramework.SoftBasic.AddArrayData( ref data_text, values, value_count_max );
  459. }
  460. }
  461. /// <summary>
  462. /// 新增指定关键字曲线的一个数据,注意该关键字的曲线必须存在,否则无效
  463. /// </summary>
  464. /// <param name="key">曲线的关键字</param>
  465. /// <param name="value">数据值</param>
  466. public void AddCurveData( string key, float value )
  467. {
  468. AddCurveData( key, new float[] { value } );
  469. }
  470. /// <summary>
  471. /// 新增指定关键字曲线的一组数据,注意该关键字的曲线必须存在,否则无效
  472. /// </summary>
  473. /// <param name="key">曲线的关键字</param>
  474. /// <param name="values">数组值</param>
  475. public void AddCurveData( string key, float[] values )
  476. {
  477. AddCurveData( key, values, false );
  478. if (values?.Length > 0)
  479. {
  480. AddCurveTime( values.Length );
  481. }
  482. Invalidate( );
  483. }
  484. /// <summary>
  485. /// 新增指定关键字数组曲线的一组数据,注意该关键字的曲线必须存在,否则无效,一个数据对应一个数组
  486. /// </summary>
  487. /// <param name="keys">曲线的关键字数组</param>
  488. /// <param name="values">数组值</param>
  489. public void AddCurveData( string[] keys, float[] values )
  490. {
  491. if (keys == null) throw new ArgumentNullException( "keys" );
  492. if (values == null) throw new ArgumentNullException( "values" );
  493. if (keys.Length != values.Length) throw new Exception( "两个参数的数组长度不一致。" );
  494. for (int i = 0; i < keys.Length; i++)
  495. {
  496. AddCurveData( keys[i], new float[] { values[i] }, false );
  497. }
  498. AddCurveTime( 1 );
  499. // 统一的更新显示
  500. Invalidate( );
  501. }
  502. #endregion
  503. #region Auxiliary Line
  504. // 辅助线的信息,允许自定义辅助线信息,来标注特殊的线条
  505. private List<AuxiliaryLine> auxiliary_lines; // 所有辅助线的列表
  506. /// <summary>
  507. /// 新增一条左侧的辅助线,使用默认的文本颜色
  508. /// </summary>
  509. /// <param name="value">数据值</param>
  510. public void AddLeftAuxiliary( float value )
  511. {
  512. AddLeftAuxiliary( value, ColorLinesAndText );
  513. }
  514. /// <summary>
  515. /// 新增一条左侧的辅助线,使用指定的颜色
  516. /// </summary>
  517. /// <param name="value">数据值</param>
  518. /// <param name="lineColor">线条颜色</param>
  519. public void AddLeftAuxiliary( float value, Color lineColor )
  520. {
  521. AddLeftAuxiliary( value, lineColor, 1f );
  522. }
  523. /// <summary>
  524. /// 新增一条左侧的辅助线
  525. /// </summary>
  526. /// <param name="value">数据值</param>
  527. /// <param name="lineColor">线条颜色</param>
  528. /// <param name="lineThickness">线条宽度</param>
  529. public void AddLeftAuxiliary( float value, Color lineColor, float lineThickness )
  530. {
  531. AddAuxiliary( value, lineColor, lineThickness, true );
  532. }
  533. /// <summary>
  534. /// 新增一条右侧的辅助线,使用默认的文本颜色
  535. /// </summary>
  536. /// <param name="value">数据值</param>
  537. public void AddRightAuxiliary( float value )
  538. {
  539. AddRightAuxiliary( value, ColorLinesAndText );
  540. }
  541. /// <summary>
  542. /// 新增一条右侧的辅助线,使用指定的颜色
  543. /// </summary>
  544. /// <param name="value">数据值</param>
  545. /// <param name="lineColor">线条颜色</param>
  546. public void AddRightAuxiliary( float value, Color lineColor )
  547. {
  548. AddRightAuxiliary( value, lineColor, 1f );
  549. }
  550. /// <summary>
  551. /// 新增一条右侧的辅助线
  552. /// </summary>
  553. /// <param name="value">数据值</param>
  554. /// <param name="lineColor">线条颜色</param>
  555. /// <param name="lineThickness">线条宽度</param>
  556. public void AddRightAuxiliary( float value, Color lineColor, float lineThickness )
  557. {
  558. AddAuxiliary( value, lineColor, lineThickness, false );
  559. }
  560. private void AddAuxiliary( float value, Color lineColor, float lineThickness, bool isLeft )
  561. {
  562. auxiliary_lines.Add( new AuxiliaryLine( )
  563. {
  564. Value = value,
  565. LineColor = lineColor,
  566. PenDash = new Pen( lineColor )
  567. {
  568. DashStyle = System.Drawing.Drawing2D.DashStyle.Custom,
  569. DashPattern = new float[] { 5, 5 }
  570. },
  571. IsLeftFrame = isLeft,
  572. LineThickness = lineThickness,
  573. LineTextBrush = new SolidBrush( lineColor ),
  574. } );
  575. Invalidate( );
  576. }
  577. /// <summary>
  578. /// 移除所有的指定值的辅助曲线,包括左边的和右边的
  579. /// </summary>
  580. /// <param name="value"></param>
  581. public void RemoveAuxiliary( float value )
  582. {
  583. int removeCount = 0;
  584. for (int i = auxiliary_lines.Count - 1; i >= 0; i--)
  585. {
  586. if (auxiliary_lines[i].Value == value)
  587. {
  588. auxiliary_lines[i].Dispose( );
  589. auxiliary_lines.RemoveAt( i );
  590. removeCount++;
  591. }
  592. }
  593. if (removeCount > 0) Invalidate( );
  594. }
  595. /// <summary>
  596. /// 移除所有的辅助线
  597. /// </summary>
  598. public void RemoveAllAuxiliary( )
  599. {
  600. int removeCount = auxiliary_lines.Count;
  601. auxiliary_lines.Clear( );
  602. if (removeCount > 0) Invalidate( );
  603. }
  604. #endregion
  605. #region Private Method
  606. #endregion
  607. #region Core Paint
  608. private Font font_size9 = null;
  609. private Font font_size12 = null;
  610. private Brush brush_deep = null; // 文本的颜色
  611. private Pen pen_normal = null; // 绘制极轴和分段符的坐标线
  612. private Pen pen_dash = null; // 绘制图形的虚线
  613. private Color color_normal = Color.DeepPink; // 文本的颜色
  614. private Color color_deep = Color.DimGray; // 坐标轴及数字文本的信息
  615. private Color color_dash = Color.Gray; // 虚线的颜色
  616. private StringFormat format_left = null; // 靠左对齐的文本
  617. private StringFormat format_right = null; // 靠右对齐的文本
  618. private StringFormat format_center = null; // 中间对齐的文本
  619. private void UserCurve_Paint( object sender, PaintEventArgs e )
  620. {
  621. Graphics g = e.Graphics;
  622. g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
  623. g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
  624. if (BackColor != Color.Transparent)
  625. {
  626. g.Clear( BackColor );
  627. }
  628. int width_totle = Width;
  629. int heigh_totle = Height;
  630. if (width_totle < 120 || heigh_totle < 60) return;
  631. // 绘制极轴
  632. g.DrawLines( pen_normal, new Point[] {
  633. new Point(leftRight-1, upDowm - 8),
  634. new Point(leftRight-1, heigh_totle - upDowm),
  635. new Point(width_totle - leftRight, heigh_totle - upDowm),
  636. new Point(width_totle - leftRight, upDowm - 8)
  637. } );
  638. // 绘制图表的标题
  639. if(!string.IsNullOrEmpty(value_title))
  640. {
  641. g.DrawString(value_title, font_size9, brush_deep, new Rectangle(0, 0, width_totle - 1, 20), format_center);
  642. }
  643. // 绘制倒三角
  644. BasicFramework.SoftPainting.PaintTriangle( g, brush_deep, new Point( leftRight - 1, upDowm - 8 ), 4, BasicFramework.GraphDirection.Upward );
  645. BasicFramework.SoftPainting.PaintTriangle( g, brush_deep, new Point( width_totle - leftRight, upDowm - 8 ), 4, BasicFramework.GraphDirection.Upward );
  646. // 计算所有辅助线的实际值
  647. for (int i = 0; i < auxiliary_lines.Count; i++)
  648. {
  649. if (auxiliary_lines[i].IsLeftFrame)
  650. {
  651. auxiliary_lines[i].PaintValue = BasicFramework.SoftPainting.ComputePaintLocationY( value_max_left, value_min_left, (heigh_totle - upDowm - upDowm), auxiliary_lines[i].Value ) + upDowm;
  652. }
  653. else
  654. {
  655. auxiliary_lines[i].PaintValue = BasicFramework.SoftPainting.ComputePaintLocationY( value_max_right, value_min_right, (heigh_totle - upDowm - upDowm), auxiliary_lines[i].Value ) + upDowm;
  656. }
  657. }
  658. // 绘制刻度线,以及刻度文本
  659. for (int i = 0; i <= value_Segment; i++)
  660. {
  661. float valueTmpLeft = i * (value_max_left - value_min_left) / value_Segment + value_min_left;
  662. float paintTmp = BasicFramework.SoftPainting.ComputePaintLocationY( value_max_left, value_min_left, (heigh_totle - upDowm - upDowm), valueTmpLeft ) + upDowm;
  663. if (IsNeedPaintDash( paintTmp ))
  664. {
  665. // 左坐标轴
  666. g.DrawLine( pen_normal, leftRight - 4, paintTmp, leftRight - 1, paintTmp );
  667. RectangleF rectTmp = new RectangleF( 0, paintTmp - 9, leftRight - 4, 20 );
  668. g.DrawString( valueTmpLeft.ToString( ), font_size9, brush_deep, rectTmp, format_right );
  669. // 右坐标轴
  670. float valueTmpRight = i * (value_max_right - value_min_right) / value_Segment + value_min_right;
  671. g.DrawLine( pen_normal, width_totle - leftRight + 1, paintTmp, width_totle - leftRight + 4, paintTmp );
  672. rectTmp.Location = new PointF( width_totle - leftRight + 4, paintTmp - 9 );
  673. g.DrawString( valueTmpRight.ToString( ), font_size9, brush_deep, rectTmp, format_left );
  674. if (i > 0 && value_IsRenderDashLine) g.DrawLine( pen_dash, leftRight, paintTmp, width_totle - leftRight, paintTmp );
  675. }
  676. }
  677. // 绘制纵向虚线信息
  678. if (value_IsRenderDashLine)
  679. {
  680. if (value_IsAbscissaStrech)
  681. {
  682. // 拉伸模式下,因为错位是均匀的,所以根据数据来显示
  683. float offect = (width_totle - leftRight * 2) * 1.0f / (value_StrechDataCountMax - 1);
  684. int dataCount = CalculateDataCountByOffect( offect );
  685. for (int i = 0; i < value_StrechDataCountMax; i += dataCount)
  686. {
  687. if (i > 0 && i < value_StrechDataCountMax - 1)
  688. {
  689. g.DrawLine( pen_dash, i * offect + leftRight, upDowm, i * offect + leftRight, heigh_totle - upDowm - 1 );
  690. }
  691. if (data_text != null)
  692. {
  693. if (i < data_text.Length && ((i * offect + leftRight) < (data_text.Length - 1) * offect + leftRight -40 ))
  694. {
  695. Rectangle rec = new Rectangle( (int)(i * offect), heigh_totle - upDowm + 1, 100, upDowm );
  696. g.DrawString( data_text[i], font_size9, brush_deep, rec, format_center );
  697. }
  698. }
  699. }
  700. if (data_text?.Length > 1)
  701. {
  702. if (data_text.Length < value_StrechDataCountMax)
  703. {
  704. // 绘制最前端的虚线
  705. g.DrawLine( pen_dash, (data_text.Length - 1) * offect + leftRight, upDowm, (data_text.Length - 1) * offect + leftRight, heigh_totle - upDowm - 1 );
  706. }
  707. Rectangle rec = new Rectangle( (int)((data_text.Length - 1) * offect + leftRight) - leftRight, heigh_totle - upDowm + 1, 100, upDowm );
  708. g.DrawString( data_text[data_text.Length - 1], font_size9, brush_deep, rec, format_center );
  709. }
  710. }
  711. else
  712. {
  713. int countTmp = width_totle - 2 * leftRight + 1;
  714. // 普通模式下绘制图形
  715. for (int i = leftRight; i < width_totle - leftRight; i += value_IntervalAbscissaText)
  716. {
  717. if (i != leftRight)
  718. {
  719. g.DrawLine( pen_dash, i, upDowm, i, heigh_totle - upDowm - 1 );
  720. }
  721. if (data_text != null)
  722. {
  723. int right_limit = countTmp > data_text.Length ? data_text.Length : countTmp;
  724. if ((i - leftRight) < data_text.Length)
  725. {
  726. if ((right_limit - (i - leftRight)) > 40)
  727. {
  728. // 点数大于1的时候才绘制
  729. if (data_text.Length <= countTmp)
  730. {
  731. Rectangle rec = new Rectangle( i - leftRight, heigh_totle - upDowm + 1, 100, upDowm );
  732. g.DrawString( data_text[i - leftRight], font_size9, brush_deep, rec, format_center );
  733. }
  734. else
  735. {
  736. Rectangle rec = new Rectangle( i - leftRight, heigh_totle - upDowm + 1, 100, upDowm );
  737. g.DrawString( data_text[i - leftRight + data_text.Length - countTmp], font_size9, brush_deep, rec, format_center );
  738. }
  739. }
  740. }
  741. }
  742. }
  743. if (data_text?.Length > 1)
  744. {
  745. if (data_text.Length < countTmp)
  746. {
  747. // 绘制最前端的虚线
  748. g.DrawLine( pen_dash, (data_text.Length + leftRight - 1), upDowm, (data_text.Length + leftRight - 1), heigh_totle - upDowm - 1 );
  749. Rectangle rec = new Rectangle( (data_text.Length + leftRight - 1) - leftRight, heigh_totle - upDowm + 1, 100, upDowm );
  750. g.DrawString( data_text[data_text.Length - 1], font_size9, brush_deep, rec, format_center );
  751. }
  752. else
  753. {
  754. Rectangle rec = new Rectangle( width_totle - leftRight - leftRight, heigh_totle - upDowm + 1, 100, upDowm );
  755. g.DrawString( data_text[data_text.Length - 1], font_size9, brush_deep, rec, format_center );
  756. }
  757. }
  758. }
  759. }
  760. // 绘制辅助线信息
  761. for (int i = 0; i < auxiliary_lines.Count; i++)
  762. {
  763. if (auxiliary_lines[i].IsLeftFrame)
  764. {
  765. // 左坐标轴
  766. g.DrawLine( auxiliary_lines[i].PenDash, leftRight - 4, auxiliary_lines[i].PaintValue, leftRight - 1, auxiliary_lines[i].PaintValue );
  767. RectangleF rectTmp = new RectangleF( 0, auxiliary_lines[i].PaintValue - 9, leftRight - 4, 20 );
  768. g.DrawString( auxiliary_lines[i].Value.ToString( ), font_size9, auxiliary_lines[i].LineTextBrush, rectTmp, format_right );
  769. }
  770. else
  771. {
  772. g.DrawLine( auxiliary_lines[i].PenDash, width_totle - leftRight + 1, auxiliary_lines[i].PaintValue, width_totle - leftRight + 4, auxiliary_lines[i].PaintValue );
  773. RectangleF rectTmp = new RectangleF( width_totle - leftRight + 4, auxiliary_lines[i].PaintValue - 9, leftRight - 4, 20 );
  774. g.DrawString( auxiliary_lines[i].Value.ToString( ), font_size9, auxiliary_lines[i].LineTextBrush, rectTmp, format_left );
  775. }
  776. g.DrawLine( auxiliary_lines[i].PenDash, leftRight, auxiliary_lines[i].PaintValue, width_totle - leftRight, auxiliary_lines[i].PaintValue );
  777. }
  778. // 绘制线条
  779. if (value_IsAbscissaStrech)
  780. {
  781. // 横坐标充满图形
  782. foreach (var line in data_list.Values)
  783. {
  784. if (line.Data?.Length > 1)
  785. {
  786. float offect = (width_totle - leftRight * 2) * 1.0f / (value_StrechDataCountMax - 1);
  787. // 点数大于1的时候才绘制
  788. PointF[] points = new PointF[line.Data.Length];
  789. for (int i = 0; i < line.Data.Length; i++)
  790. {
  791. points[i].X = leftRight + i * offect;
  792. points[i].Y = BasicFramework.SoftPainting.ComputePaintLocationY(
  793. line.IsLeftFrame ? value_max_left : value_max_right,
  794. line.IsLeftFrame ? value_min_left : value_min_right,
  795. (heigh_totle - upDowm - upDowm), line.Data[i] ) + upDowm;
  796. }
  797. using (Pen penTmp = new Pen( line.LineColor, line.LineThickness ))
  798. {
  799. g.DrawLines( penTmp, points );
  800. }
  801. }
  802. }
  803. }
  804. else
  805. {
  806. // 横坐标对应图形
  807. foreach (var line in data_list.Values)
  808. {
  809. if (line.Data?.Length > 1)
  810. {
  811. int countTmp = width_totle - 2 * leftRight + 1;
  812. PointF[] points;
  813. // 点数大于1的时候才绘制
  814. if (line.Data.Length <= countTmp)
  815. {
  816. points = new PointF[line.Data.Length];
  817. for (int i = 0; i < line.Data.Length; i++)
  818. {
  819. points[i].X = leftRight + i;
  820. points[i].Y = BasicFramework.SoftPainting.ComputePaintLocationY(
  821. line.IsLeftFrame ? value_max_left : value_max_right,
  822. line.IsLeftFrame ? value_min_left : value_min_right,
  823. (heigh_totle - upDowm - upDowm), line.Data[i] ) + upDowm;
  824. }
  825. }
  826. else
  827. {
  828. points = new PointF[countTmp];
  829. for (int i = 0; i < points.Length; i++)
  830. {
  831. points[i].X = leftRight + i;
  832. points[i].Y = BasicFramework.SoftPainting.ComputePaintLocationY(
  833. line.IsLeftFrame ? value_max_left : value_max_right,
  834. line.IsLeftFrame ? value_min_left : value_min_right,
  835. (heigh_totle - upDowm - upDowm), line.Data[i + line.Data.Length - countTmp] ) + upDowm;
  836. }
  837. }
  838. using (Pen penTmp = new Pen( line.LineColor, line.LineThickness ))
  839. {
  840. g.DrawLines( penTmp, points );
  841. }
  842. }
  843. }
  844. }
  845. }
  846. private bool IsNeedPaintDash( float paintValue )
  847. {
  848. // 遍历所有的数据组
  849. for (int i = 0; i < auxiliary_lines.Count; i++)
  850. {
  851. if (Math.Abs( auxiliary_lines[i].PaintValue - paintValue ) < font_size9.Height)
  852. {
  853. // 与辅助线冲突,不需要绘制
  854. return false;
  855. }
  856. }
  857. // 需要绘制虚线
  858. return true;
  859. }
  860. private int CalculateDataCountByOffect(float offect)
  861. {
  862. if (offect > 40) return 1;
  863. offect = 40f / offect;
  864. return (int)Math.Ceiling( offect );
  865. }
  866. #endregion
  867. #region Size Changed
  868. private void UserCurve_SizeChanged( object sender, EventArgs e )
  869. {
  870. Invalidate( );
  871. }
  872. #endregion
  873. }
  874. /// <summary>
  875. /// 曲线数据对象
  876. /// </summary>
  877. internal class HslCurveItem
  878. {
  879. /// <summary>
  880. /// 实例化一个对象
  881. /// </summary>
  882. public HslCurveItem( )
  883. {
  884. LineThickness = 1.0f;
  885. IsLeftFrame = true;
  886. }
  887. /// <summary>
  888. /// 数据
  889. /// </summary>
  890. public float[] Data = null;
  891. /// <summary>
  892. /// 线条的宽度
  893. /// </summary>
  894. public float LineThickness { get; set; }
  895. /// <summary>
  896. /// 曲线颜色
  897. /// </summary>
  898. public Color LineColor { get; set; }
  899. /// <summary>
  900. /// 是否左侧参考系,True为左侧,False为右侧
  901. /// </summary>
  902. public bool IsLeftFrame { get; set; }
  903. }
  904. /// <summary>
  905. /// 辅助线对象
  906. /// </summary>
  907. internal class AuxiliaryLine : IDisposable
  908. {
  909. /// <summary>
  910. /// 实际的数据值
  911. /// </summary>
  912. public float Value { get; set; }
  913. /// <summary>
  914. /// 实际的数据绘制
  915. /// </summary>
  916. public float PaintValue { get; set; }
  917. /// <summary>
  918. /// 辅助线的颜色
  919. /// </summary>
  920. public Color LineColor { get; set; }
  921. /// <summary>
  922. /// 辅助线的画笔资源
  923. /// </summary>
  924. public Pen PenDash { get; set; }
  925. /// <summary>
  926. /// 辅助线的宽度
  927. /// </summary>
  928. public float LineThickness { get; set; }
  929. /// <summary>
  930. /// 辅助线文本的画刷
  931. /// </summary>
  932. public Brush LineTextBrush { get; set; }
  933. /// <summary>
  934. /// 是否左侧参考系,True为左侧,False为右侧
  935. /// </summary>
  936. public bool IsLeftFrame { get; set; }
  937. #region IDisposable Support
  938. private bool disposedValue = false; // 要检测冗余调用
  939. protected virtual void Dispose( bool disposing )
  940. {
  941. if (!disposedValue)
  942. {
  943. if (disposing)
  944. {
  945. // TODO: 释放托管状态(托管对象)。
  946. PenDash?.Dispose( );
  947. LineTextBrush.Dispose( );
  948. }
  949. // TODO: 释放未托管的资源(未托管的对象)并在以下内容中替代终结器。
  950. // TODO: 将大型字段设置为 null。
  951. disposedValue = true;
  952. }
  953. }
  954. // TODO: 仅当以上 Dispose(bool disposing) 拥有用于释放未托管资源的代码时才替代终结器。
  955. // ~AuxiliaryLine() {
  956. // // 请勿更改此代码。将清理代码放入以上 Dispose(bool disposing) 中。
  957. // Dispose(false);
  958. // }
  959. // 添加此代码以正确实现可处置模式。
  960. /// <summary>
  961. /// 释放内存信息
  962. /// </summary>
  963. public void Dispose( )
  964. {
  965. // 请勿更改此代码。将清理代码放入以上 Dispose(bool disposing) 中。
  966. Dispose( true );
  967. // TODO: 如果在以上内容中替代了终结器,则取消注释以下行。
  968. // GC.SuppressFinalize(this);
  969. }
  970. #endregion
  971. }
  972. }