SubAxis.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757
  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: Each chart area contains four main axes PrimaryX,
  6. // PrimaryY, SecondaryX and SecondaryY which are usually
  7. // positioned on each side of the plotting area. Most of
  8. // the charts use only two axes; X and Y, but for some
  9. // charts even 4 axes is not sufficient. Sub-axes were
  10. // introduced to proSUBACESvide unlimited number of axes in
  11. // the chart.
  12. // Each main axis has a collection of SubAxis which is
  13. // empty by default. By adding SubAxis into this collection
  14. // user can add unlimited number of sub-axis which will
  15. // be positioned next to the main axis.
  16. // Each of the SubAxis have a unique name. To associate
  17. // data series with a sub axis YSubAxisName and XSubAxisName
  18. // properties of the Series should be used.
  19. //
  20. #if SUBAXES
  21. using System;
  22. using System.Globalization;
  23. using System.Reflection;
  24. using System.Collections;
  25. using System.Collections.Specialized;
  26. using System.ComponentModel;
  27. using System.ComponentModel.Design;
  28. using System.ComponentModel.Design.Serialization;
  29. using System.Data;
  30. using System.Drawing;
  31. using System.Drawing.Design;
  32. using System.Drawing.Text;
  33. using System.Drawing.Drawing2D;
  34. using FastReport.DataVisualization.Charting;
  35. using FastReport.DataVisualization.Charting.Data;
  36. using FastReport.DataVisualization.Charting.ChartTypes;
  37. using FastReport.DataVisualization.Charting.Utilities;
  38. using FastReport.DataVisualization.Charting.Borders3D;
  39. using FastReport.DataVisualization.Charting;
  40. namespace FastReport.DataVisualization.Charting
  41. {
  42. /// <summary>
  43. /// SubAxis class is derived from the main Axis class and provides
  44. /// additional axis associated with one of the main chart axis.
  45. /// </summary>
  46. [
  47. SRDescription("DescriptionAttributeSubAxis_SubAxis"),
  48. DefaultProperty("Enabled"),
  49. TypeConverter(typeof(SubAxis.SubAxisConverter)),
  50. ]
  51. public class SubAxis : Axis
  52. {
  53. #region Fields
  54. /// <summary>
  55. /// Sub-Axis parent axis object.
  56. /// </summary>
  57. internal Axis parentAxis = null;
  58. /// <summary>
  59. /// Sub axis offset from the parent axis
  60. /// </summary>
  61. internal double offsetFromParent = 0.0;
  62. /// <summary>
  63. /// Margin between prev. axis
  64. /// </summary>
  65. internal double locationOffset = 0.0;
  66. #endregion // Fields
  67. #region Constructor
  68. /// <summary>
  69. /// Default constructor
  70. /// </summary>
  71. public SubAxis() : base()
  72. {
  73. base.Name = string.Empty;
  74. }
  75. /// <summary>
  76. /// Object constructor.
  77. /// </summary>
  78. /// <param name="name">Unique name of the object.</param>
  79. public SubAxis(string name) : base()
  80. {
  81. base.Name = name;
  82. }
  83. #endregion
  84. #region Properties
  85. /// <summary>
  86. /// Axis automatic scale breaks style.
  87. /// </summary>
  88. [
  89. Browsable(false),
  90. EditorBrowsable(EditorBrowsableState.Never),
  91. SRCategory("CategoryAttributeScale"),
  92. SRDescription("DescriptionAttributeScaleBreakStyle"),
  93. TypeConverter(typeof(NoNameExpandableObjectConverter)),
  94. NotifyParentPropertyAttribute(true),
  95. DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
  96. ]
  97. override public AxisScaleBreakStyle ScaleBreakStyle
  98. {
  99. get
  100. {
  101. return base.ScaleBreakStyle;
  102. }
  103. set
  104. {
  105. base.ScaleBreakStyle = value;
  106. }
  107. }
  108. /// <summary>
  109. /// Sub axis parent axis.
  110. /// </summary>
  111. [
  112. SRCategory("CategoryAttributeAxis"),
  113. Bindable(true),
  114. Browsable(false),
  115. DefaultValue(null),
  116. NotifyParentPropertyAttribute(true),
  117. SRDescription("DescriptionAttributeSubAxis_ParentAxis"),
  118. DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Hidden),
  119. SerializationVisibilityAttribute(SerializationVisibility.Hidden)
  120. ]
  121. public Axis ParentAxis
  122. {
  123. get
  124. {
  125. return this.parentAxis;
  126. }
  127. }
  128. /// <summary>
  129. /// Sub axis location offset relative to the previous axis.
  130. /// </summary>
  131. [
  132. SRCategory("CategoryAttributeLocation"),
  133. Bindable(true),
  134. DefaultValue(0.0),
  135. NotifyParentPropertyAttribute(true),
  136. SRDescription("DescriptionAttributeSubAxis_LocationOffset"),
  137. ]
  138. public double LocationOffset
  139. {
  140. get
  141. {
  142. return this.locationOffset;
  143. }
  144. set
  145. {
  146. this.locationOffset = value;
  147. this.Invalidate();
  148. }
  149. }
  150. /// <summary>
  151. /// Axis position
  152. /// </summary>
  153. [
  154. Bindable(true),
  155. Browsable(false),
  156. DefaultValue(AxisPosition.Left),
  157. NotifyParentPropertyAttribute(true),
  158. SRDescription("DescriptionAttributeReverse"),
  159. DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Hidden),
  160. SerializationVisibilityAttribute(SerializationVisibility.Hidden)
  161. ]
  162. override internal AxisPosition AxisPosition
  163. {
  164. get
  165. {
  166. if(this.parentAxis != null)
  167. {
  168. return this.parentAxis.AxisPosition;
  169. }
  170. return AxisPosition.Left;
  171. }
  172. set
  173. {
  174. }
  175. }
  176. /// <summary>
  177. /// SubAxis name.
  178. /// </summary>
  179. [
  180. SRCategory("CategoryAttributeAppearance"),
  181. Bindable(true),
  182. Browsable(true),
  183. DefaultValue(""),
  184. SRDescription("DescriptionAttributeSubAxis_Name"),
  185. DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Visible),
  186. SerializationVisibilityAttribute(SerializationVisibility.Attribute)
  187. ]
  188. override public string Name
  189. {
  190. get
  191. {
  192. return base.Name;
  193. }
  194. set
  195. {
  196. base.Name = value;
  197. }
  198. }
  199. /// <summary>
  200. /// Tick marks and labels move with axis when
  201. /// the crossing value is changed.
  202. /// </summary>
  203. [
  204. SRCategory("CategoryAttributeAppearance"),
  205. Browsable(false),
  206. EditorBrowsable(EditorBrowsableState.Never),
  207. Bindable(true),
  208. DefaultValue(true),
  209. SRDescription("DescriptionAttributeMarksNextToAxis"),
  210. NotifyParentPropertyAttribute(true),
  211. DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Hidden),
  212. SerializationVisibilityAttribute(SerializationVisibility.Hidden)
  213. ]
  214. override public bool IsMarksNextToAxis
  215. {
  216. get
  217. {
  218. return base.IsMarksNextToAxis;
  219. }
  220. set
  221. {
  222. base.IsMarksNextToAxis = value;
  223. }
  224. }
  225. /// <summary>
  226. /// Point where axis is crossed by another axis.
  227. /// </summary>
  228. [
  229. SRCategory("CategoryAttributeScale"),
  230. Browsable(false),
  231. EditorBrowsable(EditorBrowsableState.Never),
  232. Bindable(true),
  233. DefaultValue(Double.NaN),
  234. NotifyParentPropertyAttribute(true),
  235. SRDescription("DescriptionAttributeCrossing"),
  236. TypeConverter(typeof(AxisCrossingValueConverter)),
  237. DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Hidden),
  238. SerializationVisibilityAttribute(SerializationVisibility.Hidden)
  239. ]
  240. override public double Crossing
  241. {
  242. get
  243. {
  244. return base.Crossing;
  245. }
  246. set
  247. {
  248. base.Crossing = value;
  249. }
  250. }
  251. /// <summary>
  252. /// Sub-axes collection.
  253. /// </summary>
  254. [
  255. SRCategory("CategoryAttributeSubAxes"),
  256. Browsable(false),
  257. EditorBrowsable(EditorBrowsableState.Never),
  258. Bindable(true),
  259. SRDescription("DescriptionAttributeSubAxes"),
  260. Editor(Editors.ChartCollectionEditor.Editor, Editors.ChartCollectionEditor.Base),
  261. DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Hidden),
  262. SerializationVisibilityAttribute(SerializationVisibility.Hidden),
  263. ]
  264. override public SubAxisCollection SubAxes
  265. {
  266. get
  267. {
  268. return base.SubAxes;
  269. }
  270. }
  271. /// <summary>
  272. /// Indicates if this axis object present the main or sub axis.
  273. /// </summary>
  274. override internal bool IsSubAxis
  275. {
  276. get
  277. {
  278. return true;
  279. }
  280. }
  281. /// <summary>
  282. /// Returns sub-axis name.
  283. /// </summary>
  284. override internal string SubAxisName
  285. {
  286. get
  287. {
  288. return base.Name;
  289. }
  290. }
  291. #endregion // Properties
  292. #region Methods
  293. /// <summary>
  294. /// Find axis position using crossing value.
  295. /// </summary>
  296. /// <param name="ignoreCrossing">Axis crossing should be ignored.</param>
  297. /// <returns>Relative position</returns>
  298. override internal double GetAxisPosition(bool ignoreCrossing)
  299. {
  300. // Parent axis must be set
  301. if(this.parentAxis != null)
  302. {
  303. // Get position of the parent axis
  304. double position = this.parentAxis.GetAxisPosition(ignoreCrossing);
  305. // Addjust parent position by the offset
  306. if(this.parentAxis.AxisPosition == AxisPosition.Left)
  307. {
  308. position -= this.offsetFromParent;
  309. }
  310. else if(this.parentAxis.AxisPosition == AxisPosition.Right)
  311. {
  312. position += this.offsetFromParent;
  313. }
  314. else if(this.parentAxis.AxisPosition == AxisPosition.Top)
  315. {
  316. position -= this.offsetFromParent;
  317. }
  318. else if(this.parentAxis.AxisPosition == AxisPosition.Bottom)
  319. {
  320. position += this.offsetFromParent;
  321. }
  322. return position;
  323. }
  324. return 0.0;
  325. }
  326. #endregion // Methods
  327. #region Type converter
  328. #if WINFORMS_CONTROL
  329. internal class SubAxisConverter : TypeConverter
  330. {
  331. /// <summary>
  332. /// This method overrides CanConvertTo from TypeConverter. This is called when someone
  333. /// wants to convert an instance of object to another type. Here,
  334. /// only conversion to an InstanceDescriptor is supported.
  335. /// </summary>
  336. /// <param name="context">Descriptor context.</param>
  337. /// <param name="destinationType">Destination type.</param>
  338. /// <returns>True if object can be converted.</returns>
  339. public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
  340. {
  341. if (destinationType == typeof(InstanceDescriptor))
  342. {
  343. return true;
  344. }
  345. // Always call the base to see if it can perform the conversion.
  346. return base.CanConvertTo(context, destinationType);
  347. }
  348. /// <summary>
  349. /// This code performs the actual conversion from an object to an InstanceDescriptor.
  350. /// </summary>
  351. /// <param name="context">Descriptor context.</param>
  352. /// <param name="culture">Culture information.</param>
  353. /// <param name="value">Object value.</param>
  354. /// <param name="destinationType">Destination type.</param>
  355. /// <returns>Converted object.</returns>
  356. public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
  357. {
  358. if (destinationType == typeof(InstanceDescriptor))
  359. {
  360. ConstructorInfo ci = typeof(SubAxis).GetConstructor(System.Type.EmptyTypes);
  361. return new InstanceDescriptor(ci, null, false);
  362. }
  363. // Always call base, even if you can't convert.
  364. return base.ConvertTo(context, culture, value, destinationType);
  365. }
  366. }
  367. #endif //#if WINFORMS_CONTROL
  368. #endregion
  369. }
  370. /// <summary>
  371. /// <b>SubAxisCollection</b> is a strongly typed collection of chart sub-axes objects.
  372. /// Collection indexer can accept sub-axis index or it's unique name as a parameter.
  373. /// </summary>
  374. [
  375. SRDescription("DescriptionAttributeSubAxisCollection_SubAxisCollection"),
  376. ]
  377. public class SubAxisCollection : CollectionBase
  378. {
  379. #region Fields
  380. /// <summary>
  381. /// Sub-Axis parent axis object.
  382. /// </summary>
  383. internal Axis parentAxis = null;
  384. #endregion
  385. #region Construction and Initialization
  386. /// <summary>
  387. /// Default public constructor.
  388. /// </summary>
  389. /// <remarks>
  390. /// This constructor is for internal use and should not be part of documentation.
  391. /// </remarks>
  392. public SubAxisCollection()
  393. {
  394. this.parentAxis = null;
  395. }
  396. /// <summary>
  397. /// Public constructor.
  398. /// </summary>
  399. /// <param name="parentAxis">
  400. /// Chart <see cref="Axis"/> object.
  401. /// </param>
  402. /// <remarks>
  403. /// This constructor is for the internal use and should not be part of documentation.
  404. /// </remarks>
  405. internal SubAxisCollection(Axis parentAxis)
  406. {
  407. this.parentAxis = parentAxis;
  408. }
  409. #endregion
  410. #region Indexer
  411. /// <summary>
  412. /// SubAxis collection indexer.
  413. /// </summary>
  414. /// <remarks>
  415. /// The <b>SubAxis</b> object's name or index can be provided as a parameter. Returns the <see cref="SubAxis"/> object.
  416. /// Make sure to cast the SubAxis to it's type (e.g. LineSubAxis) to access type
  417. /// specific properties.
  418. /// </remarks>
  419. [
  420. SRDescription("DescriptionAttributeSubAxisCollection_Item"),
  421. ]
  422. public SubAxis this[object parameter]
  423. {
  424. get
  425. {
  426. // Get SubAxis by index
  427. if(parameter is int)
  428. {
  429. return (SubAxis)this.List[(int)parameter];
  430. }
  431. // Get SubAxis by name
  432. else if(parameter is string)
  433. {
  434. // Find SubAxis with specified name
  435. foreach(SubAxis SubAxis in this.List)
  436. {
  437. if(SubAxis.Name == (string)parameter)
  438. {
  439. return SubAxis;
  440. }
  441. }
  442. // SubAxis with specified name was not found
  443. throw(new ArgumentException( SR.ExceptionSubAxisNameNotFound( (string)parameter ) ) );
  444. }
  445. // Invalid type of the indexer argument
  446. throw(new ArgumentException(SR.ExceptionInvalidIndexerArgumentType));
  447. }
  448. set
  449. {
  450. // Check new SubAxis name
  451. int indexSubAxis = -1;
  452. if(value.Name.Length != 0)
  453. {
  454. indexSubAxis = this.List.IndexOf(value);
  455. }
  456. else
  457. {
  458. AssignUniqueName(value);
  459. }
  460. // Set using index in the collection
  461. if(parameter is int)
  462. {
  463. // Check if SubAxis with this name already exists
  464. if( indexSubAxis != -1 && indexSubAxis != (int)parameter)
  465. {
  466. throw( new ArgumentException( SR.ExceptionSubAxisNameAlreadyExistsInCollection( value.Name ) ) );
  467. }
  468. this.List[(int)parameter] = value;
  469. }
  470. // Set using name in the collection
  471. else if(parameter is string)
  472. {
  473. // Find legend with specified name
  474. int index = 0;
  475. foreach(SubAxis SubAxis in this.List)
  476. {
  477. if(SubAxis.Name == (string)parameter)
  478. {
  479. // Check if SubAxis with this name already exists
  480. if( indexSubAxis != -1 && indexSubAxis != index)
  481. {
  482. throw( new ArgumentException( SR.ExceptionSubAxisNameAlreadyExistsInCollection( value.Name ) ) );
  483. }
  484. this.List[index] = value;
  485. break;
  486. }
  487. ++index;
  488. }
  489. }
  490. else
  491. {
  492. throw(new ArgumentException(SR.ExceptionInvalidIndexerArgumentType));
  493. }
  494. this.Invalidate();
  495. }
  496. }
  497. #endregion
  498. #region Collection Add and Insert methods
  499. /// <summary>
  500. /// Removes the SubAxis with the specified name from the collection.
  501. /// </summary>
  502. /// <param name="name">
  503. /// Name of the SubAxis to be removed.
  504. /// </param>
  505. public void Remove(string name)
  506. {
  507. SubAxis axis = FindByName(name);
  508. if(axis != null)
  509. {
  510. this.List.Remove(axis);
  511. }
  512. }
  513. /// <summary>
  514. /// Removes the given SubAxis from the collection.
  515. /// </summary>
  516. /// <param name="SubAxis">
  517. /// <see cref="SubAxis"/> object to be removed.
  518. /// </param>
  519. public void Remove(SubAxis SubAxis)
  520. {
  521. if(SubAxis != null)
  522. {
  523. this.List.Remove(SubAxis);
  524. }
  525. }
  526. /// <summary>
  527. /// Adds a SubAxis to the end of the collection.
  528. /// </summary>
  529. /// <param name="SubAxis">
  530. /// <see cref="SubAxis"/> object to add.
  531. /// </param>
  532. /// <returns>
  533. /// Index of the newly added object.
  534. /// </returns>
  535. public int Add(SubAxis SubAxis)
  536. {
  537. return this.List.Add(SubAxis);
  538. }
  539. /// <summary>
  540. /// Inserts a SubAxis into the collection.
  541. /// </summary>
  542. /// <param name="index">
  543. /// Index to insert the object at.
  544. /// </param>
  545. /// <param name="SubAxis">
  546. /// <see cref="SubAxis"/> object to insert.
  547. /// </param>
  548. public void Insert(int index, SubAxis SubAxis)
  549. {
  550. this.List.Insert(index, SubAxis);
  551. }
  552. #endregion
  553. #region Items Inserting and Removing Notification methods
  554. /// <summary>
  555. /// Called before the new item is inserted.
  556. /// </summary>
  557. /// <param name="index">Item index.</param>
  558. /// <param name="value">Item object.</param>
  559. /// <remarks>
  560. /// This is an internal method and should not be part of the documentation.
  561. /// </remarks>
  562. protected override void OnInsert(int index, object value)
  563. {
  564. // Check SubAxis object name
  565. if( ((SubAxis)value).Name.Length == 0 )
  566. {
  567. AssignUniqueName((SubAxis)value);
  568. }
  569. else
  570. {
  571. if(this.FindByName(((SubAxis)value).Name) != null)
  572. {
  573. throw(new InvalidOperationException(SR.ExceptionSubAxisNameIsNotUnique( ((SubAxis)value).Name )));
  574. }
  575. }
  576. }
  577. /// <summary>
  578. /// After new item inserted.
  579. /// </summary>
  580. /// <param name="index">Item index.</param>
  581. /// <param name="value">Item object.</param>
  582. /// <remarks>
  583. /// This is an internal method and should not be part of the documentation.
  584. /// </remarks>
  585. protected override void OnInsertComplete(int index, object value)
  586. {
  587. // Set SubAxis parent axis reference
  588. SubAxis subAxis = (SubAxis)value;
  589. subAxis.parentAxis = this.parentAxis;
  590. if(this.parentAxis != null)
  591. {
  592. subAxis.chart = this.parentAxis.chart;
  593. subAxis.Common = this.parentAxis.Common;
  594. subAxis.chartArea = this.parentAxis.chartArea;
  595. subAxis.axisType= this.parentAxis.axisType;
  596. subAxis.AxisPosition= this.parentAxis.AxisPosition;
  597. }
  598. this.Invalidate();
  599. }
  600. /// <summary>
  601. /// After item removed.
  602. /// </summary>
  603. /// <param name="index">Item index.</param>
  604. /// <param name="value">Item object.</param>
  605. /// <remarks>
  606. /// This is an internal method and should not be part of the documentation.
  607. /// </remarks>
  608. protected override void OnRemoveComplete(int index, object value)
  609. {
  610. // Reset SubAxis parent axis reference
  611. ((SubAxis)value).parentAxis = null;
  612. this.Invalidate();
  613. }
  614. /// <summary>
  615. /// After all items removed.
  616. /// </summary>
  617. /// <remarks>
  618. /// This is an internal method and should not be part of the documentation.
  619. /// </remarks>
  620. protected override void OnClearComplete()
  621. {
  622. this.Invalidate();
  623. }
  624. #endregion
  625. #region Helper Methods
  626. /// <summary>
  627. /// Invalidates chart the collection belongs to.
  628. /// </summary>
  629. private void Invalidate()
  630. {
  631. #if WINFORMS_CONTROL
  632. if(this.parentAxis != null && this.parentAxis.chart != null)
  633. {
  634. this.parentAxis.chart.dirtyFlag = true;
  635. this.parentAxis.chart.Invalidate();
  636. }
  637. #endif
  638. }
  639. /// <summary>
  640. /// Assigns a unique name to the SubAxis object based on it's type.
  641. /// </summary>
  642. /// <param name="SubAxis">SubAxis object to be named.</param>
  643. internal void AssignUniqueName(SubAxis SubAxis)
  644. {
  645. // Generate name using SubAxis type name and unique index
  646. string name = string.Empty;
  647. int index = 1;
  648. do
  649. {
  650. name = "SubAxis" + index.ToString();
  651. ++index;
  652. } while(this.FindByName(name) != null && index < 10000 );
  653. // Asign unique name;
  654. SubAxis.Name = name;
  655. }
  656. /// <summary>
  657. /// Finds SubAxis by name.
  658. /// </summary>
  659. /// <param name="name">Name of the chart SubAxis.</param>
  660. /// <returns>SubAxis or null if it does not exist.</returns>
  661. internal SubAxis FindByName(string name)
  662. {
  663. SubAxis result = null;
  664. for(int index = 0; index < this.List.Count; index ++)
  665. {
  666. // Compare SubAxis name
  667. if(String.Compare(this[index].Name, name, true, System.Globalization.CultureInfo.CurrentCulture) == 0)
  668. {
  669. result = this[index];
  670. break;
  671. }
  672. }
  673. return result;
  674. }
  675. #endregion
  676. }
  677. }
  678. #endif // SUBAXES