SmartLabels.cs 62 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667
  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: Smart Labels are used to avoid data point's labels
  6. // overlapping. SmartLabelStyle class is exposed from
  7. // the Series and Annotation classes and allows enabling
  8. // and adjusting of SmartLabelStyle algorithm. SmartLabelStyle class
  9. // exposes a set of helper utility methods and store
  10. // information about labels in a chart area.
  11. //
  12. using System;
  13. using System.Collections;
  14. using System.ComponentModel;
  15. using System.Drawing;
  16. using System.Drawing.Design;
  17. using System.Drawing.Drawing2D;
  18. using FastReport.DataVisualization.Charting.ChartTypes;
  19. namespace FastReport.DataVisualization.Charting
  20. {
  21. #region Enumerations
  22. /// <summary>
  23. /// Line anchor cap style.
  24. /// </summary>
  25. [
  26. SRDescription("DescriptionAttributeLineAnchorCapStyle_LineAnchorCapStyle")
  27. ]
  28. public enum LineAnchorCapStyle
  29. {
  30. /// <summary>
  31. /// No line anchor cap.
  32. /// </summary>
  33. None,
  34. /// <summary>
  35. /// Arrow line anchor cap.
  36. /// </summary>
  37. Arrow,
  38. /// <summary>
  39. /// Diamond line anchor cap.
  40. /// </summary>
  41. Diamond,
  42. /// <summary>
  43. /// Square line anchor cap.
  44. /// </summary>
  45. Square,
  46. /// <summary>
  47. /// Round line anchor cap.
  48. /// </summary>
  49. Round
  50. }
  51. /// <summary>
  52. /// Data point label callout style.
  53. /// </summary>
  54. [
  55. SRDescription("DescriptionAttributeLabelCalloutStyle_LabelCalloutStyle")
  56. ]
  57. public enum LabelCalloutStyle
  58. {
  59. /// <summary>
  60. /// Label connected with the marker using just a line.
  61. /// </summary>
  62. None,
  63. /// <summary>
  64. /// Label is undelined and connected with the marker using a line.
  65. /// </summary>
  66. Underlined,
  67. /// <summary>
  68. /// Box is drawn around the label and it's connected with the marker using a line.
  69. /// </summary>
  70. Box
  71. }
  72. /// <summary>
  73. /// Data point label outside of the plotting area style.
  74. /// </summary>
  75. [
  76. SRDescription("DescriptionAttributeLabelOutsidePlotAreaStyle_LabelOutsidePlotAreaStyle")
  77. ]
  78. public enum LabelOutsidePlotAreaStyle
  79. {
  80. /// <summary>
  81. /// Labels can be positioned outside of the plotting area.
  82. /// </summary>
  83. Yes,
  84. /// <summary>
  85. /// Labels can not be positioned outside of the plotting area.
  86. /// </summary>
  87. No,
  88. /// <summary>
  89. /// Labels can be partially outside of the plotting area.
  90. /// </summary>
  91. Partial
  92. }
  93. #endregion
  94. /// <summary>
  95. /// SmartLabelStyle class is used to enable and configure the
  96. /// SmartLabelStyle algorithm for data point labels and annotations.
  97. /// In most of the cases it is enough just to enable the algorithm,
  98. /// but this class also contains properties which allow controlling
  99. /// how the labels are moved around to avoid collisions. Visual
  100. /// appearance of callouts can also be set through this class.
  101. /// </summary>
  102. [
  103. DefaultProperty("Enabled"),
  104. SRDescription("DescriptionAttributeSmartLabelsStyle_SmartLabelsStyle"),
  105. TypeConverter(typeof(NoNameExpandableObjectConverter))
  106. ]
  107. public class SmartLabelStyle
  108. {
  109. #region Fields
  110. // Reference to the series this style belongs to
  111. internal object chartElement = null;
  112. // Indicates if SmartLabelStyle algorithm is enabled.
  113. private bool _enabled = true;
  114. // Indicates that marker overlapping by label is allowed.
  115. private bool _isMarkerOverlappingAllowed = false;
  116. // Indicates that overlapped labels that can't be repositioned will be hidden.
  117. private bool _isOverlappedHidden = true;
  118. // Possible moving directions for the overlapped SmartLabelStyle.
  119. private LabelAlignmentStyles _movingDirection = LabelAlignmentStyles.Top | LabelAlignmentStyles.Bottom | LabelAlignmentStyles.Right | LabelAlignmentStyles.Left | LabelAlignmentStyles.TopLeft | LabelAlignmentStyles.TopRight | LabelAlignmentStyles.BottomLeft | LabelAlignmentStyles.BottomRight;
  120. // Minimum distance the overlapped SmartLabelStyle can be moved from the marker. Distance is measured in pixels.
  121. private double _minMovingDistance = 0.0;
  122. // Maximum distance the overlapped SmartLabelStyle can be moved from the marker. Distance is measured in pixels.
  123. private double _maxMovingDistance = 30.0;
  124. // Defines if SmartLabelStyle are allowed to be drawn outside of the plotting area.
  125. private LabelOutsidePlotAreaStyle _allowOutsidePlotArea = LabelOutsidePlotAreaStyle.Partial;
  126. // Callout style of the repositioned SmartLabelStyle.
  127. private LabelCalloutStyle _calloutStyle = LabelCalloutStyle.Underlined;
  128. // Label callout line color.
  129. private Color _calloutLineColor = Color.Black;
  130. // Label callout line style.
  131. private ChartDashStyle _calloutLineDashStyle = ChartDashStyle.Solid;
  132. // Label callout back color. Applies to the Box style only!
  133. private Color _calloutBackColor = Color.Transparent;
  134. // Label callout line width.
  135. private int _calloutLineWidth = 1;
  136. // Label callout line anchor cap.
  137. private LineAnchorCapStyle _calloutLineAnchorCapStyle = LineAnchorCapStyle.Arrow;
  138. #endregion
  139. #region Constructors and initialization
  140. /// <summary>
  141. /// Default public constructor.
  142. /// </summary>
  143. public SmartLabelStyle()
  144. {
  145. this.chartElement = null;
  146. }
  147. /// <summary>
  148. /// Constructor.
  149. /// </summary>
  150. /// <param name="chartElement">Chart element this style belongs to.</param>
  151. internal SmartLabelStyle(Object chartElement)
  152. {
  153. this.chartElement = chartElement;
  154. }
  155. #endregion
  156. #region Properties
  157. /// <summary>
  158. /// SmartLabelStyle algorithm enabled flag.
  159. /// </summary>
  160. [
  161. SRCategory("CategoryAttributeMisc"),
  162. Bindable(true),
  163. DefaultValue(true),
  164. SRDescription("DescriptionAttributeEnabled13"),
  165. ParenthesizePropertyNameAttribute(true),
  166. ]
  167. virtual public bool Enabled
  168. {
  169. get
  170. {
  171. return _enabled;
  172. }
  173. set
  174. {
  175. _enabled = value;
  176. Invalidate();
  177. }
  178. }
  179. /// <summary>
  180. /// Indicates that marker overlapping by label is allowed.
  181. /// </summary>
  182. [
  183. SRCategory("CategoryAttributeMisc"),
  184. Bindable(true),
  185. DefaultValue(false),
  186. SRDescription("DescriptionAttributeMarkerOverlapping"),
  187. ]
  188. virtual public bool IsMarkerOverlappingAllowed
  189. {
  190. get
  191. {
  192. return _isMarkerOverlappingAllowed;
  193. }
  194. set
  195. {
  196. _isMarkerOverlappingAllowed = value;
  197. Invalidate();
  198. }
  199. }
  200. /// <summary>
  201. /// Indicates that overlapped labels that can't be repositioned will be hidden.
  202. /// </summary>
  203. [
  204. SRCategory("CategoryAttributeMisc"),
  205. Bindable(true),
  206. DefaultValue(true),
  207. SRDescription("DescriptionAttributeHideOverlapped"),
  208. ]
  209. virtual public bool IsOverlappedHidden
  210. {
  211. get
  212. {
  213. return _isOverlappedHidden;
  214. }
  215. set
  216. {
  217. _isOverlappedHidden = value;
  218. Invalidate();
  219. }
  220. }
  221. /// <summary>
  222. /// Possible moving directions for the overlapped SmartLabelStyle.
  223. /// </summary>
  224. [
  225. SRCategory("CategoryAttributeMisc"),
  226. Bindable(true),
  227. DefaultValue(typeof(LabelAlignmentStyles), "Top, Bottom, Right, Left, TopLeft, TopRight, BottomLeft, BottomRight"),
  228. SRDescription("DescriptionAttributeMovingDirection"),
  229. #if DESIGNER
  230. Editor(typeof(FlagsEnumUITypeEditor), typeof(UITypeEditor))
  231. #endif
  232. ]
  233. virtual public LabelAlignmentStyles MovingDirection
  234. {
  235. get
  236. {
  237. return _movingDirection;
  238. }
  239. set
  240. {
  241. if (value == 0)
  242. {
  243. throw (new InvalidOperationException(SR.ExceptionSmartLabelsDirectionUndefined));
  244. }
  245. _movingDirection = value;
  246. Invalidate();
  247. }
  248. }
  249. /// <summary>
  250. /// Minimum distance the overlapped SmartLabelStyle can be moved from the marker. Distance is measured in pixels.
  251. /// </summary>
  252. [
  253. SRCategory("CategoryAttributeMisc"),
  254. Bindable(true),
  255. DefaultValue(0.0),
  256. SRDescription("DescriptionAttributeMinMovingDistance"),
  257. ]
  258. virtual public double MinMovingDistance
  259. {
  260. get
  261. {
  262. return _minMovingDistance;
  263. }
  264. set
  265. {
  266. if (value < 0)
  267. {
  268. throw (new InvalidOperationException(SR.ExceptionSmartLabelsMinMovingDistanceIsNegative));
  269. }
  270. _minMovingDistance = value;
  271. Invalidate();
  272. }
  273. }
  274. /// <summary>
  275. /// Maximum distance the overlapped SmartLabelStyle can be moved from the marker. Distance is measured in pixels.
  276. /// </summary>
  277. [
  278. SRCategory("CategoryAttributeMisc"),
  279. Bindable(true),
  280. DefaultValue(30.0),
  281. SRDescription("DescriptionAttributeMaxMovingDistance"),
  282. ]
  283. virtual public double MaxMovingDistance
  284. {
  285. get
  286. {
  287. return _maxMovingDistance;
  288. }
  289. set
  290. {
  291. if (value < 0)
  292. {
  293. throw (new InvalidOperationException(SR.ExceptionSmartLabelsMaxMovingDistanceIsNegative));
  294. }
  295. _maxMovingDistance = value;
  296. Invalidate();
  297. }
  298. }
  299. /// <summary>
  300. /// Defines if SmartLabelStyle are allowed to be drawn outside of the plotting area.
  301. /// </summary>
  302. [
  303. SRCategory("CategoryAttributeMisc"),
  304. Bindable(true),
  305. DefaultValue(LabelOutsidePlotAreaStyle.Partial),
  306. SRDescription("DescriptionAttributeAllowOutsidePlotArea"),
  307. ]
  308. virtual public LabelOutsidePlotAreaStyle AllowOutsidePlotArea
  309. {
  310. get
  311. {
  312. return _allowOutsidePlotArea;
  313. }
  314. set
  315. {
  316. _allowOutsidePlotArea = value;
  317. Invalidate();
  318. }
  319. }
  320. /// <summary>
  321. /// Callout style of the repositioned SmartLabelStyle.
  322. /// </summary>
  323. [
  324. SRCategory("CategoryAttributeMisc"),
  325. Bindable(true),
  326. DefaultValue(LabelCalloutStyle.Underlined),
  327. SRDescription("DescriptionAttributeCalloutStyle3"),
  328. ]
  329. virtual public LabelCalloutStyle CalloutStyle
  330. {
  331. get
  332. {
  333. return _calloutStyle;
  334. }
  335. set
  336. {
  337. _calloutStyle = value;
  338. Invalidate();
  339. }
  340. }
  341. /// <summary>
  342. /// Label callout line color.
  343. /// </summary>
  344. [
  345. SRCategory("CategoryAttributeAppearance"),
  346. Bindable(true),
  347. DefaultValue(typeof(Color), "Black"),
  348. SRDescription("DescriptionAttributeCalloutLineColor"),
  349. TypeConverter(typeof(ColorConverter)),
  350. #if DESIGNER
  351. Editor(typeof(ChartColorEditor), typeof(UITypeEditor))
  352. #endif
  353. ]
  354. virtual public Color CalloutLineColor
  355. {
  356. get
  357. {
  358. return _calloutLineColor;
  359. }
  360. set
  361. {
  362. _calloutLineColor = value;
  363. Invalidate();
  364. }
  365. }
  366. /// <summary>
  367. /// Label callout line style.
  368. /// </summary>
  369. [
  370. SRCategory("CategoryAttributeAppearance"),
  371. Bindable(true),
  372. DefaultValue(ChartDashStyle.Solid),
  373. SRDescription("DescriptionAttributeLineDashStyle"),
  374. ]
  375. virtual public ChartDashStyle CalloutLineDashStyle
  376. {
  377. get
  378. {
  379. return _calloutLineDashStyle;
  380. }
  381. set
  382. {
  383. _calloutLineDashStyle = value;
  384. Invalidate();
  385. }
  386. }
  387. /// <summary>
  388. /// Label callout back color. Applies to the Box style only!
  389. /// </summary>
  390. [
  391. SRCategory("CategoryAttributeAppearance"),
  392. Bindable(true),
  393. DefaultValue(typeof(Color), "Transparent"),
  394. SRDescription("DescriptionAttributeCalloutBackColor"),
  395. TypeConverter(typeof(ColorConverter)),
  396. #if DESIGNER
  397. Editor(typeof(ChartColorEditor), typeof(UITypeEditor))
  398. #endif
  399. ]
  400. virtual public Color CalloutBackColor
  401. {
  402. get
  403. {
  404. return _calloutBackColor;
  405. }
  406. set
  407. {
  408. _calloutBackColor = value;
  409. Invalidate();
  410. }
  411. }
  412. /// <summary>
  413. /// Label callout line width.
  414. /// </summary>
  415. [
  416. SRCategory("CategoryAttributeAppearance"),
  417. Bindable(true),
  418. DefaultValue(1),
  419. SRDescription("DescriptionAttributeLineWidth"),
  420. ]
  421. virtual public int CalloutLineWidth
  422. {
  423. get
  424. {
  425. return _calloutLineWidth;
  426. }
  427. set
  428. {
  429. _calloutLineWidth = value;
  430. Invalidate();
  431. }
  432. }
  433. /// <summary>
  434. /// Label callout line anchor cap.
  435. /// </summary>
  436. [
  437. SRCategory("CategoryAttributeAppearance"),
  438. Bindable(true),
  439. DefaultValue(LineAnchorCapStyle.Arrow),
  440. SRDescription(SR.Keys.DescriptionAttributeCalloutLineAnchorCap),
  441. ]
  442. virtual public LineAnchorCapStyle CalloutLineAnchorCapStyle
  443. {
  444. get
  445. {
  446. return _calloutLineAnchorCapStyle;
  447. }
  448. set
  449. {
  450. _calloutLineAnchorCapStyle = value;
  451. Invalidate();
  452. }
  453. }
  454. #endregion
  455. #region Methods
  456. /// <summary>
  457. /// Invalidates assosiated chart element.
  458. /// </summary>
  459. private void Invalidate()
  460. {
  461. if (chartElement != null)
  462. {
  463. if (chartElement is Series)
  464. {
  465. ((Series)chartElement).Invalidate(false, false);
  466. }
  467. else if (chartElement is Annotation)
  468. {
  469. ((Annotation)chartElement).Invalidate();
  470. }
  471. }
  472. }
  473. #endregion
  474. }
  475. /// <summary>
  476. /// SmartLabelStyle class implements the SmartLabelStyle algorithm for the
  477. /// data series points. It keeps track of all labels drawn and
  478. /// detects their collisions. When labels collision is detected
  479. /// the algorithm tries to resolve it by repositioning the labels.
  480. /// If label can not be repositioned it maybe hidden depending on
  481. /// the current settings.
  482. /// </summary>
  483. [
  484. SRDescription("DescriptionAttributeSmartLabels_SmartLabels"),
  485. ]
  486. internal class SmartLabel
  487. {
  488. #region Fields
  489. // List of all SmartLabelStyle positions in the area
  490. internal ArrayList smartLabelsPositions = null;
  491. // Indicates that not a single collision is allowed
  492. internal bool checkAllCollisions = false;
  493. // Number of positions in array for the markers
  494. internal int markersCount = 0;
  495. #endregion
  496. #region Constructors and initialization
  497. /// <summary>
  498. /// Default public constructor.
  499. /// </summary>
  500. public SmartLabel()
  501. {
  502. }
  503. #endregion
  504. #region Methods
  505. /// <summary>
  506. /// Reset SmartLabelStyle object.
  507. /// </summary>
  508. internal void Reset()
  509. {
  510. // Re-initialize list of labels position
  511. smartLabelsPositions = new ArrayList();
  512. }
  513. /// <summary>
  514. /// Process single SmartLabelStyle by adjusting it's position in case of collision.
  515. /// </summary>
  516. /// <param name="common">Reference to common elements.</param>
  517. /// <param name="graph">Reference to chart graphics object.</param>
  518. /// <param name="area">Chart area.</param>
  519. /// <param name="smartLabelStyle">Smart labels style.</param>
  520. /// <param name="labelPosition">Original label position.</param>
  521. /// <param name="labelSize">Label text size.</param>
  522. /// <param name="format">Label string format.</param>
  523. /// <param name="markerPosition">Marker position.</param>
  524. /// <param name="markerSize">Marker size.</param>
  525. /// <param name="labelAlignment">Original label alignment.</param>
  526. /// <returns>Adjusted position of the label.</returns>
  527. internal PointF AdjustSmartLabelPosition(
  528. CommonElements common,
  529. ChartGraphics graph,
  530. ChartArea area,
  531. SmartLabelStyle smartLabelStyle,
  532. PointF labelPosition,
  533. SizeF labelSize,
  534. StringFormat format,
  535. PointF markerPosition,
  536. SizeF markerSize,
  537. LabelAlignmentStyles labelAlignment)
  538. {
  539. return AdjustSmartLabelPosition(
  540. common,
  541. graph,
  542. area,
  543. smartLabelStyle,
  544. labelPosition,
  545. labelSize,
  546. format,
  547. markerPosition,
  548. markerSize,
  549. labelAlignment,
  550. false);
  551. }
  552. /// <summary>
  553. /// Process single SmartLabelStyle by adjusting it's position in case of collision.
  554. /// </summary>
  555. /// <param name="common">Reference to common elements.</param>
  556. /// <param name="graph">Reference to chart graphics object.</param>
  557. /// <param name="area">Chart area.</param>
  558. /// <param name="smartLabelStyle">Smart labels style.</param>
  559. /// <param name="labelPosition">Original label position.</param>
  560. /// <param name="labelSize">Label text size.</param>
  561. /// <param name="format">Label string format.</param>
  562. /// <param name="markerPosition">Marker position.</param>
  563. /// <param name="markerSize">Marker size.</param>
  564. /// <param name="labelAlignment">Original label alignment.</param>
  565. /// <param name="checkCalloutLineOverlapping">Indicates that labels overlapping by callout line must be checked.</param>
  566. /// <returns>Adjusted position of the label.</returns>
  567. internal PointF AdjustSmartLabelPosition(
  568. CommonElements common,
  569. ChartGraphics graph,
  570. ChartArea area,
  571. SmartLabelStyle smartLabelStyle,
  572. PointF labelPosition,
  573. SizeF labelSize,
  574. StringFormat format,
  575. PointF markerPosition,
  576. SizeF markerSize,
  577. LabelAlignmentStyles labelAlignment,
  578. bool checkCalloutLineOverlapping)
  579. {
  580. // Check if SmartLabelStyle are enabled
  581. if (smartLabelStyle.Enabled)
  582. {
  583. bool labelMovedAway = false;
  584. // Add series markers positions to avoid their overlapping
  585. bool rememberMarkersCount = (this.smartLabelsPositions.Count == 0);
  586. AddMarkersPosition(common, area);
  587. if (rememberMarkersCount)
  588. {
  589. this.markersCount = this.smartLabelsPositions.Count;
  590. }
  591. // Check label collision
  592. if (IsSmartLabelCollide(
  593. common,
  594. graph,
  595. area,
  596. smartLabelStyle,
  597. labelPosition,
  598. labelSize,
  599. markerPosition,
  600. format,
  601. labelAlignment,
  602. checkCalloutLineOverlapping))
  603. {
  604. // Try to find a new position for the SmartLabelStyle
  605. labelMovedAway = FindNewPosition(
  606. common,
  607. graph,
  608. area,
  609. smartLabelStyle,
  610. ref labelPosition,
  611. labelSize,
  612. format,
  613. markerPosition,
  614. ref markerSize,
  615. ref labelAlignment,
  616. checkCalloutLineOverlapping);
  617. // Draw label callout if label was moved away or
  618. // it's displayed in the corners of the marker
  619. if (labelMovedAway ||
  620. (labelAlignment == LabelAlignmentStyles.BottomLeft ||
  621. labelAlignment == LabelAlignmentStyles.BottomRight ||
  622. labelAlignment == LabelAlignmentStyles.TopLeft ||
  623. labelAlignment == LabelAlignmentStyles.TopRight))
  624. {
  625. if (!labelPosition.IsEmpty)
  626. {
  627. DrawCallout(
  628. common,
  629. graph,
  630. area,
  631. smartLabelStyle,
  632. labelPosition,
  633. labelSize,
  634. format,
  635. markerPosition,
  636. markerSize,
  637. labelAlignment);
  638. }
  639. }
  640. }
  641. // Add label position into the list
  642. AddSmartLabelPosition(graph, labelPosition, labelSize, format);
  643. }
  644. // Return label position
  645. return labelPosition;
  646. }
  647. /// <summary>
  648. /// Process single SmartLabelStyle by adjusting it's position in case of collision.
  649. /// </summary>
  650. /// <param name="common">Reference to common elements.</param>
  651. /// <param name="graph">Reference to chart graphics object.</param>
  652. /// <param name="area">Chart area.</param>
  653. /// <param name="smartLabelStyle">Smart labels style.</param>
  654. /// <param name="labelPosition">Original label position.</param>
  655. /// <param name="labelSize">Label text size.</param>
  656. /// <param name="format">Label string format.</param>
  657. /// <param name="markerPosition">Marker position.</param>
  658. /// <param name="markerSize">Marker size.</param>
  659. /// <param name="labelAlignment">Label alignment.</param>
  660. /// <param name="checkCalloutLineOverlapping">Indicates that labels overlapping by callout line must be checked.</param>
  661. /// <returns>True if label was moved away from the marker.</returns>
  662. private bool FindNewPosition(
  663. CommonElements common,
  664. ChartGraphics graph,
  665. ChartArea area,
  666. SmartLabelStyle smartLabelStyle,
  667. ref PointF labelPosition,
  668. SizeF labelSize,
  669. StringFormat format,
  670. PointF markerPosition,
  671. ref SizeF markerSize,
  672. ref LabelAlignmentStyles labelAlignment,
  673. bool checkCalloutLineOverlapping)
  674. {
  675. SizeF newMarkerSize = SizeF.Empty;
  676. PointF newLabelPosition = PointF.Empty;
  677. int positionIndex = 0;
  678. float labelMovement = 0f;
  679. bool labelMovedAway = false;
  680. LabelAlignmentStyles[] positions = new LabelAlignmentStyles[] {
  681. LabelAlignmentStyles.Top,
  682. LabelAlignmentStyles.Bottom,
  683. LabelAlignmentStyles.Left,
  684. LabelAlignmentStyles.Right,
  685. LabelAlignmentStyles.TopLeft,
  686. LabelAlignmentStyles.TopRight,
  687. LabelAlignmentStyles.BottomLeft,
  688. LabelAlignmentStyles.BottomRight,
  689. LabelAlignmentStyles.Center
  690. };
  691. // Get relative size of single pixel
  692. SizeF pixelSize = graph.GetRelativeSize(new SizeF(1f, 1f));
  693. // Try to find a new position for the label
  694. bool positionFound = false;
  695. float movingStep = 2f;
  696. float minMove = (float)Math.Min(smartLabelStyle.MinMovingDistance, smartLabelStyle.MaxMovingDistance);
  697. float maxMove = (float)Math.Max(smartLabelStyle.MinMovingDistance, smartLabelStyle.MaxMovingDistance);
  698. for (labelMovement = minMove; !positionFound && labelMovement <= maxMove; labelMovement += movingStep)
  699. {
  700. // Move label by increasing marker size by 4 pixels
  701. newMarkerSize = new SizeF(
  702. markerSize.Width + labelMovement * (pixelSize.Width * 2f),
  703. markerSize.Height + labelMovement * (pixelSize.Height * 2f));
  704. // Loop through different alignment types
  705. for (positionIndex = 0; positionIndex < positions.Length; positionIndex++)
  706. {
  707. // Center label alignment should only be tried once!
  708. if (positions[positionIndex] == LabelAlignmentStyles.Center && labelMovement != minMove)
  709. {
  710. continue;
  711. }
  712. // Check if this alignment is valid
  713. if ((smartLabelStyle.MovingDirection & positions[positionIndex]) == positions[positionIndex])
  714. {
  715. // Calculate new position of the label
  716. newLabelPosition = CalculatePosition(
  717. positions[positionIndex],
  718. markerPosition,
  719. newMarkerSize,
  720. labelSize,
  721. ref format);
  722. // Check new position collision
  723. if (!IsSmartLabelCollide(
  724. common,
  725. null,
  726. area,
  727. smartLabelStyle,
  728. newLabelPosition,
  729. labelSize,
  730. markerPosition,
  731. format,
  732. positions[positionIndex],
  733. checkCalloutLineOverlapping))
  734. {
  735. positionFound = true;
  736. labelMovedAway = (labelMovement == 0f) ? false : true;
  737. break;
  738. }
  739. }
  740. }
  741. }
  742. // Set new data if new label position was found
  743. if (positionFound)
  744. {
  745. markerSize = newMarkerSize;
  746. labelPosition = newLabelPosition;
  747. labelAlignment = positions[positionIndex];
  748. }
  749. // DEBUG code
  750. #if DEBUG
  751. if (common.Chart.ShowDebugMarkings)
  752. {
  753. RectangleF lp = GetLabelPosition(graph, labelPosition, labelSize, format, false);
  754. if (positionFound)
  755. {
  756. graph.Graphics.DrawRectangle(Pens.Green, Rectangle.Round(graph.GetAbsoluteRectangle(lp)));
  757. }
  758. else
  759. {
  760. graph.Graphics.DrawRectangle(new Pen(Color.Magenta, 3), Rectangle.Round(graph.GetAbsoluteRectangle(lp)));
  761. }
  762. }
  763. #endif
  764. // Do not draw overlapped labels that can't be repositioned
  765. if (!positionFound && smartLabelStyle.IsOverlappedHidden)
  766. {
  767. labelPosition = PointF.Empty;
  768. }
  769. return (labelMovedAway && positionFound) ? true : false;
  770. }
  771. /// <summary>
  772. /// Process single SmartLabelStyle by adjusting it's position in case of collision.
  773. /// </summary>
  774. /// <param name="common">Reference to common elements.</param>
  775. /// <param name="graph">Reference to chart graphics object.</param>
  776. /// <param name="area">Chart area.</param>
  777. /// <param name="smartLabelStyle">Smart labels style.</param>
  778. /// <param name="labelPosition">Original label position.</param>
  779. /// <param name="labelSize">Label text size.</param>
  780. /// <param name="format">Label string format.</param>
  781. /// <param name="markerPosition">Marker position.</param>
  782. /// <param name="markerSize">Marker size.</param>
  783. /// <param name="labelAlignment">Label alignment.</param>
  784. /// <returns>Adjusted position of the label.</returns>
  785. virtual internal void DrawCallout(
  786. CommonElements common,
  787. ChartGraphics graph,
  788. ChartArea area,
  789. SmartLabelStyle smartLabelStyle,
  790. PointF labelPosition,
  791. SizeF labelSize,
  792. StringFormat format,
  793. PointF markerPosition,
  794. SizeF markerSize,
  795. LabelAlignmentStyles labelAlignment)
  796. {
  797. // Calculate label position rectangle
  798. RectangleF labelRectAbs = graph.GetAbsoluteRectangle(
  799. GetLabelPosition(graph, labelPosition, labelSize, format, true));
  800. // Create callout pen
  801. Pen calloutPen = new Pen(smartLabelStyle.CalloutLineColor, smartLabelStyle.CalloutLineWidth);
  802. calloutPen.DashStyle = graph.GetPenStyle(smartLabelStyle.CalloutLineDashStyle);
  803. // Draw callout frame
  804. if (smartLabelStyle.CalloutStyle == LabelCalloutStyle.Box)
  805. {
  806. // Fill callout box around the label
  807. if (smartLabelStyle.CalloutBackColor != Color.Transparent)
  808. {
  809. using (Brush calloutBrush = new SolidBrush(smartLabelStyle.CalloutBackColor))
  810. {
  811. graph.FillRectangle(calloutBrush, labelRectAbs);
  812. }
  813. }
  814. // Draw box border
  815. graph.DrawRectangle(calloutPen, labelRectAbs.X, labelRectAbs.Y, labelRectAbs.Width, labelRectAbs.Height);
  816. }
  817. else if (smartLabelStyle.CalloutStyle == LabelCalloutStyle.Underlined)
  818. {
  819. if (labelAlignment == LabelAlignmentStyles.Right)
  820. {
  821. // Draw line to the left of label's text
  822. graph.DrawLine(calloutPen, labelRectAbs.X, labelRectAbs.Top, labelRectAbs.X, labelRectAbs.Bottom);
  823. }
  824. else if (labelAlignment == LabelAlignmentStyles.Left)
  825. {
  826. // Draw line to the right of label's text
  827. graph.DrawLine(calloutPen, labelRectAbs.Right, labelRectAbs.Top, labelRectAbs.Right, labelRectAbs.Bottom);
  828. }
  829. else if (labelAlignment == LabelAlignmentStyles.Bottom)
  830. {
  831. // Draw line on top of the label's text
  832. graph.DrawLine(calloutPen, labelRectAbs.X, labelRectAbs.Top, labelRectAbs.Right, labelRectAbs.Top);
  833. }
  834. else
  835. {
  836. // Draw line under the label's text
  837. graph.DrawLine(calloutPen, labelRectAbs.X, labelRectAbs.Bottom, labelRectAbs.Right, labelRectAbs.Bottom);
  838. }
  839. }
  840. // Calculate connector line point on the label
  841. PointF connectorPosition = graph.GetAbsolutePoint(labelPosition);
  842. if (labelAlignment == LabelAlignmentStyles.Top)
  843. {
  844. connectorPosition.Y = labelRectAbs.Bottom;
  845. }
  846. else if (labelAlignment == LabelAlignmentStyles.Bottom)
  847. {
  848. connectorPosition.Y = labelRectAbs.Top;
  849. }
  850. if (smartLabelStyle.CalloutStyle == LabelCalloutStyle.Underlined)
  851. {
  852. if (labelAlignment == LabelAlignmentStyles.TopLeft ||
  853. labelAlignment == LabelAlignmentStyles.TopRight ||
  854. labelAlignment == LabelAlignmentStyles.BottomLeft ||
  855. labelAlignment == LabelAlignmentStyles.BottomRight)
  856. {
  857. connectorPosition.Y = labelRectAbs.Bottom;
  858. }
  859. }
  860. // Apply anchor cap settings
  861. if (smartLabelStyle.CalloutLineAnchorCapStyle == LineAnchorCapStyle.Arrow)
  862. {
  863. //calloutPen.StartCap = LineCap.ArrowAnchor;
  864. calloutPen.StartCap = LineCap.Custom;
  865. calloutPen.CustomStartCap = new AdjustableArrowCap(calloutPen.Width + 2, calloutPen.Width + 3, true);
  866. }
  867. else if (smartLabelStyle.CalloutLineAnchorCapStyle == LineAnchorCapStyle.Diamond)
  868. {
  869. calloutPen.StartCap = LineCap.DiamondAnchor;
  870. }
  871. else if (smartLabelStyle.CalloutLineAnchorCapStyle == LineAnchorCapStyle.Round)
  872. {
  873. calloutPen.StartCap = LineCap.RoundAnchor;
  874. }
  875. else if (smartLabelStyle.CalloutLineAnchorCapStyle == LineAnchorCapStyle.Square)
  876. {
  877. calloutPen.StartCap = LineCap.SquareAnchor;
  878. }
  879. // Draw connection line between marker position and label
  880. PointF markerPositionAbs = graph.GetAbsolutePoint(markerPosition);
  881. graph.DrawLine(
  882. calloutPen,
  883. markerPositionAbs.X,
  884. markerPositionAbs.Y,
  885. connectorPosition.X,
  886. connectorPosition.Y);
  887. }
  888. /// <summary>
  889. /// Checks SmartLabelStyle collision.
  890. /// </summary>
  891. /// <param name="common">Reference to common elements.</param>
  892. /// <param name="graph">Reference to chart graphics object.</param>
  893. /// <param name="area">Chart area.</param>
  894. /// <param name="smartLabelStyle">Smart labels style.</param>
  895. /// <param name="position">Original label position.</param>
  896. /// <param name="size">Label text size.</param>
  897. /// <param name="markerPosition">Marker position.</param>
  898. /// <param name="format">Label string format.</param>
  899. /// <param name="labelAlignment">Label alignment.</param>
  900. /// <param name="checkCalloutLineOverlapping">Indicates that labels overlapping by callout line must be checked.</param>
  901. /// <returns>True if label collides.</returns>
  902. virtual internal bool IsSmartLabelCollide(
  903. CommonElements common,
  904. ChartGraphics graph,
  905. ChartArea area,
  906. SmartLabelStyle smartLabelStyle,
  907. PointF position,
  908. SizeF size,
  909. PointF markerPosition,
  910. StringFormat format,
  911. LabelAlignmentStyles labelAlignment,
  912. bool checkCalloutLineOverlapping)
  913. {
  914. bool collisionDetected = false;
  915. // Calculate label position rectangle
  916. RectangleF labelPosition = GetLabelPosition(graph, position, size, format, false);
  917. // Check if label goes outside of the chart picture
  918. if (labelPosition.X < 0f || labelPosition.Y < 0f ||
  919. labelPosition.Bottom > 100f || labelPosition.Right > 100f)
  920. {
  921. #if DEBUG
  922. // DEBUG: Mark collided labels
  923. if (graph != null && common != null && common.Chart != null && common.Chart.ShowDebugMarkings)
  924. {
  925. graph.Graphics.DrawRectangle(Pens.Cyan, Rectangle.Round(graph.GetAbsoluteRectangle(labelPosition)));
  926. }
  927. #endif
  928. collisionDetected = true;
  929. }
  930. // Check if label is drawn outside of plotting area (collides with axis?).
  931. if (!collisionDetected && area != null)
  932. {
  933. if (area.chartAreaIsCurcular)
  934. {
  935. using (GraphicsPath areaPath = new GraphicsPath())
  936. {
  937. // Add circular shape of the area into the graphics path
  938. areaPath.AddEllipse(area.PlotAreaPosition.ToRectangleF());
  939. if (smartLabelStyle.AllowOutsidePlotArea == LabelOutsidePlotAreaStyle.Partial)
  940. {
  941. PointF centerPos = new PointF(
  942. labelPosition.X + labelPosition.Width / 2f,
  943. labelPosition.Y + labelPosition.Height / 2f);
  944. if (!areaPath.IsVisible(centerPos))
  945. {
  946. // DEBUG: Mark collided labels
  947. #if DEBUG
  948. if (graph != null && common.Chart.ShowDebugMarkings)
  949. {
  950. graph.Graphics.DrawRectangle(Pens.Cyan, Rectangle.Round(graph.GetAbsoluteRectangle(labelPosition)));
  951. }
  952. #endif
  953. collisionDetected = true;
  954. }
  955. }
  956. else if (smartLabelStyle.AllowOutsidePlotArea == LabelOutsidePlotAreaStyle.No)
  957. {
  958. if (!areaPath.IsVisible(labelPosition.Location) ||
  959. !areaPath.IsVisible(new PointF(labelPosition.Right, labelPosition.Y)) ||
  960. !areaPath.IsVisible(new PointF(labelPosition.Right, labelPosition.Bottom)) ||
  961. !areaPath.IsVisible(new PointF(labelPosition.X, labelPosition.Bottom)))
  962. {
  963. // DEBUG: Mark collided labels
  964. #if DEBUG
  965. if (graph != null && common.Chart.ShowDebugMarkings)
  966. {
  967. graph.Graphics.DrawRectangle(Pens.Cyan, Rectangle.Round(graph.GetAbsoluteRectangle(labelPosition)));
  968. }
  969. #endif
  970. collisionDetected = true;
  971. }
  972. }
  973. }
  974. }
  975. else
  976. {
  977. if (smartLabelStyle.AllowOutsidePlotArea == LabelOutsidePlotAreaStyle.Partial)
  978. {
  979. PointF centerPos = new PointF(
  980. labelPosition.X + labelPosition.Width / 2f,
  981. labelPosition.Y + labelPosition.Height / 2f);
  982. if (!area.PlotAreaPosition.ToRectangleF().Contains(centerPos))
  983. {
  984. // DEBUG: Mark collided labels
  985. #if DEBUG
  986. if (graph != null && common.Chart.ShowDebugMarkings)
  987. {
  988. graph.Graphics.DrawRectangle(Pens.Cyan, Rectangle.Round(graph.GetAbsoluteRectangle(labelPosition)));
  989. }
  990. #endif
  991. collisionDetected = true;
  992. }
  993. }
  994. else if (smartLabelStyle.AllowOutsidePlotArea == LabelOutsidePlotAreaStyle.No)
  995. {
  996. if (!area.PlotAreaPosition.ToRectangleF().Contains(labelPosition))
  997. {
  998. // DEBUG: Mark collided labels
  999. #if DEBUG
  1000. if (graph != null && common.Chart.ShowDebugMarkings)
  1001. {
  1002. graph.Graphics.DrawRectangle(Pens.Cyan, Rectangle.Round(graph.GetAbsoluteRectangle(labelPosition)));
  1003. }
  1004. #endif
  1005. collisionDetected = true;
  1006. }
  1007. }
  1008. }
  1009. }
  1010. // Check if 1 collisuion is aceptable in case of cennter alignment
  1011. bool allowOneCollision =
  1012. (labelAlignment == LabelAlignmentStyles.Center && !smartLabelStyle.IsMarkerOverlappingAllowed) ? true : false;
  1013. if (this.checkAllCollisions)
  1014. {
  1015. allowOneCollision = false;
  1016. }
  1017. // Loop through all smart label positions
  1018. if (!collisionDetected && this.smartLabelsPositions != null)
  1019. {
  1020. int index = -1;
  1021. foreach (RectangleF pos in this.smartLabelsPositions)
  1022. {
  1023. // Increase index
  1024. ++index;
  1025. // Check if label collide with other labels or markers.
  1026. bool collision = pos.IntersectsWith(labelPosition);
  1027. // Check if label callout line collide with other labels or markers.
  1028. // Line may overlap markers!
  1029. if (!collision &&
  1030. checkCalloutLineOverlapping &&
  1031. index >= markersCount)
  1032. {
  1033. PointF labelCenter = new PointF(
  1034. labelPosition.X + labelPosition.Width / 2f,
  1035. labelPosition.Y + labelPosition.Height / 2f);
  1036. if (LineIntersectRectangle(pos, markerPosition, labelCenter))
  1037. {
  1038. collision = true;
  1039. }
  1040. }
  1041. // Collision detected
  1042. if (collision)
  1043. {
  1044. // Check if 1 collision allowed
  1045. if (allowOneCollision)
  1046. {
  1047. allowOneCollision = false;
  1048. continue;
  1049. }
  1050. // DEBUG: Mark collided labels
  1051. #if DEBUG
  1052. if (graph != null &&
  1053. common.ChartPicture != null &&
  1054. common.ChartPicture.ChartGraph != null &&
  1055. common.Chart.ShowDebugMarkings)
  1056. {
  1057. common.ChartPicture.ChartGraph.Graphics.DrawRectangle(Pens.Blue, Rectangle.Round(common.ChartPicture.ChartGraph.GetAbsoluteRectangle(pos)));
  1058. common.ChartPicture.ChartGraph.Graphics.DrawRectangle(Pens.Red, Rectangle.Round(common.ChartPicture.ChartGraph.GetAbsoluteRectangle(labelPosition)));
  1059. }
  1060. #endif
  1061. collisionDetected = true;
  1062. break;
  1063. }
  1064. }
  1065. }
  1066. return collisionDetected;
  1067. }
  1068. /// <summary>
  1069. /// Checks if rectangle intersected by the line.
  1070. /// </summary>
  1071. /// <param name="rect">Rectangle to be tested.</param>
  1072. /// <param name="point1">First line point.</param>
  1073. /// <param name="point2">Second line point.</param>
  1074. /// <returns>True if line intersects rectangle.</returns>
  1075. private bool LineIntersectRectangle(RectangleF rect, PointF point1, PointF point2)
  1076. {
  1077. // Check for horizontal line
  1078. if (point1.X == point2.X)
  1079. {
  1080. if (point1.X >= rect.X && point1.X <= rect.Right)
  1081. {
  1082. if (point1.Y < rect.Y && point2.Y < rect.Y)
  1083. {
  1084. return false;
  1085. }
  1086. if (point1.Y > rect.Bottom && point2.Y > rect.Bottom)
  1087. {
  1088. return false;
  1089. }
  1090. return true;
  1091. }
  1092. return false;
  1093. }
  1094. // Check for vertical line
  1095. if (point1.Y == point2.Y)
  1096. {
  1097. if (point1.Y >= rect.Y && point1.Y <= rect.Bottom)
  1098. {
  1099. if (point1.X < rect.X && point2.X < rect.X)
  1100. {
  1101. return false;
  1102. }
  1103. if (point1.X > rect.Right && point2.X > rect.Right)
  1104. {
  1105. return false;
  1106. }
  1107. return true;
  1108. }
  1109. return false;
  1110. }
  1111. // Check if line completly outside rectangle
  1112. if (point1.X < rect.X && point2.X < rect.X)
  1113. {
  1114. return false;
  1115. }
  1116. else if (point1.X > rect.Right && point2.X > rect.Right)
  1117. {
  1118. return false;
  1119. }
  1120. else if (point1.Y < rect.Y && point2.Y < rect.Y)
  1121. {
  1122. return false;
  1123. }
  1124. else if (point1.Y > rect.Bottom && point2.Y > rect.Bottom)
  1125. {
  1126. return false;
  1127. }
  1128. // Check if one of the points inside rectangle
  1129. if (rect.Contains(point1) ||
  1130. rect.Contains(point2))
  1131. {
  1132. return true;
  1133. }
  1134. // Calculate intersection point of the line with each side of the rectangle
  1135. PointF intersection = CalloutAnnotation.GetIntersectionY(point1, point2, rect.Y);
  1136. if (rect.Contains(intersection))
  1137. {
  1138. return true;
  1139. }
  1140. intersection = CalloutAnnotation.GetIntersectionY(point1, point2, rect.Bottom);
  1141. if (rect.Contains(intersection))
  1142. {
  1143. return true;
  1144. }
  1145. intersection = CalloutAnnotation.GetIntersectionX(point1, point2, rect.X);
  1146. if (rect.Contains(intersection))
  1147. {
  1148. return true;
  1149. }
  1150. intersection = CalloutAnnotation.GetIntersectionX(point1, point2, rect.Right);
  1151. if (rect.Contains(intersection))
  1152. {
  1153. return true;
  1154. }
  1155. return false;
  1156. }
  1157. /// <summary>
  1158. /// Adds positions of the series markers into the list.
  1159. /// </summary>
  1160. /// <param name="common">Reference to common elements.</param>
  1161. /// <param name="area">Chart area.</param>
  1162. virtual internal void AddMarkersPosition(
  1163. CommonElements common,
  1164. ChartArea area)
  1165. {
  1166. // Proceed only if there is no items in the list yet
  1167. if (this.smartLabelsPositions.Count == 0 && area != null)
  1168. {
  1169. // Get chart types registry
  1170. ChartTypeRegistry registry = common.ChartTypeRegistry;
  1171. // Loop through all the series from this chart area
  1172. foreach (Series series in common.DataManager.Series)
  1173. {
  1174. // Check if marker overapping is enabled for the series
  1175. if (series.ChartArea == area.Name &&
  1176. series.SmartLabelStyle.Enabled &&
  1177. !series.SmartLabelStyle.IsMarkerOverlappingAllowed)
  1178. {
  1179. // Get series chart type
  1180. IChartType chartType = registry.GetChartType(series.ChartTypeName);
  1181. // Add series markers positions into the list
  1182. chartType.AddSmartLabelMarkerPositions(common, area, series, this.smartLabelsPositions);
  1183. }
  1184. }
  1185. // Make sure labels do not intersect with scale breaks
  1186. foreach (Axis currentAxis in area.Axes)
  1187. {
  1188. // Check if scale breaks are defined and there are non zero spacing
  1189. if (currentAxis.ScaleBreakStyle.Spacing > 0.0
  1190. && currentAxis.ScaleSegments.Count > 0)
  1191. {
  1192. for (int index = 0; index < (currentAxis.ScaleSegments.Count - 1); index++)
  1193. {
  1194. // Get break position in pixel coordinates
  1195. RectangleF breakPosition = currentAxis.ScaleSegments[index].GetBreakLinePosition(common.graph, currentAxis.ScaleSegments[index + 1]);
  1196. breakPosition = common.graph.GetRelativeRectangle(breakPosition);
  1197. // Create array list if needed
  1198. if (this.smartLabelsPositions == null)
  1199. {
  1200. this.smartLabelsPositions = new ArrayList();
  1201. }
  1202. // Add label position into the list
  1203. this.smartLabelsPositions.Add(breakPosition);
  1204. }
  1205. }
  1206. }
  1207. }
  1208. }
  1209. /// <summary>
  1210. /// Adds single Smart Label position into the list.
  1211. /// </summary>
  1212. /// <param name="graph">Chart graphics object.</param>
  1213. /// <param name="position">Original label position.</param>
  1214. /// <param name="size">Label text size.</param>
  1215. /// <param name="format">Label string format.</param>
  1216. internal void AddSmartLabelPosition(
  1217. ChartGraphics graph,
  1218. PointF position,
  1219. SizeF size,
  1220. StringFormat format)
  1221. {
  1222. // Calculate label position rectangle
  1223. RectangleF labelPosition = GetLabelPosition(graph, position, size, format, false);
  1224. if (this.smartLabelsPositions == null)
  1225. {
  1226. this.smartLabelsPositions = new ArrayList();
  1227. }
  1228. // Add label position into the list
  1229. this.smartLabelsPositions.Add(labelPosition);
  1230. }
  1231. /// <summary>
  1232. /// Gets rectangle position of the label.
  1233. /// </summary>
  1234. /// <param name="graph">Chart graphics object.</param>
  1235. /// <param name="position">Original label position.</param>
  1236. /// <param name="size">Label text size.</param>
  1237. /// <param name="format">Label string format.</param>
  1238. /// <param name="adjustForDrawing">Result position is adjusted for drawing.</param>
  1239. /// <returns>Label rectangle position.</returns>
  1240. internal RectangleF GetLabelPosition(
  1241. ChartGraphics graph,
  1242. PointF position,
  1243. SizeF size,
  1244. StringFormat format,
  1245. bool adjustForDrawing)
  1246. {
  1247. // Calculate label position rectangle
  1248. RectangleF labelPosition = RectangleF.Empty;
  1249. labelPosition.Width = size.Width;
  1250. labelPosition.Height = size.Height;
  1251. // Calculate pixel size in relative coordiantes
  1252. SizeF pixelSize = SizeF.Empty;
  1253. if (graph != null)
  1254. {
  1255. pixelSize = graph.GetRelativeSize(new SizeF(1f, 1f));
  1256. }
  1257. if (format.Alignment == StringAlignment.Far)
  1258. {
  1259. labelPosition.X = position.X - size.Width;
  1260. if (adjustForDrawing && !pixelSize.IsEmpty)
  1261. {
  1262. labelPosition.X -= 4f * pixelSize.Width;
  1263. labelPosition.Width += 4f * pixelSize.Width;
  1264. }
  1265. }
  1266. else if (format.Alignment == StringAlignment.Near)
  1267. {
  1268. labelPosition.X = position.X;
  1269. if (adjustForDrawing && !pixelSize.IsEmpty)
  1270. {
  1271. labelPosition.Width += 4f * pixelSize.Width;
  1272. }
  1273. }
  1274. else if (format.Alignment == StringAlignment.Center)
  1275. {
  1276. labelPosition.X = position.X - size.Width / 2F;
  1277. if (adjustForDrawing && !pixelSize.IsEmpty)
  1278. {
  1279. labelPosition.X -= 2f * pixelSize.Width;
  1280. labelPosition.Width += 4f * pixelSize.Width;
  1281. }
  1282. }
  1283. if (format.LineAlignment == StringAlignment.Far)
  1284. {
  1285. labelPosition.Y = position.Y - size.Height;
  1286. }
  1287. else if (format.LineAlignment == StringAlignment.Near)
  1288. {
  1289. labelPosition.Y = position.Y;
  1290. }
  1291. else if (format.LineAlignment == StringAlignment.Center)
  1292. {
  1293. labelPosition.Y = position.Y - size.Height / 2F;
  1294. }
  1295. return labelPosition;
  1296. }
  1297. /// <summary>
  1298. /// Gets point position of the label.
  1299. /// </summary>
  1300. /// <param name="labelAlignment">Label alignment.</param>
  1301. /// <param name="markerPosition">Marker position.</param>
  1302. /// <param name="sizeMarker">Marker size.</param>
  1303. /// <param name="sizeFont">Label size.</param>
  1304. /// <param name="format">String format.</param>
  1305. /// <returns>Label point position.</returns>
  1306. private PointF CalculatePosition(
  1307. LabelAlignmentStyles labelAlignment,
  1308. PointF markerPosition,
  1309. SizeF sizeMarker,
  1310. SizeF sizeFont,
  1311. ref StringFormat format)
  1312. {
  1313. format.Alignment = StringAlignment.Near;
  1314. format.LineAlignment = StringAlignment.Center;
  1315. // Calculate label position
  1316. PointF position = new PointF(markerPosition.X, markerPosition.Y);
  1317. switch (labelAlignment)
  1318. {
  1319. case LabelAlignmentStyles.Center:
  1320. format.Alignment = StringAlignment.Center;
  1321. break;
  1322. case LabelAlignmentStyles.Bottom:
  1323. format.Alignment = StringAlignment.Center;
  1324. position.Y += sizeMarker.Height / 1.75F;
  1325. position.Y += sizeFont.Height / 2F;
  1326. break;
  1327. case LabelAlignmentStyles.Top:
  1328. format.Alignment = StringAlignment.Center;
  1329. position.Y -= sizeMarker.Height / 1.75F;
  1330. position.Y -= sizeFont.Height / 2F;
  1331. break;
  1332. case LabelAlignmentStyles.Left:
  1333. format.Alignment = StringAlignment.Far;
  1334. position.X -= sizeMarker.Height / 1.75F;
  1335. break;
  1336. case LabelAlignmentStyles.TopLeft:
  1337. format.Alignment = StringAlignment.Far;
  1338. position.X -= sizeMarker.Height / 1.75F;
  1339. position.Y -= sizeMarker.Height / 1.75F;
  1340. position.Y -= sizeFont.Height / 2F;
  1341. break;
  1342. case LabelAlignmentStyles.BottomLeft:
  1343. format.Alignment = StringAlignment.Far;
  1344. position.X -= sizeMarker.Height / 1.75F;
  1345. position.Y += sizeMarker.Height / 1.75F;
  1346. position.Y += sizeFont.Height / 2F;
  1347. break;
  1348. case LabelAlignmentStyles.Right:
  1349. position.X += sizeMarker.Height / 1.75F;
  1350. break;
  1351. case LabelAlignmentStyles.TopRight:
  1352. position.X += sizeMarker.Height / 1.75F;
  1353. position.Y -= sizeMarker.Height / 1.75F;
  1354. position.Y -= sizeFont.Height / 2F;
  1355. break;
  1356. case LabelAlignmentStyles.BottomRight:
  1357. position.X += sizeMarker.Height / 1.75F;
  1358. position.Y += sizeMarker.Height / 1.75F;
  1359. position.Y += sizeFont.Height / 2F;
  1360. break;
  1361. }
  1362. return position;
  1363. }
  1364. #endregion
  1365. }
  1366. /// <summary>
  1367. /// AnnotationSmartLabel class provides SmartLabelStyle functionality
  1368. /// specific to the annotation objects.
  1369. /// </summary>
  1370. [
  1371. SRDescription("DescriptionAttributeAnnotationSmartLabels_AnnotationSmartLabels"),
  1372. ]
  1373. internal class AnnotationSmartLabel : SmartLabel
  1374. {
  1375. #region Constructors and initialization
  1376. /// <summary>
  1377. /// Default public constructor.
  1378. /// </summary>
  1379. public AnnotationSmartLabel()
  1380. {
  1381. }
  1382. #endregion
  1383. #region Methods
  1384. /// <summary>
  1385. /// Checks SmartLabelStyle collision.
  1386. /// </summary>
  1387. /// <param name="common">Reference to common elements.</param>
  1388. /// <param name="graph">Reference to chart graphics object.</param>
  1389. /// <param name="area">Chart area.</param>
  1390. /// <param name="smartLabelStyle">Smart labels style.</param>
  1391. /// <param name="position">Original label position.</param>
  1392. /// <param name="size">Label text size.</param>
  1393. /// <param name="markerPosition">Marker position.</param>
  1394. /// <param name="format">Label string format.</param>
  1395. /// <param name="labelAlignment">Label alignment.</param>
  1396. /// <param name="checkCalloutLineOverlapping">Indicates that labels overlapping by callout line must be checked.</param>
  1397. /// <returns>True if label collides.</returns>
  1398. override internal bool IsSmartLabelCollide(
  1399. CommonElements common,
  1400. ChartGraphics graph,
  1401. ChartArea area,
  1402. SmartLabelStyle smartLabelStyle,
  1403. PointF position,
  1404. SizeF size,
  1405. PointF markerPosition,
  1406. StringFormat format,
  1407. LabelAlignmentStyles labelAlignment,
  1408. bool checkCalloutLineOverlapping)
  1409. {
  1410. bool collisionDetected = false;
  1411. //*******************************************************************
  1412. //** Check collision with smatl labels of series in chart area
  1413. //*******************************************************************
  1414. if (area != null && area.Visible)
  1415. {
  1416. area.smartLabels.checkAllCollisions = true;
  1417. if (area.smartLabels.IsSmartLabelCollide(
  1418. common,
  1419. graph,
  1420. area,
  1421. smartLabelStyle,
  1422. position,
  1423. size,
  1424. markerPosition,
  1425. format,
  1426. labelAlignment,
  1427. checkCalloutLineOverlapping))
  1428. {
  1429. area.smartLabels.checkAllCollisions = false;
  1430. return true;
  1431. }
  1432. area.smartLabels.checkAllCollisions = false;
  1433. }
  1434. //*******************************************************************
  1435. //** Check collision with other annotations.
  1436. //*******************************************************************
  1437. // Calculate label position rectangle
  1438. RectangleF labelPosition = GetLabelPosition(graph, position, size, format, false);
  1439. // Check if 1 collisuion is aceptable in case of cennter alignment
  1440. bool allowOneCollision =
  1441. (labelAlignment == LabelAlignmentStyles.Center && !smartLabelStyle.IsMarkerOverlappingAllowed) ? true : false;
  1442. if (this.checkAllCollisions)
  1443. {
  1444. allowOneCollision = false;
  1445. }
  1446. // Check if label collide with other labels or markers.
  1447. foreach (RectangleF pos in this.smartLabelsPositions)
  1448. {
  1449. if (pos.IntersectsWith(labelPosition))
  1450. {
  1451. // Check if 1 collision allowed
  1452. if (allowOneCollision)
  1453. {
  1454. allowOneCollision = false;
  1455. continue;
  1456. }
  1457. // DEBUG: Mark collided labels
  1458. #if DEBUG
  1459. if (graph != null && common.Chart.ShowDebugMarkings)
  1460. {
  1461. graph.Graphics.DrawRectangle(Pens.Blue, Rectangle.Round(graph.GetAbsoluteRectangle(pos)));
  1462. graph.Graphics.DrawRectangle(Pens.Red, Rectangle.Round(graph.GetAbsoluteRectangle(labelPosition)));
  1463. }
  1464. #endif
  1465. collisionDetected = true;
  1466. break;
  1467. }
  1468. }
  1469. return collisionDetected;
  1470. }
  1471. /// <summary>
  1472. /// Adds positions of the series markers into the list.
  1473. /// </summary>
  1474. /// <param name="common">Reference to common elements.</param>
  1475. /// <param name="area">Chart area.</param>
  1476. override internal void AddMarkersPosition(
  1477. CommonElements common,
  1478. ChartArea area)
  1479. {
  1480. // Proceed only if there is no items in the list yet
  1481. if (this.smartLabelsPositions.Count == 0 &&
  1482. common != null &&
  1483. common.Chart != null)
  1484. {
  1485. // Add annotations anchor points
  1486. foreach (Annotation annotation in common.Chart.Annotations)
  1487. {
  1488. annotation.AddSmartLabelMarkerPositions(this.smartLabelsPositions);
  1489. }
  1490. }
  1491. }
  1492. /// <summary>
  1493. /// Process single SmartLabelStyle by adjusting it's position in case of collision.
  1494. /// </summary>
  1495. /// <param name="common">Reference to common elements.</param>
  1496. /// <param name="graph">Reference to chart graphics object.</param>
  1497. /// <param name="area">Chart area.</param>
  1498. /// <param name="smartLabelStyle">Smart labels style.</param>
  1499. /// <param name="labelPosition">Original label position.</param>
  1500. /// <param name="labelSize">Label text size.</param>
  1501. /// <param name="format">Label string format.</param>
  1502. /// <param name="markerPosition">Marker position.</param>
  1503. /// <param name="markerSize">Marker size.</param>
  1504. /// <param name="labelAlignment">Label alignment.</param>
  1505. /// <returns>Adjusted position of the label.</returns>
  1506. override internal void DrawCallout(
  1507. CommonElements common,
  1508. ChartGraphics graph,
  1509. ChartArea area,
  1510. SmartLabelStyle smartLabelStyle,
  1511. PointF labelPosition,
  1512. SizeF labelSize,
  1513. StringFormat format,
  1514. PointF markerPosition,
  1515. SizeF markerSize,
  1516. LabelAlignmentStyles labelAlignment)
  1517. {
  1518. // No callout is drawn for the annotations
  1519. }
  1520. #endregion
  1521. }
  1522. }