ChartAreaCursor.cs 49 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698
  1. // Licensed to the .NET Foundation under one or more agreements.
  2. // The .NET Foundation licenses this file to you under the MIT license.
  3. // See the LICENSE file in the project root for more information.
  4. //
  5. // Purpose: A cursor is a horizontal or vertical line that
  6. // defines a position along an axis. A range selection
  7. // is a range along an axis that is defined by a beginning
  8. // and end position, and is displayed using a semi-transparent
  9. // color.
  10. // Both cursors and range selections are implemented by the
  11. // Cursor class, which is exposed as the CursorX and CursorY
  12. // properties of the ChartArea object. The CursorX object is
  13. // for the X axis of a chart area, and the CursorY object is
  14. // for the Y axis. The AxisType property of these objects
  15. // determines if the associated axis is primary or secondary.
  16. // Cursors and range selections can be set via end-user
  17. // interaction and programmatically.
  18. //
  19. using System;
  20. using System.Windows.Forms;
  21. using System.Collections.Generic;
  22. using System.ComponentModel;
  23. using System.Drawing;
  24. using System.Drawing.Design;
  25. using System.Drawing.Drawing2D;
  26. namespace FastReport.DataVisualization.Charting
  27. {
  28. /// <summary>
  29. /// The Cursor class is responsible for chart axes cursor and selection
  30. /// functionality. It contains properties which define visual appearance,
  31. /// position and behavior settings. It also contains methods for
  32. /// drawing cursor and selection in the plotting area.
  33. /// </summary>
  34. [
  35. DefaultProperty("Enabled"),
  36. SRDescription("DescriptionAttributeCursor_Cursor"),
  37. ]
  38. public class Cursor : IDisposable
  39. {
  40. #region Cursor constructors and initialization
  41. /// <summary>
  42. /// Public constructor
  43. /// </summary>
  44. public Cursor()
  45. {
  46. }
  47. /// <summary>
  48. /// Initialize cursor class.
  49. /// </summary>
  50. /// <param name="chartArea">Chart area the cursor belongs to.</param>
  51. /// <param name="attachedToXAxis">Indicates which axes should be used X or Y.</param>
  52. internal void Initialize(ChartArea chartArea, AxisName attachedToXAxis)
  53. {
  54. // Set chart are reference
  55. this._chartArea = chartArea;
  56. // Attach cursor to specified axis
  57. this._attachedToXAxis = attachedToXAxis;
  58. }
  59. #endregion
  60. #region Cursor fields
  61. // Reference to the chart area object the cursor belongs to
  62. private ChartArea _chartArea = null;
  63. // Defines which axis the cursor attached to X or Y
  64. private AxisName _attachedToXAxis = AxisName.X;
  65. // Enables/Disables chart area cursor.
  66. private bool _isUserEnabled = false;
  67. // Enables/Disables chart area selection.
  68. private bool _isUserSelectionEnabled = false;
  69. // Indicates that cursor will automatically scroll the area scaleView if necessary.
  70. private bool _autoScroll = true;
  71. // Cursor line color
  72. private Color _lineColor = Color.Red;
  73. // Cursor line width
  74. private int _lineWidth = 1;
  75. // Cursor line style
  76. private ChartDashStyle _lineDashStyle = ChartDashStyle.Solid;
  77. // Chart area selection color
  78. private Color _selectionColor = Color.LightGray;
  79. // AxisName of the axes (primary/secondary) the cursor is attached to
  80. private AxisType _axisType = AxisType.Primary;
  81. // Cursor position
  82. private double _position = Double.NaN;
  83. // Range selection start position.
  84. private double _selectionStart = Double.NaN;
  85. // Range selection end position.
  86. private double _selectionEnd = Double.NaN;
  87. // Cursor movement interval current & original values
  88. private double _interval = 1;
  89. // Cursor movement interval type
  90. private DateTimeIntervalType _intervalType = DateTimeIntervalType.Auto;
  91. // Cursor movement interval offset current & original values
  92. private double _intervalOffset = 0;
  93. // Cursor movement interval offset type
  94. private DateTimeIntervalType _intervalOffsetType = DateTimeIntervalType.Auto;
  95. // Reference to the axis obhect
  96. private Axis _axis = null;
  97. // User selection start point
  98. private PointF _userSelectionStart = PointF.Empty;
  99. // Indicates that selection must be drawn
  100. private bool _drawSelection = true;
  101. // Indicates that events must be fired when position/selection is changed
  102. private bool _fireUserChangingEvent = false;
  103. // Indicates that XXXChanged events must be fired when position/selection is changed
  104. private bool _fireUserChangedEvent = false;
  105. // Scroll size and direction when AutoScroll is set
  106. private MouseEventArgs _mouseMoveArguments = null;
  107. // Timer used to scroll the data while selecting
  108. private System.Windows.Forms.Timer _scrollTimer = new System.Windows.Forms.Timer();
  109. // Indicates that axis data scaleView was scrolled as a result of the mouse move event
  110. private bool _viewScrolledOnMouseMove = false;
  111. #endregion
  112. #region Cursor "Behavior" public properties.
  113. /// <summary>
  114. /// Gets or sets the position of a cursor.
  115. /// </summary>
  116. [
  117. SRCategory("CategoryAttributeBehavior"),
  118. Bindable(true),
  119. DefaultValue(Double.NaN),
  120. SRDescription("DescriptionAttributeCursor_Position"),
  121. ParenthesizePropertyNameAttribute(true),
  122. TypeConverter(typeof(DoubleDateNanValueConverter)),
  123. ]
  124. public double Position
  125. {
  126. get
  127. {
  128. return _position;
  129. }
  130. set
  131. {
  132. if(_position != value)
  133. {
  134. _position = value;
  135. // Align cursor in connected areas
  136. if(this._chartArea != null && this._chartArea.Common != null && this._chartArea.Common.ChartPicture != null)
  137. {
  138. if(!this._chartArea.alignmentInProcess)
  139. {
  140. AreaAlignmentOrientations orientation = (this._attachedToXAxis == AxisName.X || this._attachedToXAxis == AxisName.X2) ?
  141. AreaAlignmentOrientations.Vertical : AreaAlignmentOrientations.Horizontal;
  142. this._chartArea.Common.ChartPicture.AlignChartAreasCursor(this._chartArea, orientation, false);
  143. }
  144. }
  145. if(this._chartArea != null && !this._chartArea.alignmentInProcess)
  146. {
  147. this.Invalidate(false);
  148. }
  149. if (this._chartArea != null)
  150. _chartArea.CallOnModifing();
  151. }
  152. }
  153. }
  154. /// <summary>
  155. /// Gets or sets the starting position of a cursor's selected range.
  156. /// </summary>
  157. [
  158. SRCategory("CategoryAttributeBehavior"),
  159. Bindable(true),
  160. DefaultValue(Double.NaN),
  161. SRDescription("DescriptionAttributeCursor_SelectionStart"),
  162. TypeConverter(typeof(DoubleDateNanValueConverter)),
  163. ]
  164. public double SelectionStart
  165. {
  166. get
  167. {
  168. return _selectionStart;
  169. }
  170. set
  171. {
  172. if(_selectionStart != value)
  173. {
  174. _selectionStart = value;
  175. // Align cursor in connected areas
  176. if(this._chartArea != null && this._chartArea.Common != null && this._chartArea.Common.ChartPicture != null)
  177. {
  178. if(!this._chartArea.alignmentInProcess)
  179. {
  180. AreaAlignmentOrientations orientation = (this._attachedToXAxis == AxisName.X || this._attachedToXAxis == AxisName.X2) ?
  181. AreaAlignmentOrientations.Vertical : AreaAlignmentOrientations.Horizontal;
  182. this._chartArea.Common.ChartPicture.AlignChartAreasCursor(this._chartArea, orientation, false);
  183. }
  184. }
  185. if(this._chartArea != null && !this._chartArea.alignmentInProcess)
  186. {
  187. this.Invalidate(false);
  188. }
  189. if (this._chartArea != null)
  190. _chartArea.CallOnModifing();
  191. }
  192. }
  193. }
  194. /// <summary>
  195. /// Gets or sets the ending position of a range selection.
  196. /// </summary>
  197. [
  198. SRCategory("CategoryAttributeBehavior"),
  199. Bindable(true),
  200. DefaultValue(Double.NaN),
  201. SRDescription("DescriptionAttributeCursor_SelectionEnd"),
  202. TypeConverter(typeof(DoubleDateNanValueConverter)),
  203. ]
  204. public double SelectionEnd
  205. {
  206. get
  207. {
  208. return _selectionEnd;
  209. }
  210. set
  211. {
  212. if(_selectionEnd != value)
  213. {
  214. _selectionEnd = value;
  215. // Align cursor in connected areas
  216. if(this._chartArea != null && this._chartArea.Common != null && this._chartArea.Common.ChartPicture != null)
  217. {
  218. if(!this._chartArea.alignmentInProcess)
  219. {
  220. AreaAlignmentOrientations orientation = (this._attachedToXAxis == AxisName.X || this._attachedToXAxis == AxisName.X2) ?
  221. AreaAlignmentOrientations.Vertical : AreaAlignmentOrientations.Horizontal;
  222. this._chartArea.Common.ChartPicture.AlignChartAreasCursor(this._chartArea, orientation, false);
  223. }
  224. }
  225. if(this._chartArea != null && !this._chartArea.alignmentInProcess)
  226. {
  227. this.Invalidate(false);
  228. }
  229. if (this._chartArea != null)
  230. _chartArea.CallOnModifing();
  231. }
  232. }
  233. }
  234. /// <summary>
  235. /// Gets or sets a property that enables or disables the cursor interface.
  236. /// </summary>
  237. [
  238. SRCategory("CategoryAttributeBehavior"),
  239. Bindable(true),
  240. DefaultValue(false),
  241. SRDescription("DescriptionAttributeCursor_UserEnabled"),
  242. ]
  243. public bool IsUserEnabled
  244. {
  245. get
  246. {
  247. return _isUserEnabled;
  248. }
  249. set
  250. {
  251. _isUserEnabled = value;
  252. if (this._chartArea != null)
  253. _chartArea.CallOnModifing();
  254. }
  255. }
  256. /// <summary>
  257. /// Gets or sets a property that enables or disables the range selection interface.
  258. /// </summary>
  259. [
  260. SRCategory("CategoryAttributeBehavior"),
  261. Bindable(true),
  262. DefaultValue(false),
  263. SRDescription("DescriptionAttributeCursor_UserSelection"),
  264. ]
  265. public bool IsUserSelectionEnabled
  266. {
  267. get
  268. {
  269. return _isUserSelectionEnabled;
  270. }
  271. set
  272. {
  273. _isUserSelectionEnabled = value;
  274. if (this._chartArea != null)
  275. _chartArea.CallOnModifing();
  276. }
  277. }
  278. /// <summary>
  279. /// Determines if scrolling will occur if a range selection operation
  280. /// extends beyond a boundary of the chart area.
  281. /// </summary>
  282. [
  283. SRCategory("CategoryAttributeBehavior"),
  284. Bindable(true),
  285. DefaultValue(true),
  286. SRDescription("DescriptionAttributeCursor_AutoScroll"),
  287. ]
  288. public bool AutoScroll
  289. {
  290. get
  291. {
  292. return _autoScroll;
  293. }
  294. set
  295. {
  296. _autoScroll = value;
  297. if (this._chartArea != null)
  298. _chartArea.CallOnModifing();
  299. }
  300. }
  301. /// <summary>
  302. /// Gets or sets the type of axis that the cursor is attached to.
  303. /// </summary>
  304. [
  305. SRCategory("CategoryAttributeBehavior"),
  306. Bindable(true),
  307. SRDescription("DescriptionAttributeCursor_AxisType"),
  308. DefaultValue(AxisType.Primary)
  309. ]
  310. public AxisType AxisType
  311. {
  312. get
  313. {
  314. return _axisType;
  315. }
  316. set
  317. {
  318. _axisType = value;
  319. // Reset reference to the axis object
  320. _axis = null;
  321. this.Invalidate(true);
  322. if (this._chartArea != null)
  323. _chartArea.CallOnModifing();
  324. }
  325. }
  326. /// <summary>
  327. /// Gets or sets the cursor movement interval.
  328. /// </summary>
  329. [
  330. SRCategory("CategoryAttributeBehavior"),
  331. Bindable(true),
  332. DefaultValue(1.0),
  333. SRDescription("DescriptionAttributeCursor_Interval"),
  334. ]
  335. public double Interval
  336. {
  337. get
  338. {
  339. return _interval;
  340. }
  341. set
  342. {
  343. _interval = value;
  344. if (this._chartArea != null)
  345. _chartArea.CallOnModifing();
  346. }
  347. }
  348. /// <summary>
  349. /// Gets or sets the unit of measurement of the Interval property.
  350. /// </summary>
  351. [
  352. SRCategory("CategoryAttributeBehavior"),
  353. Bindable(true),
  354. DefaultValue(DateTimeIntervalType.Auto),
  355. SRDescription("DescriptionAttributeCursor_IntervalType")
  356. ]
  357. public DateTimeIntervalType IntervalType
  358. {
  359. get
  360. {
  361. return _intervalType;
  362. }
  363. set
  364. {
  365. _intervalType = (value != DateTimeIntervalType.NotSet) ? value : DateTimeIntervalType.Auto;
  366. if (this._chartArea != null)
  367. _chartArea.CallOnModifing();
  368. }
  369. }
  370. /// <summary>
  371. /// Gets or sets the interval offset, which determines
  372. /// where to draw the cursor and range selection.
  373. /// </summary>
  374. [
  375. SRCategory("CategoryAttributeBehavior"),
  376. Bindable(true),
  377. DefaultValue(0.0),
  378. SRDescription("DescriptionAttributeCursor_IntervalOffset"),
  379. ]
  380. public double IntervalOffset
  381. {
  382. get
  383. {
  384. return _intervalOffset;
  385. }
  386. set
  387. {
  388. // Validation
  389. if( value < 0.0 )
  390. {
  391. throw (new ArgumentException(SR.ExceptionCursorIntervalOffsetIsNegative, "value"));
  392. }
  393. _intervalOffset = value;
  394. if (this._chartArea != null)
  395. _chartArea.CallOnModifing();
  396. }
  397. }
  398. /// <summary>
  399. /// Gets or sets the unit of measurement of the IntervalOffset property.
  400. /// </summary>
  401. [
  402. SRCategory("CategoryAttributeBehavior"),
  403. Bindable(true),
  404. DefaultValue(DateTimeIntervalType.Auto),
  405. SRDescription("DescriptionAttributeCursor_IntervalOffsetType"),
  406. ]
  407. public DateTimeIntervalType IntervalOffsetType
  408. {
  409. get
  410. {
  411. return _intervalOffsetType;
  412. }
  413. set
  414. {
  415. _intervalOffsetType = (value != DateTimeIntervalType.NotSet) ? value : DateTimeIntervalType.Auto;
  416. if (this._chartArea != null)
  417. _chartArea.CallOnModifing();
  418. }
  419. }
  420. #endregion
  421. #region Cursor "Appearance" public properties
  422. /// <summary>
  423. /// Gets or sets the color the cursor line.
  424. /// </summary>
  425. [
  426. SRCategory("CategoryAttributeAppearance"),
  427. Bindable(true),
  428. DefaultValue(typeof(Color), "Red"),
  429. SRDescription("DescriptionAttributeLineColor"),
  430. TypeConverter(typeof(ColorConverter)),
  431. #if DESIGNER
  432. Editor(typeof(ChartColorEditor), typeof(UITypeEditor))
  433. #endif
  434. ]
  435. public Color LineColor
  436. {
  437. get
  438. {
  439. return _lineColor;
  440. }
  441. set
  442. {
  443. _lineColor = value;
  444. this.Invalidate(false);
  445. if (this._chartArea != null)
  446. _chartArea.CallOnModifing();
  447. }
  448. }
  449. /// <summary>
  450. /// Gets or sets the style of the cursor line.
  451. /// </summary>
  452. [
  453. SRCategory("CategoryAttributeAppearance"),
  454. Bindable(true),
  455. DefaultValue(ChartDashStyle.Solid),
  456. SRDescription("DescriptionAttributeLineDashStyle"),
  457. ]
  458. public ChartDashStyle LineDashStyle
  459. {
  460. get
  461. {
  462. return _lineDashStyle;
  463. }
  464. set
  465. {
  466. _lineDashStyle = value;
  467. this.Invalidate(false);
  468. if (this._chartArea != null)
  469. _chartArea.CallOnModifing();
  470. }
  471. }
  472. /// <summary>
  473. /// Gets or sets the width of the cursor line.
  474. /// </summary>
  475. [
  476. SRCategory("CategoryAttributeAppearance"),
  477. Bindable(true),
  478. DefaultValue(1),
  479. SRDescription("DescriptionAttributeLineWidth"),
  480. ]
  481. public int LineWidth
  482. {
  483. get
  484. {
  485. return _lineWidth;
  486. }
  487. set
  488. {
  489. if(value < 0)
  490. {
  491. throw (new ArgumentOutOfRangeException("value", SR.ExceptionCursorLineWidthIsNegative));
  492. }
  493. _lineWidth = value;
  494. this.Invalidate(true);
  495. if (this._chartArea != null)
  496. _chartArea.CallOnModifing();
  497. }
  498. }
  499. /// <summary>
  500. /// Gets or sets a semi-transparent color that highlights a range of data.
  501. /// </summary>
  502. [
  503. SRCategory("CategoryAttributeAppearance"),
  504. Bindable(true),
  505. DefaultValue(typeof(Color), "LightGray"),
  506. SRDescription("DescriptionAttributeCursor_SelectionColor"),
  507. TypeConverter(typeof(ColorConverter)),
  508. #if DESIGNER
  509. Editor(typeof(ChartColorEditor), typeof(UITypeEditor))
  510. #endif
  511. ]
  512. public Color SelectionColor
  513. {
  514. get
  515. {
  516. return _selectionColor;
  517. }
  518. set
  519. {
  520. _selectionColor = value;
  521. this.Invalidate(false);
  522. if (this._chartArea != null)
  523. _chartArea.CallOnModifing();
  524. }
  525. }
  526. #endregion
  527. #region Cursor painting methods
  528. /// <summary>
  529. /// Draws chart area cursor and selection.
  530. /// </summary>
  531. /// <param name="graph">Reference to the ChartGraphics object.</param>
  532. internal void Paint( ChartGraphics graph )
  533. {
  534. //***************************************************
  535. //** Prepare for drawing
  536. //***************************************************
  537. // Do not proceed with painting if cursor is not attached to the axis
  538. if(this.GetAxis() == null ||
  539. this._chartArea == null ||
  540. this._chartArea.Common == null ||
  541. this._chartArea.Common.ChartPicture == null ||
  542. this._chartArea.Common.ChartPicture.isPrinting)
  543. {
  544. return;
  545. }
  546. // Get plot area position
  547. RectangleF plotAreaPosition = this._chartArea.PlotAreaPosition.ToRectangleF();
  548. // Detect if cursor is horizontal or vertical
  549. bool horizontal = true;
  550. if(this.GetAxis().AxisPosition == AxisPosition.Bottom || this.GetAxis().AxisPosition == AxisPosition.Top)
  551. {
  552. horizontal = false;
  553. }
  554. //***************************************************
  555. //** Draw selection
  556. //***************************************************
  557. // Check if selection need to be drawn
  558. if(this._drawSelection &&
  559. !double.IsNaN(this.SelectionStart) &&
  560. !double.IsNaN(this.SelectionEnd) &&
  561. this.SelectionColor != Color.Empty)
  562. {
  563. // Calculate selection rectangle
  564. RectangleF rectSelection = GetSelectionRect(plotAreaPosition);
  565. rectSelection.Intersect(plotAreaPosition);
  566. // Get opposite axis selection rectangle
  567. RectangleF rectOppositeSelection = GetOppositeSelectionRect(plotAreaPosition);
  568. // Draw selection if rectangle is not empty
  569. if(!rectSelection.IsEmpty && rectSelection.Width > 0 && rectSelection.Height > 0)
  570. {
  571. // Limit selection rectangle to the area of the opposite selection
  572. if(!rectOppositeSelection.IsEmpty && rectOppositeSelection.Width > 0 && rectOppositeSelection.Height > 0)
  573. {
  574. rectSelection.Intersect(rectOppositeSelection);
  575. // We do not need to draw selection in the opposite axis
  576. Cursor oppositeCursor =
  577. (_attachedToXAxis == AxisName.X || _attachedToXAxis == AxisName.X2) ?
  578. _chartArea.CursorY : _chartArea.CursorX;
  579. oppositeCursor._drawSelection = false;
  580. }
  581. // Make sure selection is inside plotting area
  582. rectSelection.Intersect(plotAreaPosition);
  583. // If selection rectangle is not empty
  584. if(rectSelection.Width > 0 && rectSelection.Height > 0)
  585. {
  586. // Add transparency to solid colors
  587. Color rangeSelectionColor = this.SelectionColor;
  588. if(rangeSelectionColor.A == 255)
  589. {
  590. rangeSelectionColor = Color.FromArgb(120, rangeSelectionColor);
  591. }
  592. // Draw selection
  593. graph.FillRectangleRel( rectSelection,
  594. rangeSelectionColor,
  595. ChartHatchStyle.None,
  596. "",
  597. ChartImageWrapMode.Tile,
  598. Color.Empty,
  599. ChartImageAlignmentStyle.Center,
  600. GradientStyle.None,
  601. Color.Empty,
  602. Color.Empty,
  603. 0,
  604. ChartDashStyle.NotSet,
  605. Color.Empty,
  606. 0,
  607. PenAlignment.Inset );
  608. }
  609. }
  610. }
  611. //***************************************************
  612. //** Draw cursor
  613. //***************************************************
  614. // Check if cursor need to be drawn
  615. if(!double.IsNaN(this.Position) &&
  616. this.LineColor != Color.Empty &&
  617. this.LineWidth > 0 &&
  618. this.LineDashStyle != ChartDashStyle.NotSet)
  619. {
  620. // Calculate line position
  621. bool insideArea = false;
  622. PointF point1 = PointF.Empty;
  623. PointF point2 = PointF.Empty;
  624. if(horizontal)
  625. {
  626. // Set cursor coordinates
  627. point1.X = plotAreaPosition.X;
  628. point1.Y = (float)this.GetAxis().GetLinearPosition(this.Position);
  629. point2.X = plotAreaPosition.Right;
  630. point2.Y = point1.Y;
  631. // Check if cursor is inside plotting rect
  632. if(point1.Y >= plotAreaPosition.Y && point1.Y <= plotAreaPosition.Bottom)
  633. {
  634. insideArea = true;
  635. }
  636. }
  637. else
  638. {
  639. // Set cursor coordinates
  640. point1.X = (float)this.GetAxis().GetLinearPosition(this.Position);
  641. point1.Y = plotAreaPosition.Y;
  642. point2.X = point1.X;
  643. point2.Y = plotAreaPosition.Bottom;
  644. // Check if cursor is inside plotting rect
  645. if(point1.X >= plotAreaPosition.X && point1.X <= plotAreaPosition.Right)
  646. {
  647. insideArea = true;
  648. }
  649. }
  650. // Draw cursor if it's inside the chart area plotting rectangle
  651. if(insideArea)
  652. {
  653. graph.DrawLineRel(this.LineColor, this.LineWidth, this.LineDashStyle, point1, point2);
  654. }
  655. }
  656. // Reset draw selection flag
  657. this._drawSelection = true;
  658. }
  659. #endregion
  660. #region Cursor position setting methods
  661. /// <summary>
  662. /// This method sets the position of a cursor within a chart area at a given axis value.
  663. /// </summary>
  664. /// <param name="newPosition">The new position of the cursor. Measured as a value along the relevant axis.</param>
  665. public void SetCursorPosition(double newPosition)
  666. {
  667. // Check if we are setting different value
  668. if(this.Position != newPosition)
  669. {
  670. double newRoundedPosition = RoundPosition(newPosition);
  671. // Send PositionChanging event
  672. if(_fireUserChangingEvent && GetChartObject() != null)
  673. {
  674. CursorEventArgs arguments = new CursorEventArgs(this._chartArea, this.GetAxis(), newRoundedPosition);
  675. GetChartObject().OnCursorPositionChanging(arguments);
  676. // Check if position values were changed in the event
  677. newRoundedPosition = arguments.NewPosition;
  678. }
  679. // Change position
  680. this.Position = newRoundedPosition;
  681. // Send PositionChanged event
  682. if(_fireUserChangedEvent && GetChartObject() != null)
  683. {
  684. CursorEventArgs arguments = new CursorEventArgs(this._chartArea, this.GetAxis(), this.Position);
  685. GetChartObject().OnCursorPositionChanged(arguments);
  686. }
  687. }
  688. }
  689. /// <summary>
  690. /// This method displays a cursor at the specified position. Measured in pixels.
  691. /// </summary>
  692. /// <param name="point">A PointF structure that specifies where the cursor will be drawn.</param>
  693. /// <param name="roundToBoundary">If true, the cursor will be drawn along the nearest chart area boundary
  694. /// when the specified position does not fall within a ChartArea object.</param>
  695. public void SetCursorPixelPosition(PointF point, bool roundToBoundary)
  696. {
  697. if(this._chartArea != null && this._chartArea.Common != null && this.GetAxis() != null)
  698. {
  699. PointF relativeCoord = GetPositionInPlotArea(point, roundToBoundary);
  700. if(!relativeCoord.IsEmpty)
  701. {
  702. // Get new cursor position
  703. double newCursorPosition = PositionToCursorPosition(relativeCoord);
  704. // Set new cursor & selection position
  705. this.SetCursorPosition(newCursorPosition);
  706. }
  707. }
  708. }
  709. /// <summary>
  710. /// This method sets the position of a selected range within a chart area at given axis values.
  711. /// </summary>
  712. /// <param name="newStart">The new starting position of the range selection. Measured as a value along the relevant axis..</param>
  713. /// <param name="newEnd">The new ending position of the range selection. Measured as a value along the relevant axis.</param>
  714. public void SetSelectionPosition(double newStart, double newEnd)
  715. {
  716. // Check if we are setting different value
  717. if(this.SelectionStart != newStart || this.SelectionEnd != newEnd)
  718. {
  719. // Send PositionChanging event
  720. double newRoundedSelectionStart = RoundPosition(newStart);
  721. double newRoundedSelectionEnd = RoundPosition(newEnd);
  722. // Send SelectionRangeChanging event
  723. if(_fireUserChangingEvent && GetChartObject() != null)
  724. {
  725. CursorEventArgs arguments = new CursorEventArgs(this._chartArea, this.GetAxis(), newRoundedSelectionStart, newRoundedSelectionEnd);
  726. GetChartObject().OnSelectionRangeChanging(arguments);
  727. // Check if position values were changed in the event
  728. newRoundedSelectionStart = arguments.NewSelectionStart;
  729. newRoundedSelectionEnd = arguments.NewSelectionEnd;
  730. }
  731. // Change selection position
  732. this._selectionStart = newRoundedSelectionStart;
  733. this._selectionEnd = newRoundedSelectionEnd;
  734. // Align cursor in connected areas
  735. if(this._chartArea != null && this._chartArea.Common != null && this._chartArea.Common.ChartPicture != null)
  736. {
  737. if(!this._chartArea.alignmentInProcess)
  738. {
  739. AreaAlignmentOrientations orientation = (this._attachedToXAxis == AxisName.X || this._attachedToXAxis == AxisName.X2) ?
  740. AreaAlignmentOrientations.Vertical : AreaAlignmentOrientations.Horizontal;
  741. this._chartArea.Common.ChartPicture.AlignChartAreasCursor(this._chartArea, orientation, true);
  742. }
  743. }
  744. if(this._chartArea != null && !this._chartArea.alignmentInProcess)
  745. {
  746. this.Invalidate(false);
  747. }
  748. // Send SelectionRangeChanged event
  749. if(_fireUserChangedEvent && GetChartObject() != null)
  750. {
  751. CursorEventArgs arguments = new CursorEventArgs(this._chartArea, this.GetAxis(), this.SelectionStart, this.SelectionEnd);
  752. GetChartObject().OnSelectionRangeChanged(arguments);
  753. }
  754. }
  755. }
  756. /// <summary>
  757. /// This method sets the starting and ending positions of a range selection.
  758. /// </summary>
  759. /// <param name="startPoint">A PointF structure that specifies where the range selection begins.</param>
  760. /// <param name="endPoint">A PointF structure that specifies where the range selection ends</param>
  761. /// <param name="roundToBoundary">If true, the starting and ending points will be rounded to the nearest chart area boundary
  762. /// when the specified positions do not fall within a ChartArea object.</param>
  763. public void SetSelectionPixelPosition(PointF startPoint, PointF endPoint, bool roundToBoundary)
  764. {
  765. if(this._chartArea != null && this._chartArea.Common != null && this.GetAxis() != null)
  766. {
  767. // Calculating the start position
  768. double newStart = this.SelectionStart;
  769. if(!startPoint.IsEmpty)
  770. {
  771. PointF relativeCoord = GetPositionInPlotArea(startPoint, roundToBoundary);
  772. if(!relativeCoord.IsEmpty)
  773. {
  774. // Get new selection start position
  775. newStart = PositionToCursorPosition(relativeCoord);
  776. }
  777. }
  778. // Setting the end position
  779. double newEnd = newStart;
  780. if(!endPoint.IsEmpty)
  781. {
  782. PointF relativeCoord = GetPositionInPlotArea(endPoint, roundToBoundary);
  783. if(!relativeCoord.IsEmpty)
  784. {
  785. // Get new selection position
  786. newEnd = PositionToCursorPosition(relativeCoord);
  787. }
  788. }
  789. // Set new selection start & end position
  790. this.SetSelectionPosition(newStart, newEnd);
  791. }
  792. }
  793. #endregion
  794. #region Position rounding methods
  795. /// <summary>
  796. /// Rounds new position of the cursor or range selection
  797. /// </summary>
  798. /// <param name="cursorPosition"></param>
  799. /// <returns></returns>
  800. internal double RoundPosition(double cursorPosition)
  801. {
  802. double roundedPosition = cursorPosition;
  803. if(!double.IsNaN(roundedPosition))
  804. {
  805. // Check if position rounding is required
  806. if(this.GetAxis() != null &&
  807. this.Interval != 0 &&
  808. !double.IsNaN(this.Interval))
  809. {
  810. // Get first series attached to this axis
  811. Series axisSeries = null;
  812. if(_axis.axisType == AxisName.X || _axis.axisType == AxisName.X2)
  813. {
  814. List<string> seriesArray = _axis.ChartArea.GetXAxesSeries((_axis.axisType == AxisName.X) ? AxisType.Primary : AxisType.Secondary, _axis.SubAxisName);
  815. if(seriesArray.Count > 0)
  816. {
  817. string seriesName = seriesArray[0] as string;
  818. axisSeries = _axis.Common.DataManager.Series[seriesName];
  819. if(axisSeries != null && !axisSeries.IsXValueIndexed)
  820. {
  821. axisSeries = null;
  822. }
  823. }
  824. }
  825. // If interval type is not set - use number
  826. DateTimeIntervalType intervalType =
  827. (this.IntervalType == DateTimeIntervalType.Auto) ?
  828. DateTimeIntervalType.Number : this.IntervalType;
  829. // If interval offset type is not set - use interval type
  830. DateTimeIntervalType offsetType =
  831. (this.IntervalOffsetType == DateTimeIntervalType.Auto) ?
  832. intervalType : this.IntervalOffsetType;
  833. // Round numbers
  834. if(intervalType == DateTimeIntervalType.Number)
  835. {
  836. double newRoundedPosition = Math.Round(roundedPosition / this.Interval) * this.Interval;
  837. // Add offset number
  838. if(this.IntervalOffset != 0 &&
  839. !double.IsNaN(IntervalOffset) &&
  840. offsetType != DateTimeIntervalType.Auto)
  841. {
  842. if(this.IntervalOffset > 0)
  843. {
  844. newRoundedPosition += ChartHelper.GetIntervalSize(newRoundedPosition, this.IntervalOffset, offsetType);
  845. }
  846. else
  847. {
  848. newRoundedPosition -= ChartHelper.GetIntervalSize(newRoundedPosition, this.IntervalOffset, offsetType);
  849. }
  850. }
  851. // Find rounded position after/before the current
  852. double nextPosition = newRoundedPosition;
  853. if(newRoundedPosition <= cursorPosition)
  854. {
  855. nextPosition += ChartHelper.GetIntervalSize(newRoundedPosition, this.Interval, intervalType, axisSeries, 0, DateTimeIntervalType.Number, true);
  856. }
  857. else
  858. {
  859. nextPosition -= ChartHelper.GetIntervalSize(newRoundedPosition, this.Interval, intervalType, axisSeries, 0, DateTimeIntervalType.Number, true);
  860. }
  861. // Choose closest rounded position
  862. if(Math.Abs(nextPosition - cursorPosition) > Math.Abs(cursorPosition - newRoundedPosition))
  863. {
  864. roundedPosition = newRoundedPosition;
  865. }
  866. else
  867. {
  868. roundedPosition = nextPosition;
  869. }
  870. }
  871. // Round date/time
  872. else
  873. {
  874. // Find one rounded position prior and one after current position
  875. // Adjust start position depending on the interval and type
  876. double prevPosition = ChartHelper.AlignIntervalStart(cursorPosition, this.Interval, intervalType, axisSeries);
  877. // Adjust start position depending on the interval offset and offset type
  878. if( IntervalOffset != 0 && axisSeries == null)
  879. {
  880. if(this.IntervalOffset > 0)
  881. {
  882. prevPosition += ChartHelper.GetIntervalSize(
  883. prevPosition,
  884. this.IntervalOffset,
  885. offsetType,
  886. axisSeries,
  887. 0,
  888. DateTimeIntervalType.Number,
  889. true);
  890. }
  891. else
  892. {
  893. prevPosition += ChartHelper.GetIntervalSize(
  894. prevPosition,
  895. -this.IntervalOffset,
  896. offsetType,
  897. axisSeries,
  898. 0,
  899. DateTimeIntervalType.Number,
  900. true);
  901. }
  902. }
  903. // Find rounded position after/before the current
  904. double nextPosition = prevPosition;
  905. if(prevPosition <= cursorPosition)
  906. {
  907. nextPosition += ChartHelper.GetIntervalSize(prevPosition, this.Interval, intervalType, axisSeries, 0, DateTimeIntervalType.Number, true);
  908. }
  909. else
  910. {
  911. nextPosition -= ChartHelper.GetIntervalSize(prevPosition, this.Interval, intervalType, axisSeries, 0, DateTimeIntervalType.Number, true);
  912. }
  913. // Choose closest rounded position
  914. if(Math.Abs(nextPosition - cursorPosition) > Math.Abs(cursorPosition - prevPosition))
  915. {
  916. roundedPosition = prevPosition;
  917. }
  918. else
  919. {
  920. roundedPosition = nextPosition;
  921. }
  922. }
  923. }
  924. }
  925. return roundedPosition;
  926. }
  927. #endregion
  928. #region Mouse events handling for the Cursor
  929. /// <summary>
  930. /// Mouse down event handler.
  931. /// </summary>
  932. internal void Cursor_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
  933. {
  934. // Set flag to fire position changing events
  935. _fireUserChangingEvent = true;
  936. _fireUserChangedEvent = false;
  937. // Check if left mouse button was clicked in chart area
  938. if(e.Button == MouseButtons.Left && !GetPositionInPlotArea(new PointF(e.X, e.Y), false).IsEmpty)
  939. {
  940. // Change cursor position and selection start position when mouse down
  941. if(this.IsUserEnabled)
  942. {
  943. SetCursorPixelPosition(new PointF(e.X, e.Y), false);
  944. }
  945. if(this.IsUserSelectionEnabled)
  946. {
  947. this._userSelectionStart = new PointF(e.X, e.Y);
  948. SetSelectionPixelPosition(this._userSelectionStart, PointF.Empty, false);
  949. }
  950. }
  951. // Clear flag to fire position changing events
  952. _fireUserChangingEvent = false;
  953. _fireUserChangedEvent = false;
  954. }
  955. /// <summary>
  956. /// Mouse up event handler.
  957. /// </summary>
  958. internal void Cursor_MouseUp(object sender, System.Windows.Forms.MouseEventArgs e)
  959. {
  960. // If in range selection mode
  961. if(!this._userSelectionStart.IsEmpty)
  962. {
  963. // Stop timer
  964. _scrollTimer.Stop();
  965. _mouseMoveArguments = null;
  966. // Check if axis data scaleView zooming UI is enabled
  967. if(this._axis != null &&
  968. this._axis.ScaleView.Zoomable &&
  969. !double.IsNaN(this.SelectionStart) &&
  970. !double.IsNaN(this.SelectionEnd) &&
  971. this.SelectionStart != this.SelectionEnd)
  972. {
  973. // Zoom data scaleView
  974. double start = Math.Min(this.SelectionStart, this.SelectionEnd);
  975. double size = (double)Math.Max(this.SelectionStart, this.SelectionEnd) - start;
  976. bool zoomed = this._axis.ScaleView.Zoom(start, size, DateTimeIntervalType.Number, true, true);
  977. // Clear image buffer
  978. if(this._chartArea.areaBufferBitmap != null && zoomed)
  979. {
  980. this._chartArea.areaBufferBitmap.Dispose();
  981. this._chartArea.areaBufferBitmap = null;
  982. }
  983. // Clear range selection
  984. this.SelectionStart = double.NaN;
  985. this.SelectionEnd = double.NaN;
  986. // NOTE: Fixes issue #6823
  987. // Clear cursor position after the zoom in operation
  988. this.Position = double.NaN;
  989. // Align cursor in connected areas
  990. if(this._chartArea != null && this._chartArea.Common != null && this._chartArea.Common.ChartPicture != null)
  991. {
  992. if(!this._chartArea.alignmentInProcess)
  993. {
  994. AreaAlignmentOrientations orientation = (this._attachedToXAxis == AxisName.X || this._attachedToXAxis == AxisName.X2) ?
  995. AreaAlignmentOrientations.Vertical : AreaAlignmentOrientations.Horizontal;
  996. this._chartArea.Common.ChartPicture.AlignChartAreasZoomed(this._chartArea, orientation, zoomed);
  997. }
  998. }
  999. }
  1000. // Fire XXXChanged events
  1001. if(GetChartObject() != null)
  1002. {
  1003. CursorEventArgs arguments = new CursorEventArgs(this._chartArea, this.GetAxis(), this.SelectionStart, this.SelectionEnd);
  1004. GetChartObject().OnSelectionRangeChanged(arguments);
  1005. arguments = new CursorEventArgs(this._chartArea, this.GetAxis(), this.Position);
  1006. GetChartObject().OnCursorPositionChanged(arguments);
  1007. }
  1008. // Stop range selection mode
  1009. this._userSelectionStart = PointF.Empty;
  1010. }
  1011. }
  1012. /// <summary>
  1013. /// Mouse move event handler.
  1014. /// </summary>
  1015. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Mobility", "CA1601:DoNotUseTimersThatPreventPowerStateChanges", Justification = "The timer is used for simulating scrolling behavior")]
  1016. internal void Cursor_MouseMove(System.Windows.Forms.MouseEventArgs e, ref bool handled)
  1017. {
  1018. // Process range selection
  1019. if(this._userSelectionStart != PointF.Empty)
  1020. {
  1021. // Mouse move event should not be handled by any other chart elements
  1022. handled = true;
  1023. // Set flag to fire position changing events
  1024. _fireUserChangingEvent = true;
  1025. _fireUserChangedEvent = false;
  1026. // Check if mouse position is outside of the chart area and if not - try scrolling
  1027. if(this.AutoScroll)
  1028. {
  1029. if(this._chartArea != null && this._chartArea.Common != null && this.GetAxis()!= null)
  1030. {
  1031. // Check if axis data scaleView is enabled
  1032. if(!double.IsNaN(this._axis.ScaleView.Position) && !double.IsNaN(this._axis.ScaleView.Size))
  1033. {
  1034. ScrollType scrollType = ScrollType.SmallIncrement;
  1035. bool insideChartArea = true;
  1036. double offsetFromBoundary = 0.0;
  1037. // Translate mouse pixel coordinates into the relative chart area coordinates
  1038. float mouseX = e.X * 100F / ((float)(this._chartArea.Common.Width - 1));
  1039. float mouseY = e.Y * 100F / ((float)(this._chartArea.Common.Height - 1));
  1040. // Check if coordinate is inside chart plotting area
  1041. if(this._axis.AxisPosition == AxisPosition.Bottom || this._axis.AxisPosition == AxisPosition.Top)
  1042. {
  1043. if(mouseX < this._chartArea.PlotAreaPosition.X)
  1044. {
  1045. scrollType = ScrollType.SmallDecrement;
  1046. insideChartArea = false;
  1047. offsetFromBoundary = this._chartArea.PlotAreaPosition.X - mouseX;
  1048. }
  1049. else if(mouseX > this._chartArea.PlotAreaPosition.Right)
  1050. {
  1051. scrollType = ScrollType.SmallIncrement;
  1052. insideChartArea = false;
  1053. offsetFromBoundary = mouseX - this._chartArea.PlotAreaPosition.Right;
  1054. }
  1055. }
  1056. else
  1057. {
  1058. if(mouseY < this._chartArea.PlotAreaPosition.Y)
  1059. {
  1060. scrollType = ScrollType.SmallIncrement;
  1061. insideChartArea = false;
  1062. offsetFromBoundary = this._chartArea.PlotAreaPosition.Y - mouseY;
  1063. }
  1064. else if(mouseY > this._chartArea.PlotAreaPosition.Bottom)
  1065. {
  1066. scrollType = ScrollType.SmallDecrement;
  1067. insideChartArea = false;
  1068. offsetFromBoundary = mouseY - this._chartArea.PlotAreaPosition.Bottom;
  1069. }
  1070. }
  1071. // Try scrolling scaleView position
  1072. if(!insideChartArea)
  1073. {
  1074. // Set flag that data scaleView was scrolled
  1075. _viewScrolledOnMouseMove = true;
  1076. // Get minimum scroll interval
  1077. double scrollInterval = ChartHelper.GetIntervalSize(
  1078. this._axis.ScaleView.Position,
  1079. this._axis.ScaleView.GetScrollingLineSize(),
  1080. this._axis.ScaleView.GetScrollingLineSizeType());
  1081. offsetFromBoundary *= 2;
  1082. if(offsetFromBoundary > scrollInterval)
  1083. {
  1084. scrollInterval = ((int)(offsetFromBoundary / scrollInterval)) * scrollInterval;
  1085. }
  1086. // Scroll axis data scaleView
  1087. double newDataViewPosition = this._axis.ScaleView.Position;
  1088. if(scrollType == ScrollType.SmallIncrement)
  1089. {
  1090. newDataViewPosition += scrollInterval;
  1091. }
  1092. else
  1093. {
  1094. newDataViewPosition -= scrollInterval;
  1095. }
  1096. // Scroll axis data scaleView
  1097. this._axis.ScaleView.Scroll(newDataViewPosition);
  1098. // Save last mouse move arguments
  1099. _mouseMoveArguments = new MouseEventArgs(e.Button, e.Clicks, e.X, e.Y, e.Delta);
  1100. // Start selection scrolling timer
  1101. if(!_scrollTimer.Enabled)
  1102. {
  1103. // Start timer
  1104. _scrollTimer.Tick += new EventHandler(SelectionScrollingTimerEventProcessor);
  1105. _scrollTimer.Interval = 200;
  1106. _scrollTimer.Start();
  1107. }
  1108. }
  1109. else
  1110. {
  1111. // Stop timer
  1112. _scrollTimer.Stop();
  1113. _mouseMoveArguments = null;
  1114. }
  1115. }
  1116. }
  1117. }
  1118. // Change cursor position and selection end position when mouse moving
  1119. if(this.IsUserEnabled)
  1120. {
  1121. SetCursorPixelPosition(new PointF(e.X, e.Y), true);
  1122. }
  1123. if(this.IsUserSelectionEnabled)
  1124. {
  1125. // Set selection
  1126. SetSelectionPixelPosition(PointF.Empty, new PointF(e.X, e.Y), true);
  1127. }
  1128. // Clear flag to fire position changing events
  1129. _fireUserChangingEvent = false;
  1130. _fireUserChangedEvent = false;
  1131. // Clear flag that data scaleView was scrolled
  1132. _viewScrolledOnMouseMove = false;
  1133. }
  1134. }
  1135. /// <summary>
  1136. /// This is the method to run when the timer is raised.
  1137. /// Used to scroll axis data scaleView while mouse is outside of the chart area.
  1138. /// </summary>
  1139. /// <param name="myObject"></param>
  1140. /// <param name="myEventArgs"></param>
  1141. private void SelectionScrollingTimerEventProcessor(Object myObject, EventArgs myEventArgs)
  1142. {
  1143. // Simulate mouse move events
  1144. if(_mouseMoveArguments != null)
  1145. {
  1146. bool handled = false;
  1147. this.Cursor_MouseMove(_mouseMoveArguments, ref handled);
  1148. }
  1149. }
  1150. #endregion
  1151. #region Cursor helper methods
  1152. /// <summary>
  1153. /// Helper function which returns a reference to the chart object
  1154. /// </summary>
  1155. /// <returns>Chart object reference.</returns>
  1156. private Chart GetChartObject()
  1157. {
  1158. if(this._chartArea != null )
  1159. {
  1160. return this._chartArea.Chart;
  1161. }
  1162. return null;
  1163. }
  1164. /// <summary>
  1165. /// Get rectangle of the axis range selection.
  1166. /// </summary>
  1167. /// <returns>Selection rectangle.</returns>
  1168. /// <param name="plotAreaPosition">Plot area rectangle.</param>
  1169. /// <returns></returns>
  1170. private RectangleF GetSelectionRect(RectangleF plotAreaPosition)
  1171. {
  1172. RectangleF rect = RectangleF.Empty;
  1173. if(this._axis != null &&
  1174. this.SelectionStart != this.SelectionEnd)
  1175. {
  1176. double start = (float)this._axis.GetLinearPosition(this.SelectionStart);
  1177. double end = (float)this._axis.GetLinearPosition(this.SelectionEnd);
  1178. // Detect if cursor is horizontal or vertical
  1179. bool horizontal = true;
  1180. if(this.GetAxis().AxisPosition == AxisPosition.Bottom || this.GetAxis().AxisPosition == AxisPosition.Top)
  1181. {
  1182. horizontal = false;
  1183. }
  1184. if(horizontal)
  1185. {
  1186. rect.X = plotAreaPosition.X;
  1187. rect.Width = plotAreaPosition.Width;
  1188. rect.Y = (float)Math.Min(start, end);
  1189. rect.Height = (float)Math.Max(start, end) - rect.Y;
  1190. }
  1191. else
  1192. {
  1193. rect.Y = plotAreaPosition.Y;
  1194. rect.Height = plotAreaPosition.Height;
  1195. rect.X = (float)Math.Min(start, end);
  1196. rect.Width = (float)Math.Max(start, end) - rect.X;
  1197. }
  1198. }
  1199. return rect;
  1200. }
  1201. /// <summary>
  1202. /// Get rectangle of the opposite axis selection
  1203. /// </summary>
  1204. /// <param name="plotAreaPosition">Plot area rectangle.</param>
  1205. /// <returns>Opposite selection rectangle.</returns>
  1206. private RectangleF GetOppositeSelectionRect(RectangleF plotAreaPosition)
  1207. {
  1208. if(_chartArea != null)
  1209. {
  1210. // Get opposite cursor
  1211. Cursor oppositeCursor =
  1212. (_attachedToXAxis == AxisName.X || _attachedToXAxis == AxisName.X2) ?
  1213. _chartArea.CursorY : _chartArea.CursorX;
  1214. return oppositeCursor.GetSelectionRect(plotAreaPosition);
  1215. }
  1216. return RectangleF.Empty;
  1217. }
  1218. /// <summary>
  1219. /// Converts X or Y position value to the cursor axis value
  1220. /// </summary>
  1221. /// <param name="position">Position in relative coordinates.</param>
  1222. /// <returns>Cursor position as axis value.</returns>
  1223. private double PositionToCursorPosition(PointF position)
  1224. {
  1225. // Detect if cursor is horizontal or vertical
  1226. bool horizontal = true;
  1227. if(this.GetAxis().AxisPosition == AxisPosition.Bottom || this.GetAxis().AxisPosition == AxisPosition.Top)
  1228. {
  1229. horizontal = false;
  1230. }
  1231. // Convert relative coordinates into axis values
  1232. double newCursorPosition = double.NaN;
  1233. if(horizontal)
  1234. {
  1235. newCursorPosition = this.GetAxis().PositionToValue(position.Y);
  1236. }
  1237. else
  1238. {
  1239. newCursorPosition = this.GetAxis().PositionToValue(position.X);
  1240. }
  1241. // Round new position using Step & StepType properties
  1242. newCursorPosition = RoundPosition(newCursorPosition);
  1243. return newCursorPosition;
  1244. }
  1245. /// <summary>
  1246. /// Checks if specified point is located inside the plotting area.
  1247. /// Converts pixel coordinates to relative.
  1248. /// </summary>
  1249. /// <param name="point">Point coordinates to test.</param>
  1250. /// <param name="roundToBoundary">Indicates that coordinates must be rounded to area boundary.</param>
  1251. /// <returns>PointF.IsEmpty or relative coordinates in plotting area.</returns>
  1252. private PointF GetPositionInPlotArea(PointF point, bool roundToBoundary)
  1253. {
  1254. PointF result = PointF.Empty;
  1255. if(this._chartArea != null && this._chartArea.Common != null && this.GetAxis()!= null)
  1256. {
  1257. // Translate mouse pixel coordinates into the relative chart area coordinates
  1258. result.X = point.X * 100F / ((float)(this._chartArea.Common.Width - 1));
  1259. result.Y = point.Y * 100F / ((float)(this._chartArea.Common.Height - 1));
  1260. // Round coordinate if it' outside chart plotting area
  1261. RectangleF plotAreaPosition = this._chartArea.PlotAreaPosition.ToRectangleF();
  1262. if(roundToBoundary)
  1263. {
  1264. if(result.X < plotAreaPosition.X)
  1265. {
  1266. result.X = plotAreaPosition.X;
  1267. }
  1268. if(result.X > plotAreaPosition.Right)
  1269. {
  1270. result.X = plotAreaPosition.Right;
  1271. }
  1272. if(result.Y < plotAreaPosition.Y)
  1273. {
  1274. result.Y = plotAreaPosition.Y;
  1275. }
  1276. if(result.Y > plotAreaPosition.Bottom)
  1277. {
  1278. result.Y = plotAreaPosition.Bottom;
  1279. }
  1280. }
  1281. else
  1282. {
  1283. // Check if coordinate is inside chart plotting area
  1284. if(result.X < plotAreaPosition.X ||
  1285. result.X > plotAreaPosition.Right ||
  1286. result.Y < plotAreaPosition.Y ||
  1287. result.Y > plotAreaPosition.Bottom)
  1288. {
  1289. result = PointF.Empty;
  1290. }
  1291. }
  1292. }
  1293. return result;
  1294. }
  1295. /// <summary>
  1296. /// Invalidate chart are with the cursor.
  1297. /// </summary>
  1298. /// <param name="invalidateArea">Chart area must be invalidated.</param>
  1299. private void Invalidate(bool invalidateArea)
  1300. {
  1301. if(this.GetChartObject() != null && this._chartArea != null && !this.GetChartObject().disableInvalidates)
  1302. {
  1303. // If data scaleView was scrolled - just invalidate the chart area
  1304. if(_viewScrolledOnMouseMove || invalidateArea || this.GetChartObject().dirtyFlag)
  1305. {
  1306. this._chartArea.Invalidate();
  1307. }
  1308. // If only cursor/selection position was changed - use optimized drawing algorithm
  1309. else
  1310. {
  1311. // Set flag to redraw cursor/selection only
  1312. this.GetChartObject().paintTopLevelElementOnly = true;
  1313. // Invalidate and update the chart
  1314. this._chartArea.Invalidate();
  1315. this.GetChartObject().Update();
  1316. // Clear flag to redraw cursor/selection only
  1317. this.GetChartObject().paintTopLevelElementOnly = false;
  1318. }
  1319. }
  1320. }
  1321. /// <summary>
  1322. /// Gets axis objects the cursor is attached to.
  1323. /// </summary>
  1324. /// <returns>Axis object.</returns>
  1325. internal Axis GetAxis()
  1326. {
  1327. if(_axis == null && _chartArea != null)
  1328. {
  1329. if(_attachedToXAxis == AxisName.X)
  1330. {
  1331. _axis = (_axisType == AxisType.Primary) ? _chartArea.AxisX : _chartArea.AxisX2;
  1332. }
  1333. else
  1334. {
  1335. _axis = (_axisType == AxisType.Primary) ? _chartArea.AxisY : _chartArea.AxisY2;
  1336. }
  1337. }
  1338. return _axis;
  1339. }
  1340. #endregion
  1341. #region IDisposable Members
  1342. /// <summary>
  1343. /// Releases unmanaged and - optionally - managed resources
  1344. /// </summary>
  1345. /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
  1346. protected virtual void Dispose(bool disposing)
  1347. {
  1348. if (disposing)
  1349. {
  1350. // Dispose managed resources
  1351. if (this._scrollTimer != null)
  1352. {
  1353. this._scrollTimer.Dispose();
  1354. this._scrollTimer = null;
  1355. }
  1356. }
  1357. }
  1358. /// <summary>
  1359. /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
  1360. /// </summary>
  1361. public void Dispose()
  1362. {
  1363. Dispose(true);
  1364. GC.SuppressFinalize(this);
  1365. }
  1366. #endregion
  1367. }
  1368. /// <summary>
  1369. /// The CursorEventArgs class stores the event arguments for cursor and selection events.
  1370. /// </summary>
  1371. public class CursorEventArgs : EventArgs
  1372. {
  1373. #region Private fields
  1374. // Private fields for properties values storage
  1375. private ChartArea _chartArea = null;
  1376. private Axis _axis = null;
  1377. private double _newPosition = double.NaN;
  1378. private double _newSelectionStart = double.NaN;
  1379. private double _newSelectionEnd = double.NaN;
  1380. #endregion
  1381. #region Constructors
  1382. /// <summary>
  1383. /// CursorEventArgs constructor.
  1384. /// </summary>
  1385. /// <param name="chartArea">ChartArea of the cursor.</param>
  1386. /// <param name="axis">Axis of the cursor.</param>
  1387. /// <param name="newPosition">New cursor position.</param>
  1388. public CursorEventArgs(ChartArea chartArea, Axis axis, double newPosition)
  1389. {
  1390. this._chartArea = chartArea;
  1391. this._axis = axis;
  1392. this._newPosition = newPosition;
  1393. this._newSelectionStart = double.NaN;
  1394. this._newSelectionEnd = double.NaN;
  1395. }
  1396. /// <summary>
  1397. /// CursorEventArgs constructor.
  1398. /// </summary>
  1399. /// <param name="chartArea">ChartArea of the cursor.</param>
  1400. /// <param name="axis">Axis of the cursor.</param>
  1401. /// <param name="newSelectionStart">New range selection starting position.</param>
  1402. /// <param name="newSelectionEnd">New range selection ending position.</param>
  1403. public CursorEventArgs(ChartArea chartArea, Axis axis, double newSelectionStart, double newSelectionEnd)
  1404. {
  1405. this._chartArea = chartArea;
  1406. this._axis = axis;
  1407. this._newPosition = double.NaN;
  1408. this._newSelectionStart = newSelectionStart;
  1409. this._newSelectionEnd = newSelectionEnd;
  1410. }
  1411. #endregion
  1412. #region Properties
  1413. /// <summary>
  1414. /// ChartArea of the event.
  1415. /// </summary>
  1416. [
  1417. SRDescription("DescriptionAttributeChartArea"),
  1418. ]
  1419. public ChartArea ChartArea
  1420. {
  1421. get
  1422. {
  1423. return _chartArea;
  1424. }
  1425. }
  1426. /// <summary>
  1427. /// Axis of the event.
  1428. /// </summary>
  1429. [
  1430. SRDescription("DescriptionAttributeAxis"),
  1431. ]
  1432. public Axis Axis
  1433. {
  1434. get
  1435. {
  1436. return _axis;
  1437. }
  1438. }
  1439. /// <summary>
  1440. /// New cursor position.
  1441. /// </summary>
  1442. [
  1443. SRDescription("DescriptionAttributeCursorEventArgs_NewPosition"),
  1444. ]
  1445. public double NewPosition
  1446. {
  1447. get
  1448. {
  1449. return _newPosition;
  1450. }
  1451. set
  1452. {
  1453. _newPosition = value;
  1454. }
  1455. }
  1456. /// <summary>
  1457. /// New range selection starting position.
  1458. /// </summary>
  1459. [
  1460. SRDescription("DescriptionAttributeCursorEventArgs_NewSelectionStart"),
  1461. ]
  1462. public double NewSelectionStart
  1463. {
  1464. get
  1465. {
  1466. return _newSelectionStart;
  1467. }
  1468. set
  1469. {
  1470. _newSelectionStart = value;
  1471. }
  1472. }
  1473. /// <summary>
  1474. /// New range selection ending position.
  1475. /// </summary>
  1476. [
  1477. SRDescription("DescriptionAttributeCursorEventArgs_NewSelectionEnd"),
  1478. ]
  1479. public double NewSelectionEnd
  1480. {
  1481. get
  1482. {
  1483. return _newSelectionEnd;
  1484. }
  1485. set
  1486. {
  1487. _newSelectionEnd = value;
  1488. }
  1489. }
  1490. #endregion
  1491. }
  1492. }