GraphViewTests.cs 52 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636
  1. using System.Text;
  2. using UnitTests;
  3. using Xunit.Abstractions;
  4. namespace UnitTests.ViewsTests;
  5. #region Helper Classes
  6. internal class FakeHAxis : HorizontalAxis
  7. {
  8. public List<Point> DrawAxisLinePoints = new ();
  9. public List<int> LabelPoints = new ();
  10. public override void DrawAxisLabel (GraphView graph, int screenPosition, string text)
  11. {
  12. base.DrawAxisLabel (graph, screenPosition, text);
  13. LabelPoints.Add (screenPosition);
  14. }
  15. protected override void DrawAxisLine (GraphView graph, int x, int y)
  16. {
  17. base.DrawAxisLine (graph, x, y);
  18. DrawAxisLinePoints.Add (new Point (x, y));
  19. }
  20. }
  21. internal class FakeVAxis : VerticalAxis
  22. {
  23. public List<Point> DrawAxisLinePoints = new ();
  24. public List<int> LabelPoints = new ();
  25. public override void DrawAxisLabel (GraphView graph, int screenPosition, string text)
  26. {
  27. base.DrawAxisLabel (graph, screenPosition, text);
  28. LabelPoints.Add (screenPosition);
  29. }
  30. protected override void DrawAxisLine (GraphView graph, int x, int y)
  31. {
  32. base.DrawAxisLine (graph, x, y);
  33. DrawAxisLinePoints.Add (new Point (x, y));
  34. }
  35. }
  36. #endregion
  37. public class GraphViewTests : FakeDriverBase
  38. {
  39. /// <summary>
  40. /// A cell size of 0 would result in mapping all graph space into the same cell of the console. Since
  41. /// <see cref="GraphView.CellSize"/> is mutable a sensible place to check this is in redraw.
  42. /// </summary>
  43. [Fact]
  44. [AutoInitShutdown]
  45. public void CellSizeZero ()
  46. {
  47. var gv = new GraphView ();
  48. gv.BeginInit ();
  49. gv.EndInit ();
  50. //gv.Scheme = new Scheme ();
  51. gv.Viewport = new Rectangle (0, 0, 50, 30);
  52. gv.Series.Add (new ScatterSeries { Points = new List<PointF> { new (1, 1) } });
  53. gv.CellSize = new PointF (0, 5);
  54. var ex = Assert.Throws<Exception> (() => gv.Draw ());
  55. Assert.Equal ("CellSize cannot be 0", ex.Message);
  56. // Shutdown must be called to safely clean up Application if Init has been called
  57. Application.Shutdown ();
  58. }
  59. /// <summary>Returns a basic very small graph (10 x 5)</summary>
  60. /// <returns></returns>
  61. public static GraphView GetGraph ()
  62. {
  63. var gv = new GraphView ()
  64. {
  65. Driver = Application.Driver ?? CreateFakeDriver ()
  66. };
  67. gv.BeginInit ();
  68. gv.EndInit ();
  69. //gv.Scheme = new Scheme ();
  70. gv.MarginBottom = 1;
  71. gv.MarginLeft = 1;
  72. gv.Viewport = new Rectangle (0, 0, 10, 5);
  73. return gv;
  74. }
  75. /// <summary>
  76. /// Tests that each point in the screen space maps to a rectangle of (float) graph space and that each corner of
  77. /// that rectangle of graph space maps back to the same row/col of the graph that was fed in
  78. /// </summary>
  79. [Fact]
  80. public void TestReversing_ScreenToGraphSpace ()
  81. {
  82. var gv = new GraphView ();
  83. gv.BeginInit ();
  84. gv.EndInit ();
  85. gv.Viewport = new Rectangle (0, 0, 50, 30);
  86. // How much graph space each cell of the console depicts
  87. gv.CellSize = new PointF (0.1f, 0.25f);
  88. gv.AxisX.Increment = 1;
  89. gv.AxisX.ShowLabelsEvery = 1;
  90. gv.AxisY.Increment = 1;
  91. gv.AxisY.ShowLabelsEvery = 1;
  92. // Start the graph at 80
  93. gv.ScrollOffset = new PointF (0, 80);
  94. for (var x = 0; x < gv.Viewport.Width; x++)
  95. {
  96. for (var y = 0; y < gv.Viewport.Height; y++)
  97. {
  98. RectangleF graphSpace = gv.ScreenToGraphSpace (x, y);
  99. // See
  100. // https://en.wikipedia.org/wiki/Machine_epsilon
  101. var epsilon = 0.0001f;
  102. Point p = gv.GraphSpaceToScreen (
  103. new PointF (
  104. graphSpace.Left + epsilon,
  105. graphSpace.Top + epsilon
  106. )
  107. );
  108. Assert.Equal (x, p.X);
  109. Assert.Equal (y, p.Y);
  110. p = gv.GraphSpaceToScreen (
  111. new PointF (
  112. graphSpace.Right - epsilon,
  113. graphSpace.Top + epsilon
  114. )
  115. );
  116. Assert.Equal (x, p.X);
  117. Assert.Equal (y, p.Y);
  118. p = gv.GraphSpaceToScreen (
  119. new PointF (
  120. graphSpace.Left + epsilon,
  121. graphSpace.Bottom - epsilon
  122. )
  123. );
  124. Assert.Equal (x, p.X);
  125. Assert.Equal (y, p.Y);
  126. p = gv.GraphSpaceToScreen (
  127. new PointF (
  128. graphSpace.Right - epsilon,
  129. graphSpace.Bottom - epsilon
  130. )
  131. );
  132. Assert.Equal (x, p.X);
  133. Assert.Equal (y, p.Y);
  134. }
  135. }
  136. }
  137. #region Screen to Graph Tests
  138. [Fact]
  139. public void ScreenToGraphSpace_DefaultCellSize ()
  140. {
  141. var gv = new GraphView ();
  142. gv.BeginInit ();
  143. gv.EndInit ();
  144. gv.Viewport = new Rectangle (0, 0, 20, 10);
  145. // origin should be bottom left
  146. RectangleF botLeft = gv.ScreenToGraphSpace (0, 9);
  147. Assert.Equal (0, botLeft.X);
  148. Assert.Equal (0, botLeft.Y);
  149. Assert.Equal (1, botLeft.Width);
  150. Assert.Equal (1, botLeft.Height);
  151. // up 2 rows of the console and along 1 col
  152. RectangleF up2along1 = gv.ScreenToGraphSpace (1, 7);
  153. Assert.Equal (1, up2along1.X);
  154. Assert.Equal (2, up2along1.Y);
  155. }
  156. [Fact]
  157. public void ScreenToGraphSpace_DefaultCellSize_WithMargin ()
  158. {
  159. var gv = new GraphView ();
  160. gv.BeginInit ();
  161. gv.EndInit ();
  162. gv.Viewport = new Rectangle (0, 0, 20, 10);
  163. // origin should be bottom left
  164. RectangleF botLeft = gv.ScreenToGraphSpace (0, 9);
  165. Assert.Equal (0, botLeft.X);
  166. Assert.Equal (0, botLeft.Y);
  167. Assert.Equal (1, botLeft.Width);
  168. Assert.Equal (1, botLeft.Height);
  169. gv.MarginLeft = 1;
  170. botLeft = gv.ScreenToGraphSpace (0, 9);
  171. // Origin should be at 1,9 now to leave a margin of 1
  172. // so screen position 0,9 would be data space -1,0
  173. Assert.Equal (-1, botLeft.X);
  174. Assert.Equal (0, botLeft.Y);
  175. Assert.Equal (1, botLeft.Width);
  176. Assert.Equal (1, botLeft.Height);
  177. gv.MarginLeft = 1;
  178. gv.MarginBottom = 1;
  179. botLeft = gv.ScreenToGraphSpace (0, 9);
  180. // Origin should be at 1,0 (to leave a margin of 1 in both sides)
  181. // so screen position 0,9 would be data space -1,-1
  182. Assert.Equal (-1, botLeft.X);
  183. Assert.Equal (-1, botLeft.Y);
  184. Assert.Equal (1, botLeft.Width);
  185. Assert.Equal (1, botLeft.Height);
  186. }
  187. [Fact]
  188. public void ScreenToGraphSpace_CustomCellSize ()
  189. {
  190. var gv = new GraphView ();
  191. gv.BeginInit ();
  192. gv.EndInit ();
  193. gv.Viewport = new Rectangle (0, 0, 20, 10);
  194. // Each cell of screen measures 5 units in graph data model vertically and 1/4 horizontally
  195. gv.CellSize = new PointF (0.25f, 5);
  196. // origin should be bottom left
  197. // (note that y=10 is actually overspilling the control, the last row is 9)
  198. RectangleF botLeft = gv.ScreenToGraphSpace (0, 9);
  199. Assert.Equal (0, botLeft.X);
  200. Assert.Equal (0, botLeft.Y);
  201. Assert.Equal (0.25f, botLeft.Width);
  202. Assert.Equal (5, botLeft.Height);
  203. // up 2 rows of the console and along 1 col
  204. RectangleF up2along1 = gv.ScreenToGraphSpace (1, 7);
  205. Assert.Equal (0.25f, up2along1.X);
  206. Assert.Equal (10, up2along1.Y);
  207. Assert.Equal (0.25f, botLeft.Width);
  208. Assert.Equal (5, botLeft.Height);
  209. }
  210. #endregion
  211. #region Graph to Screen Tests
  212. [Fact]
  213. public void GraphSpaceToScreen_DefaultCellSize ()
  214. {
  215. var gv = new GraphView ();
  216. gv.BeginInit ();
  217. gv.EndInit ();
  218. gv.Viewport = new Rectangle (0, 0, 20, 10);
  219. // origin should be bottom left
  220. Point botLeft = gv.GraphSpaceToScreen (new PointF (0, 0));
  221. Assert.Equal (0, botLeft.X);
  222. Assert.Equal (9, botLeft.Y); // row 9 of the view is the bottom left
  223. // along 2 and up 1 in graph space
  224. Point along2up1 = gv.GraphSpaceToScreen (new PointF (2, 1));
  225. Assert.Equal (2, along2up1.X);
  226. Assert.Equal (8, along2up1.Y);
  227. }
  228. [Fact]
  229. public void GraphSpaceToScreen_DefaultCellSize_WithMargin ()
  230. {
  231. var gv = new GraphView ();
  232. gv.BeginInit ();
  233. gv.EndInit ();
  234. gv.Viewport = new Rectangle (0, 0, 20, 10);
  235. // origin should be bottom left
  236. Point botLeft = gv.GraphSpaceToScreen (new PointF (0, 0));
  237. Assert.Equal (0, botLeft.X);
  238. Assert.Equal (9, botLeft.Y); // row 9 of the view is the bottom left
  239. gv.MarginLeft = 1;
  240. // With a margin of 1 the origin should be at x=1 y= 9
  241. botLeft = gv.GraphSpaceToScreen (new PointF (0, 0));
  242. Assert.Equal (1, botLeft.X);
  243. Assert.Equal (9, botLeft.Y); // row 9 of the view is the bottom left
  244. gv.MarginLeft = 1;
  245. gv.MarginBottom = 1;
  246. // With a margin of 1 in both directions the origin should be at x=1 y= 9
  247. botLeft = gv.GraphSpaceToScreen (new PointF (0, 0));
  248. Assert.Equal (1, botLeft.X);
  249. Assert.Equal (8, botLeft.Y); // row 8 of the view is the bottom left up 1 cell
  250. }
  251. [Fact]
  252. public void GraphSpaceToScreen_ScrollOffset ()
  253. {
  254. var gv = new GraphView ();
  255. gv.BeginInit ();
  256. gv.EndInit ();
  257. gv.Viewport = new Rectangle (0, 0, 20, 10);
  258. //graph is scrolled to present chart space -5 to 5 in both axes
  259. gv.ScrollOffset = new PointF (-5, -5);
  260. // origin should be right in the middle of the control
  261. Point botLeft = gv.GraphSpaceToScreen (new PointF (0, 0));
  262. Assert.Equal (5, botLeft.X);
  263. Assert.Equal (4, botLeft.Y);
  264. // along 2 and up 1 in graph space
  265. Point along2up1 = gv.GraphSpaceToScreen (new PointF (2, 1));
  266. Assert.Equal (7, along2up1.X);
  267. Assert.Equal (3, along2up1.Y);
  268. }
  269. [Fact]
  270. public void GraphSpaceToScreen_CustomCellSize ()
  271. {
  272. var gv = new GraphView ();
  273. gv.BeginInit ();
  274. gv.EndInit ();
  275. gv.Viewport = new Rectangle (0, 0, 20, 10);
  276. // Each cell of screen is responsible for rendering 5 units in graph data model
  277. // vertically and 1/4 horizontally
  278. gv.CellSize = new PointF (0.25f, 5);
  279. // origin should be bottom left
  280. Point botLeft = gv.GraphSpaceToScreen (new PointF (0, 0));
  281. Assert.Equal (0, botLeft.X);
  282. // row 9 of the view is the bottom left (height is 10 so 0,1,2,3..9)
  283. Assert.Equal (9, botLeft.Y);
  284. // along 2 and up 1 in graph space
  285. Point along2up1 = gv.GraphSpaceToScreen (new PointF (2, 1));
  286. Assert.Equal (8, along2up1.X);
  287. Assert.Equal (9, along2up1.Y);
  288. // Y value 4 should be rendered in bottom most row
  289. Assert.Equal (9, gv.GraphSpaceToScreen (new PointF (2, 4)).Y);
  290. // Cell height is 5 so this is the first point of graph space that should
  291. // be rendered in the graph in next row up (row 9)
  292. Assert.Equal (8, gv.GraphSpaceToScreen (new PointF (2, 5)).Y);
  293. // More boundary testing for this cell size
  294. Assert.Equal (8, gv.GraphSpaceToScreen (new PointF (2, 6)).Y);
  295. Assert.Equal (8, gv.GraphSpaceToScreen (new PointF (2, 7)).Y);
  296. Assert.Equal (8, gv.GraphSpaceToScreen (new PointF (2, 8)).Y);
  297. Assert.Equal (8, gv.GraphSpaceToScreen (new PointF (2, 9)).Y);
  298. Assert.Equal (7, gv.GraphSpaceToScreen (new PointF (2, 10)).Y);
  299. Assert.Equal (7, gv.GraphSpaceToScreen (new PointF (2, 11)).Y);
  300. }
  301. [Fact]
  302. public void GraphSpaceToScreen_CustomCellSize_WithScrollOffset ()
  303. {
  304. var gv = new GraphView ();
  305. gv.BeginInit ();
  306. gv.EndInit ();
  307. gv.Viewport = new Rectangle (0, 0, 20, 10);
  308. // Each cell of screen is responsible for rendering 5 units in graph data model
  309. // vertically and 1/4 horizontally
  310. gv.CellSize = new PointF (0.25f, 5);
  311. //graph is scrolled to present some negative chart (4 negative cols and 2 negative rows)
  312. gv.ScrollOffset = new PointF (-1, -10);
  313. // origin should be in the lower left (but not right at the bottom)
  314. Point botLeft = gv.GraphSpaceToScreen (new PointF (0, 0));
  315. Assert.Equal (4, botLeft.X);
  316. Assert.Equal (7, botLeft.Y);
  317. // along 2 and up 1 in graph space
  318. Point along2up1 = gv.GraphSpaceToScreen (new PointF (2, 1));
  319. Assert.Equal (12, along2up1.X);
  320. Assert.Equal (7, along2up1.Y);
  321. // More boundary testing for this cell size/offset
  322. Assert.Equal (6, gv.GraphSpaceToScreen (new PointF (2, 6)).Y);
  323. Assert.Equal (6, gv.GraphSpaceToScreen (new PointF (2, 7)).Y);
  324. Assert.Equal (6, gv.GraphSpaceToScreen (new PointF (2, 8)).Y);
  325. Assert.Equal (6, gv.GraphSpaceToScreen (new PointF (2, 9)).Y);
  326. Assert.Equal (5, gv.GraphSpaceToScreen (new PointF (2, 10)).Y);
  327. Assert.Equal (5, gv.GraphSpaceToScreen (new PointF (2, 11)).Y);
  328. }
  329. #endregion
  330. }
  331. public class SeriesTests
  332. {
  333. [Fact]
  334. [AutoInitShutdown]
  335. public void Series_GetsPassedCorrectViewport_AllAtOnce ()
  336. {
  337. var gv = new GraphView ();
  338. gv.BeginInit ();
  339. gv.EndInit ();
  340. gv.Viewport = new Rectangle (0, 0, 50, 30);
  341. //gv.Scheme = new Scheme ();
  342. var fullGraphBounds = RectangleF.Empty;
  343. var graphScreenBounds = Rectangle.Empty;
  344. var series = new FakeSeries (
  345. (v, s, g) =>
  346. {
  347. graphScreenBounds = s;
  348. fullGraphBounds = g;
  349. }
  350. );
  351. gv.Series.Add (series);
  352. gv.LayoutSubViews ();
  353. gv.Draw ();
  354. Assert.Equal (new RectangleF (0, 0, 50, 30), fullGraphBounds);
  355. Assert.Equal (new Rectangle (0, 0, 50, 30), graphScreenBounds);
  356. // Now we put a margin in
  357. // Graph should not spill into the margins
  358. gv.MarginBottom = 2;
  359. gv.MarginLeft = 5;
  360. // Even with a margin the graph should be drawn from
  361. // the origin, we just get less visible width/height
  362. gv.LayoutSubViews ();
  363. gv.SetNeedsDraw ();
  364. gv.Draw ();
  365. Assert.Equal (new RectangleF (0, 0, 45, 28), fullGraphBounds);
  366. // The screen space the graph will be rendered into should
  367. // not overspill the margins
  368. Assert.Equal (new Rectangle (5, 0, 45, 28), graphScreenBounds);
  369. // Shutdown must be called to safely clean up Application if Init has been called
  370. Application.Shutdown ();
  371. }
  372. /// <summary>
  373. /// Tests that the bounds passed to the ISeries for drawing into are correct even when the
  374. /// <see cref="GraphView.CellSize"/> results in multiple units of graph space being condensed into each cell of console
  375. /// </summary>
  376. [Fact]
  377. [AutoInitShutdown]
  378. public void Series_GetsPassedCorrectViewport_AllAtOnce_LargeCellSize ()
  379. {
  380. var gv = new GraphView ();
  381. gv.BeginInit ();
  382. gv.EndInit ();
  383. //gv.Scheme = new Scheme ();
  384. gv.Viewport = new Rectangle (0, 0, 50, 30);
  385. // the larger the cell size the more condensed (smaller) the graph space is
  386. gv.CellSize = new PointF (2, 5);
  387. var fullGraphBounds = RectangleF.Empty;
  388. var graphScreenBounds = Rectangle.Empty;
  389. var series = new FakeSeries (
  390. (v, s, g) =>
  391. {
  392. graphScreenBounds = s;
  393. fullGraphBounds = g;
  394. }
  395. );
  396. gv.Series.Add (series);
  397. gv.LayoutSubViews ();
  398. gv.Draw ();
  399. // Since each cell of the console is 2x5 of graph space the graph
  400. // bounds to be rendered are larger
  401. Assert.Equal (new RectangleF (0, 0, 100, 150), fullGraphBounds);
  402. Assert.Equal (new Rectangle (0, 0, 50, 30), graphScreenBounds);
  403. // Graph should not spill into the margins
  404. gv.MarginBottom = 2;
  405. gv.MarginLeft = 5;
  406. // Even with a margin the graph should be drawn from
  407. // the origin, we just get less visible width/height
  408. gv.LayoutSubViews ();
  409. gv.SetNeedsDraw ();
  410. gv.Draw ();
  411. Assert.Equal (new RectangleF (0, 0, 90, 140), fullGraphBounds);
  412. // The screen space the graph will be rendered into should
  413. // not overspill the margins
  414. Assert.Equal (new Rectangle (5, 0, 45, 28), graphScreenBounds);
  415. // Shutdown must be called to safely clean up Application if Init has been called
  416. Application.Shutdown ();
  417. }
  418. private class FakeSeries : ISeries
  419. {
  420. private readonly Action<GraphView, Rectangle, RectangleF> _drawSeries;
  421. public FakeSeries (
  422. Action<GraphView, Rectangle, RectangleF> drawSeries
  423. )
  424. {
  425. _drawSeries = drawSeries;
  426. }
  427. public void DrawSeries (GraphView graph, Rectangle bounds, RectangleF graphBounds) { _drawSeries (graph, bounds, graphBounds); }
  428. }
  429. }
  430. public class MultiBarSeriesTests
  431. {
  432. private readonly ITestOutputHelper _output;
  433. public MultiBarSeriesTests (ITestOutputHelper output) { _output = output; }
  434. [Fact]
  435. public void MultiBarSeries_BarSpacing ()
  436. {
  437. // Creates clusters of 5 adjacent bars with 2 spaces between clusters
  438. var series = new MultiBarSeries (5, 7, 1);
  439. Assert.Equal (5, series.SubSeries.Count);
  440. Assert.Equal (0, series.SubSeries.ElementAt (0).Offset);
  441. Assert.Equal (1, series.SubSeries.ElementAt (1).Offset);
  442. Assert.Equal (2, series.SubSeries.ElementAt (2).Offset);
  443. Assert.Equal (3, series.SubSeries.ElementAt (3).Offset);
  444. Assert.Equal (4, series.SubSeries.ElementAt (4).Offset);
  445. }
  446. [Fact]
  447. public void MultiBarSeriesAddValues_WrongNumber ()
  448. {
  449. // user asks for 3 bars per category
  450. var series = new MultiBarSeries (3, 7, 1);
  451. var ex = Assert.Throws<ArgumentException> (() => series.AddBars ("Cars", (Rune)'#', 1));
  452. Assert.Equal (
  453. "Number of values must match the number of bars per category (Parameter 'values')",
  454. ex.Message
  455. );
  456. }
  457. [Fact]
  458. public void MultiBarSeriesColors_RightNumber ()
  459. {
  460. Attribute [] colors =
  461. {
  462. new (Color.Green, Color.Black), new (Color.Green, Color.White), new (Color.BrightYellow, Color.White)
  463. };
  464. // user passes 3 colors and asks for 3 bars
  465. var series = new MultiBarSeries (3, 7, 1, colors);
  466. Assert.Equal (series.SubSeries.ElementAt (0).OverrideBarColor, colors [0]);
  467. Assert.Equal (series.SubSeries.ElementAt (1).OverrideBarColor, colors [1]);
  468. Assert.Equal (series.SubSeries.ElementAt (2).OverrideBarColor, colors [2]);
  469. // Shutdown must be called to safely clean up Application if Init has been called
  470. Application.Shutdown ();
  471. }
  472. [Fact]
  473. public void MultiBarSeriesColors_WrongNumber ()
  474. {
  475. Attribute [] colors = { new (Color.Green, Color.Black) };
  476. // user passes 1 color only but asks for 5 bars
  477. var ex = Assert.Throws<ArgumentException> (() => new MultiBarSeries (5, 7, 1, colors));
  478. Assert.Equal (
  479. "Number of colors must match the number of bars (Parameter 'numberOfBarsPerCategory')",
  480. ex.Message
  481. );
  482. // Shutdown must be called to safely clean up Application if Init has been called
  483. Application.Shutdown ();
  484. }
  485. [Fact]
  486. [AutoInitShutdown]
  487. public void TestRendering_MultibarSeries ()
  488. {
  489. var gv = new GraphView ();
  490. //gv.Scheme = new Scheme ();
  491. // y axis goes from 0.1 to 1 across 10 console rows
  492. // x axis goes from 0 to 20 across 20 console columns
  493. gv.Viewport = new Rectangle (0, 0, 20, 10);
  494. gv.CellSize = new PointF (1f, 0.1f);
  495. gv.MarginBottom = 1;
  496. gv.MarginLeft = 1;
  497. var multibarSeries = new MultiBarSeries (2, 4, 1);
  498. //nudge them left to avoid float rounding errors at the boundaries of cells
  499. foreach (BarSeries sub in multibarSeries.SubSeries)
  500. {
  501. sub.Offset -= 0.001f;
  502. }
  503. gv.Series.Add (multibarSeries);
  504. FakeHAxis fakeXAxis;
  505. // don't show axis labels that means any labels
  506. // that appear are explicitly from the bars
  507. gv.AxisX = fakeXAxis = new FakeHAxis { Increment = 0 };
  508. gv.AxisY = new FakeVAxis { Increment = 0 };
  509. gv.LayoutSubViews ();
  510. gv.Draw ();
  511. // Since bar series has no bars yet no labels should be displayed
  512. Assert.Empty (fakeXAxis.LabelPoints);
  513. multibarSeries.AddBars ("hey", (Rune)'M', 0.5001f, 0.5001f);
  514. fakeXAxis.LabelPoints.Clear ();
  515. gv.LayoutSubViews ();
  516. gv.SetNeedsDraw ();
  517. gv.Draw ();
  518. Assert.Equal (4, fakeXAxis.LabelPoints.Single ());
  519. multibarSeries.AddBars ("there", (Rune)'M', 0.24999f, 0.74999f);
  520. multibarSeries.AddBars ("bob", (Rune)'M', 1, 2);
  521. fakeXAxis.LabelPoints.Clear ();
  522. gv.LayoutSubViews ();
  523. gv.SetNeedsDraw ();
  524. gv.SetClipToScreen ();
  525. gv.Draw ();
  526. Assert.Equal (3, fakeXAxis.LabelPoints.Count);
  527. Assert.Equal (4, fakeXAxis.LabelPoints [0]);
  528. Assert.Equal (8, fakeXAxis.LabelPoints [1]);
  529. Assert.Equal (12, fakeXAxis.LabelPoints [2]);
  530. var looksLike =
  531. @"
  532. │ MM
  533. │ M MM
  534. │ M MM
  535. │ MM M MM
  536. │ MM M MM
  537. │ MM M MM
  538. │ MM MM MM
  539. │ MM MM MM
  540. ┼──┬M──┬M──┬M──────
  541. heytherebob ";
  542. DriverAssert.AssertDriverContentsAre (looksLike, _output);
  543. // Shutdown must be called to safely clean up Application if Init has been called
  544. Application.Shutdown ();
  545. }
  546. }
  547. public class BarSeriesTests : FakeDriverBase
  548. {
  549. [Fact]
  550. public void TestOneLongOneShortHorizontalBars_WithOffset ()
  551. {
  552. GraphView graph = GetGraph (out FakeBarSeries barSeries, out FakeHAxis axisX, out FakeVAxis axisY);
  553. graph.Driver = CreateFakeDriver ();
  554. graph.Draw ();
  555. // no bars
  556. Assert.Empty (barSeries.BarScreenStarts);
  557. Assert.Empty (axisX.LabelPoints);
  558. Assert.Empty (axisY.LabelPoints);
  559. // 0.1 units of graph y fit every screen row
  560. // so 1 unit of graph y space is 10 screen rows
  561. graph.CellSize = new PointF (0.5f, 0.1f);
  562. // Start bar 3 screen units up (y = height-3)
  563. barSeries.Offset = 0.25f;
  564. // 1 bar every 3 rows of screen
  565. barSeries.BarEvery = 0.3f;
  566. barSeries.Orientation = Orientation.Horizontal;
  567. // 1 bar that is very wide (100 graph units horizontally = screen pos 50 but bounded by screen)
  568. barSeries.Bars.Add (
  569. new BarSeriesBar ("hi1", new GraphCellToRender ((Rune)'.'), 100)
  570. );
  571. // 1 bar that is shorter
  572. barSeries.Bars.Add (
  573. new BarSeriesBar ("hi2", new GraphCellToRender ((Rune)'.'), 5)
  574. );
  575. // redraw graph
  576. graph.SetNeedsDraw ();
  577. graph.Draw ();
  578. // since bars are horizontal all have the same X start cordinates
  579. Assert.Equal (0, barSeries.BarScreenStarts [0].X);
  580. Assert.Equal (0, barSeries.BarScreenStarts [1].X);
  581. // bar goes all the way to the end so bumps up against right screen boundary
  582. // width of graph is 20
  583. Assert.Equal (19, barSeries.BarScreenEnds [0].X);
  584. // shorter bar is 5 graph units wide which is 10 screen units
  585. Assert.Equal (10, barSeries.BarScreenEnds [1].X);
  586. // first bar should be offset 6 screen units (0.25f + 0.3f graph units)
  587. // since height of control is 10 then first bar should be at screen row 4 (10-6)
  588. Assert.Equal (4, barSeries.BarScreenStarts [0].Y);
  589. // second bar should be offset 9 screen units (0.25f + 0.6f graph units)
  590. // since height of control is 10 then second bar should be at screen row 1 (10-9)
  591. Assert.Equal (1, barSeries.BarScreenStarts [1].Y);
  592. // both bars should have labels but on the y axis
  593. Assert.Equal (2, axisY.LabelPoints.Count);
  594. Assert.Empty (axisX.LabelPoints);
  595. // labels should align with the bars (same screen y axis point)
  596. Assert.Contains (4, axisY.LabelPoints);
  597. Assert.Contains (1, axisY.LabelPoints);
  598. // Shutdown must be called to safely clean up Application if Init has been called
  599. Application.Shutdown ();
  600. }
  601. [Fact]
  602. [AutoInitShutdown]
  603. public void TestTwoTallBars_WithOffset ()
  604. {
  605. GraphView graph = GetGraph (out FakeBarSeries barSeries, out FakeHAxis axisX, out FakeVAxis axisY);
  606. graph.Draw ();
  607. // no bars
  608. Assert.Empty (barSeries.BarScreenStarts);
  609. Assert.Empty (axisX.LabelPoints);
  610. Assert.Empty (axisY.LabelPoints);
  611. // 0.5 units of graph fit every screen cell
  612. // so 1 unit of graph space is 2 screen columns
  613. graph.CellSize = new PointF (0.5f, 0.1f);
  614. // Start bar 1 screen unit along
  615. barSeries.Offset = 0.5f;
  616. barSeries.BarEvery = 1f;
  617. barSeries.Bars.Add (
  618. new BarSeriesBar ("hi1", new GraphCellToRender ((Rune)'.'), 100)
  619. );
  620. barSeries.Bars.Add (
  621. new BarSeriesBar ("hi2", new GraphCellToRender ((Rune)'.'), 100)
  622. );
  623. barSeries.Orientation = Orientation.Vertical;
  624. // redraw graph
  625. graph.SetNeedsDraw ();
  626. graph.Draw ();
  627. // bar should be drawn at BarEvery 1f + offset 0.5f = 3 screen units
  628. Assert.Equal (3, barSeries.BarScreenStarts [0].X);
  629. Assert.Equal (3, barSeries.BarScreenEnds [0].X);
  630. // second bar should be BarEveryx2 = 2f + offset 0.5f = 5 screen units
  631. Assert.Equal (5, barSeries.BarScreenStarts [1].X);
  632. Assert.Equal (5, barSeries.BarScreenEnds [1].X);
  633. // both bars should have labels
  634. Assert.Equal (2, axisX.LabelPoints.Count);
  635. Assert.Contains (3, axisX.LabelPoints);
  636. Assert.Contains (5, axisX.LabelPoints);
  637. // bars are very tall but should not draw up off top of screen
  638. Assert.Equal (9, barSeries.BarScreenStarts [0].Y);
  639. Assert.Equal (0, barSeries.BarScreenEnds [0].Y);
  640. Assert.Equal (9, barSeries.BarScreenStarts [1].Y);
  641. Assert.Equal (0, barSeries.BarScreenEnds [1].Y);
  642. // Shutdown must be called to safely clean up Application if Init has been called
  643. Application.Shutdown ();
  644. }
  645. [Fact]
  646. [AutoInitShutdown]
  647. public void TestZeroHeightBar_WithName ()
  648. {
  649. GraphView graph = GetGraph (out FakeBarSeries barSeries, out FakeHAxis axisX, out FakeVAxis axisY);
  650. graph.Draw ();
  651. // no bars
  652. Assert.Empty (barSeries.BarScreenStarts);
  653. Assert.Empty (axisX.LabelPoints);
  654. Assert.Empty (axisY.LabelPoints);
  655. // bar of height 0
  656. barSeries.Bars.Add (new BarSeriesBar ("hi", new GraphCellToRender ((Rune)'.'), 0));
  657. barSeries.Orientation = Orientation.Vertical;
  658. // redraw graph
  659. graph.SetNeedsDraw ();
  660. graph.Draw ();
  661. // bar should not be drawn
  662. Assert.Empty (barSeries.BarScreenStarts);
  663. Assert.NotEmpty (axisX.LabelPoints);
  664. Assert.Empty (axisY.LabelPoints);
  665. // but bar name should be
  666. // Screen position x=2 because bars are drawn every 1f of
  667. // graph space and CellSize.X is 0.5f
  668. Assert.Contains (2, axisX.LabelPoints);
  669. // Shutdown must be called to safely clean up Application if Init has been called
  670. Application.Shutdown ();
  671. }
  672. private GraphView GetGraph (out FakeBarSeries series, out FakeHAxis axisX, out FakeVAxis axisY)
  673. {
  674. var gv = new GraphView ();
  675. gv.BeginInit ();
  676. gv.EndInit ();
  677. // y axis goes from 0.1 to 1 across 10 console rows
  678. // x axis goes from 0 to 10 across 20 console columns
  679. gv.Viewport = new Rectangle (0, 0, 20, 10);
  680. //gv.Scheme = new Scheme ();
  681. gv.CellSize = new PointF (0.5f, 0.1f);
  682. gv.Series.Add (series = new FakeBarSeries ());
  683. // don't show axis labels that means any labels
  684. // that appaer are explicitly from the bars
  685. gv.AxisX = axisX = new FakeHAxis { Increment = 0 };
  686. gv.AxisY = axisY = new FakeVAxis { Increment = 0 };
  687. return gv;
  688. }
  689. private class FakeBarSeries : BarSeries
  690. {
  691. public List<Point> BarScreenEnds { get; } = new ();
  692. public List<Point> BarScreenStarts { get; } = new ();
  693. public GraphCellToRender FinalColor { get; private set; }
  694. protected override GraphCellToRender AdjustColor (GraphCellToRender graphCellToRender) { return FinalColor = base.AdjustColor (graphCellToRender); }
  695. protected override void DrawBarLine (GraphView graph, Point start, Point end, BarSeriesBar beingDrawn)
  696. {
  697. base.DrawBarLine (graph, start, end, beingDrawn);
  698. BarScreenStarts.Add (start);
  699. BarScreenEnds.Add (end);
  700. }
  701. }
  702. }
  703. public class AxisTests
  704. {
  705. private GraphView GetGraph (out FakeHAxis axis) { return GetGraph (out axis, out _); }
  706. private GraphView GetGraph (out FakeVAxis axis) { return GetGraph (out _, out axis); }
  707. private GraphView GetGraph (out FakeHAxis axisX, out FakeVAxis axisY)
  708. {
  709. var gv = new GraphView ();
  710. gv.Viewport = new Rectangle (0, 0, 50, 30);
  711. // gv.Scheme = new Scheme ();
  712. // graph can't be completely empty or it won't draw
  713. gv.Series.Add (new ScatterSeries ());
  714. axisX = new FakeHAxis ();
  715. axisY = new FakeVAxis ();
  716. gv.AxisX = axisX;
  717. gv.AxisY = axisY;
  718. return gv;
  719. }
  720. #region HorizontalAxis Tests
  721. /// <summary>Tests that the horizontal axis is computed correctly and does not over spill it's bounds</summary>
  722. [Fact]
  723. [AutoInitShutdown]
  724. public void TestHAxisLocation_NoMargin ()
  725. {
  726. GraphView gv = GetGraph (out FakeHAxis axis);
  727. gv.LayoutSubViews ();
  728. gv.Draw ();
  729. Assert.DoesNotContain (new Point (-1, 29), axis.DrawAxisLinePoints);
  730. Assert.Contains (new Point (0, 29), axis.DrawAxisLinePoints);
  731. Assert.Contains (new Point (1, 29), axis.DrawAxisLinePoints);
  732. Assert.Contains (new Point (48, 29), axis.DrawAxisLinePoints);
  733. Assert.Contains (new Point (49, 29), axis.DrawAxisLinePoints);
  734. Assert.DoesNotContain (new Point (50, 29), axis.DrawAxisLinePoints);
  735. Assert.InRange (axis.LabelPoints.Max (), 0, 49);
  736. Assert.InRange (axis.LabelPoints.Min (), 0, 49);
  737. // Shutdown must be called to safely clean up Application if Init has been called
  738. Application.Shutdown ();
  739. }
  740. [Fact]
  741. [AutoInitShutdown]
  742. public void TestHAxisLocation_MarginBottom ()
  743. {
  744. GraphView gv = GetGraph (out FakeHAxis axis);
  745. gv.MarginBottom = 10;
  746. gv.LayoutSubViews ();
  747. gv.Draw ();
  748. Assert.DoesNotContain (new Point (-1, 19), axis.DrawAxisLinePoints);
  749. Assert.Contains (new Point (0, 19), axis.DrawAxisLinePoints);
  750. Assert.Contains (new Point (1, 19), axis.DrawAxisLinePoints);
  751. Assert.Contains (new Point (48, 19), axis.DrawAxisLinePoints);
  752. Assert.Contains (new Point (49, 19), axis.DrawAxisLinePoints);
  753. Assert.DoesNotContain (new Point (50, 19), axis.DrawAxisLinePoints);
  754. Assert.InRange (axis.LabelPoints.Max (), 0, 49);
  755. Assert.InRange (axis.LabelPoints.Min (), 0, 49);
  756. // Shutdown must be called to safely clean up Application if Init has been called
  757. Application.Shutdown ();
  758. }
  759. [Fact]
  760. [AutoInitShutdown]
  761. public void TestHAxisLocation_MarginLeft ()
  762. {
  763. GraphView gv = GetGraph (out FakeHAxis axis);
  764. gv.MarginLeft = 5;
  765. gv.LayoutSubViews ();
  766. gv.Draw ();
  767. Assert.DoesNotContain (new Point (4, 29), axis.DrawAxisLinePoints);
  768. Assert.Contains (new Point (5, 29), axis.DrawAxisLinePoints);
  769. Assert.Contains (new Point (6, 29), axis.DrawAxisLinePoints);
  770. Assert.Contains (new Point (48, 29), axis.DrawAxisLinePoints);
  771. Assert.Contains (new Point (49, 29), axis.DrawAxisLinePoints);
  772. Assert.DoesNotContain (new Point (50, 29), axis.DrawAxisLinePoints);
  773. // Axis lables should not be drawn in the margin
  774. Assert.InRange (axis.LabelPoints.Max (), 5, 49);
  775. Assert.InRange (axis.LabelPoints.Min (), 5, 49);
  776. // Shutdown must be called to safely clean up Application if Init has been called
  777. Application.Shutdown ();
  778. }
  779. #endregion
  780. #region VerticalAxisTests
  781. /// <summary>Tests that the horizontal axis is computed correctly and does not over spill it's bounds</summary>
  782. [Fact]
  783. [AutoInitShutdown]
  784. public void TestVAxisLocation_NoMargin ()
  785. {
  786. GraphView gv = GetGraph (out FakeVAxis axis);
  787. gv.LayoutSubViews ();
  788. gv.Draw ();
  789. Assert.DoesNotContain (new Point (0, -1), axis.DrawAxisLinePoints);
  790. Assert.Contains (new Point (0, 1), axis.DrawAxisLinePoints);
  791. Assert.Contains (new Point (0, 2), axis.DrawAxisLinePoints);
  792. Assert.Contains (new Point (0, 28), axis.DrawAxisLinePoints);
  793. Assert.Contains (new Point (0, 29), axis.DrawAxisLinePoints);
  794. Assert.DoesNotContain (new Point (0, 30), axis.DrawAxisLinePoints);
  795. Assert.InRange (axis.LabelPoints.Max (), 0, 29);
  796. Assert.InRange (axis.LabelPoints.Min (), 0, 29);
  797. // Shutdown must be called to safely clean up Application if Init has been called
  798. Application.Shutdown ();
  799. }
  800. [Fact]
  801. [AutoInitShutdown]
  802. public void TestVAxisLocation_MarginBottom ()
  803. {
  804. GraphView gv = GetGraph (out FakeVAxis axis);
  805. gv.MarginBottom = 10;
  806. gv.LayoutSubViews ();
  807. gv.Draw ();
  808. Assert.DoesNotContain (new Point (0, -1), axis.DrawAxisLinePoints);
  809. Assert.Contains (new Point (0, 1), axis.DrawAxisLinePoints);
  810. Assert.Contains (new Point (0, 2), axis.DrawAxisLinePoints);
  811. Assert.Contains (new Point (0, 18), axis.DrawAxisLinePoints);
  812. Assert.Contains (new Point (0, 19), axis.DrawAxisLinePoints);
  813. Assert.DoesNotContain (new Point (0, 20), axis.DrawAxisLinePoints);
  814. // Labels should not be drawn into the axis
  815. Assert.InRange (axis.LabelPoints.Max (), 0, 19);
  816. Assert.InRange (axis.LabelPoints.Min (), 0, 19);
  817. // Shutdown must be called to safely clean up Application if Init has been called
  818. Application.Shutdown ();
  819. }
  820. [Fact]
  821. [AutoInitShutdown]
  822. public void TestVAxisLocation_MarginLeft ()
  823. {
  824. GraphView gv = GetGraph (out FakeVAxis axis);
  825. gv.MarginLeft = 5;
  826. gv.LayoutSubViews ();
  827. gv.Draw ();
  828. Assert.DoesNotContain (new Point (5, -1), axis.DrawAxisLinePoints);
  829. Assert.Contains (new Point (5, 1), axis.DrawAxisLinePoints);
  830. Assert.Contains (new Point (5, 2), axis.DrawAxisLinePoints);
  831. Assert.Contains (new Point (5, 28), axis.DrawAxisLinePoints);
  832. Assert.Contains (new Point (5, 29), axis.DrawAxisLinePoints);
  833. Assert.DoesNotContain (new Point (5, 30), axis.DrawAxisLinePoints);
  834. Assert.InRange (axis.LabelPoints.Max (), 0, 29);
  835. Assert.InRange (axis.LabelPoints.Min (), 0, 29);
  836. // Shutdown must be called to safely clean up Application if Init has been called
  837. Application.Shutdown ();
  838. }
  839. #endregion
  840. }
  841. public class TextAnnotationTests
  842. {
  843. private readonly ITestOutputHelper _output;
  844. public TextAnnotationTests (ITestOutputHelper output) { _output = output; }
  845. [Theory]
  846. [InlineData (null)]
  847. [InlineData (" ")]
  848. [InlineData ("\t\t")]
  849. [AutoInitShutdown]
  850. public void TestTextAnnotation_EmptyText (string whitespace)
  851. {
  852. GraphView gv = GraphViewTests.GetGraph ();
  853. gv.Annotations.Add (
  854. new TextAnnotation { Text = whitespace, GraphPosition = new PointF (4, 2) }
  855. );
  856. // add a point a bit further along the graph so if the whitespace were rendered
  857. // the test would pick it up (AssertDriverContentsAre ignores trailing whitespace on lines)
  858. var points = new ScatterSeries ();
  859. points.Points.Add (new PointF (7, 2));
  860. gv.Series.Add (points);
  861. gv.LayoutSubViews ();
  862. gv.Draw ();
  863. var expected =
  864. @$"
  865. ┤ {Glyphs.Dot}
  866. 0┼┬┬┬┬┬┬┬┬
  867. 0 5";
  868. DriverAssert.AssertDriverContentsAre (expected, _output);
  869. // Shutdown must be called to safely clean up Application if Init has been called
  870. Application.Shutdown ();
  871. }
  872. [Fact]
  873. [AutoInitShutdown]
  874. public void TestTextAnnotation_GraphUnits ()
  875. {
  876. GraphView gv = GraphViewTests.GetGraph ();
  877. gv.Annotations.Add (
  878. new TextAnnotation { Text = "hey!", GraphPosition = new PointF (2, 2) }
  879. );
  880. gv.LayoutSubViews ();
  881. gv.Draw ();
  882. var expected =
  883. @"
  884. ┤ hey!
  885. 0┼┬┬┬┬┬┬┬┬
  886. 0 5";
  887. DriverAssert.AssertDriverContentsAre (expected, _output);
  888. // user scrolls up one unit of graph space
  889. gv.ScrollOffset = new PointF (0, 1f);
  890. gv.SetNeedsDraw ();
  891. gv.SetClipToScreen ();
  892. gv.Draw ();
  893. // we expect the text annotation to go down one line since
  894. // the scroll offset means that that point of graph space is
  895. // lower down in the view. Note the 1 on the axis too, our viewport
  896. // (excluding margins) now shows y of 1 to 4 (previously 0 to 5)
  897. expected =
  898. @"
  899. ┤ hey!
  900. 1┼┬┬┬┬┬┬┬┬
  901. 0 5";
  902. DriverAssert.AssertDriverContentsAre (expected, _output);
  903. // Shutdown must be called to safely clean up Application if Init has been called
  904. Application.Shutdown ();
  905. }
  906. [Fact]
  907. [AutoInitShutdown]
  908. public void TestTextAnnotation_LongText ()
  909. {
  910. GraphView gv = GraphViewTests.GetGraph ();
  911. gv.Annotations.Add (
  912. new TextAnnotation
  913. {
  914. Text = "hey there partner hows it going boy its great", GraphPosition = new PointF (2, 2)
  915. }
  916. );
  917. gv.LayoutSubViews ();
  918. gv.SetNeedsDraw ();
  919. gv.Draw ();
  920. // long text should get truncated
  921. // margin takes up 1 units
  922. // the GraphPosition of the anntation is 2
  923. // Leaving 7 characters of the annotation renderable (including space)
  924. var expected =
  925. @"
  926. ┤ hey the
  927. 0┼┬┬┬┬┬┬┬┬
  928. 0 5";
  929. DriverAssert.AssertDriverContentsAre (expected, _output);
  930. // Shutdown must be called to safely clean up Application if Init has been called
  931. Application.Shutdown ();
  932. }
  933. [Fact]
  934. [AutoInitShutdown]
  935. public void TestTextAnnotation_Offscreen ()
  936. {
  937. GraphView gv = GraphViewTests.GetGraph ();
  938. gv.Annotations.Add (
  939. new TextAnnotation
  940. {
  941. Text = "hey there partner hows it going boy its great", GraphPosition = new PointF (9, 2)
  942. }
  943. );
  944. gv.LayoutSubViews ();
  945. gv.Draw ();
  946. // Text is off the screen (graph x axis runs to 8 not 9)
  947. var expected =
  948. @"
  949. 0┼┬┬┬┬┬┬┬┬
  950. 0 5";
  951. DriverAssert.AssertDriverContentsAre (expected, _output);
  952. // Shutdown must be called to safely clean up Application if Init has been called
  953. Application.Shutdown ();
  954. }
  955. [Fact]
  956. [AutoInitShutdown]
  957. public void TestTextAnnotation_ScreenUnits ()
  958. {
  959. GraphView gv = GraphViewTests.GetGraph ();
  960. gv.Annotations.Add (
  961. new TextAnnotation { Text = "hey!", ScreenPosition = new Point (3, 1) }
  962. );
  963. gv.LayoutSubViews ();
  964. gv.SetClipToScreen ();
  965. gv.Draw ();
  966. var expected =
  967. @"
  968. ┤ hey!
  969. 0┼┬┬┬┬┬┬┬┬
  970. 0 5";
  971. DriverAssert.AssertDriverContentsAre (expected, _output);
  972. // user scrolls up one unit of graph space
  973. gv.ScrollOffset = new PointF (0, 1f);
  974. gv.SetNeedsDraw ();
  975. gv.SetClipToScreen ();
  976. gv.Draw ();
  977. // we expect no change in the location of the annotation (only the axis label changes)
  978. // this is because screen units are constant and do not change as the viewport into
  979. // graph space scrolls to different areas of the graph
  980. expected =
  981. @"
  982. ┤ hey!
  983. 1┼┬┬┬┬┬┬┬┬
  984. 0 5";
  985. DriverAssert.AssertDriverContentsAre (expected, _output);
  986. // user scrolls up one unit of graph space
  987. gv.ScrollOffset = new PointF (0, 1f);
  988. gv.SetNeedsDraw ();
  989. gv.SetClipToScreen ();
  990. gv.Draw ();
  991. // we expect no change in the location of the annotation (only the axis label changes)
  992. // this is because screen units are constant and do not change as the viewport into
  993. // graph space scrolls to different areas of the graph
  994. expected =
  995. @"
  996. ┤ hey!
  997. 1┼┬┬┬┬┬┬┬┬
  998. 0 5";
  999. DriverAssert.AssertDriverContentsAre (expected, _output);
  1000. // Shutdown must be called to safely clean up Application if Init has been called
  1001. Application.Shutdown ();
  1002. }
  1003. }
  1004. public class LegendTests
  1005. {
  1006. private readonly ITestOutputHelper _output;
  1007. public LegendTests (ITestOutputHelper output) { _output = output; }
  1008. [Fact]
  1009. public void Constructors_Defaults ()
  1010. {
  1011. var legend = new LegendAnnotation ();
  1012. Assert.Equal (Rectangle.Empty, legend.Viewport);
  1013. Assert.Equal (Rectangle.Empty, legend.Frame);
  1014. Assert.Equal (LineStyle.Single, legend.BorderStyle);
  1015. Assert.False (legend.BeforeSeries);
  1016. var bounds = new Rectangle (1, 2, 10, 3);
  1017. legend = new LegendAnnotation (bounds);
  1018. Assert.Equal (new Rectangle (0, 0, 8, 1), legend.Viewport);
  1019. Assert.Equal (bounds, legend.Frame);
  1020. Assert.Equal (LineStyle.Single, legend.BorderStyle);
  1021. Assert.False (legend.BeforeSeries);
  1022. legend.BorderStyle = LineStyle.None;
  1023. Assert.Equal (new Rectangle (0, 0, 10, 3), legend.Viewport);
  1024. Assert.Equal (bounds, legend.Frame);
  1025. }
  1026. [Fact]
  1027. [AutoInitShutdown]
  1028. public void LegendNormalUsage_WithBorder ()
  1029. {
  1030. GraphView gv = GraphViewTests.GetGraph ();
  1031. var legend = new LegendAnnotation (new Rectangle (2, 0, 5, 3));
  1032. legend.AddEntry (new GraphCellToRender ((Rune)'A'), "Ant");
  1033. legend.AddEntry (new GraphCellToRender ((Rune)'B'), "Bat");
  1034. gv.Annotations.Add (legend);
  1035. gv.Layout ();
  1036. gv.Draw ();
  1037. var expected =
  1038. @"
  1039. │┌───┐
  1040. ┤│AAn│
  1041. ┤└───┘
  1042. 0┼┬┬┬┬┬┬┬┬
  1043. 0 5";
  1044. DriverAssert.AssertDriverContentsAre (expected, _output);
  1045. // Shutdown must be called to safely clean up Application if Init has been called
  1046. Application.Shutdown ();
  1047. }
  1048. [Fact]
  1049. [AutoInitShutdown]
  1050. public void LegendNormalUsage_WithoutBorder ()
  1051. {
  1052. GraphView gv = GraphViewTests.GetGraph ();
  1053. var legend = new LegendAnnotation (new Rectangle (2, 0, 5, 3));
  1054. legend.AddEntry (new GraphCellToRender ((Rune)'A'), "Ant");
  1055. legend.AddEntry (new GraphCellToRender ((Rune)'B'), "?"); // this will exercise pad
  1056. legend.AddEntry (new GraphCellToRender ((Rune)'C'), "Cat");
  1057. legend.AddEntry (new GraphCellToRender ((Rune)'H'), "Hattter"); // not enough space for this oen
  1058. legend.BorderStyle = LineStyle.None;
  1059. gv.Annotations.Add (legend);
  1060. gv.Draw ();
  1061. var expected =
  1062. @"
  1063. │AAnt
  1064. ┤B?
  1065. ┤CCat
  1066. 0┼┬┬┬┬┬┬┬┬
  1067. 0 5";
  1068. DriverAssert.AssertDriverContentsAre (expected, _output);
  1069. // Shutdown must be called to safely clean up Application if Init has been called
  1070. Application.Shutdown ();
  1071. }
  1072. }
  1073. public class PathAnnotationTests
  1074. {
  1075. private readonly ITestOutputHelper _output;
  1076. public PathAnnotationTests (ITestOutputHelper output) { _output = output; }
  1077. [Fact]
  1078. public void MarginBottom_BiggerThanHeight_ExpectBlankGraph ()
  1079. {
  1080. GraphView gv = GraphViewTests.GetGraph ();
  1081. gv.Height = 10;
  1082. gv.MarginBottom = 20;
  1083. gv.Series.Add (
  1084. new ScatterSeries { Points = { new PointF (1, 1), new PointF (5, 0) } }
  1085. );
  1086. gv.LayoutSubViews ();
  1087. gv.Draw ();
  1088. var expected =
  1089. @"
  1090. ";
  1091. DriverAssert.AssertDriverContentsAre (expected, _output, gv.Driver);
  1092. // Shutdown must be called to safely clean up Application if Init has been called
  1093. Application.Shutdown ();
  1094. }
  1095. [Fact]
  1096. public void MarginLeft_BiggerThanWidth_ExpectBlankGraph ()
  1097. {
  1098. GraphView gv = GraphViewTests.GetGraph ();
  1099. gv.Width = 10;
  1100. gv.MarginLeft = 20;
  1101. gv.Series.Add (
  1102. new ScatterSeries { Points = { new PointF (1, 1), new PointF (5, 0) } }
  1103. );
  1104. gv.LayoutSubViews ();
  1105. gv.Draw ();
  1106. var expected =
  1107. @"
  1108. ";
  1109. DriverAssert.AssertDriverContentsAre (expected, _output, gv.Driver);
  1110. // Shutdown must be called to safely clean up Application if Init has been called
  1111. Application.Shutdown ();
  1112. }
  1113. [Fact]
  1114. [AutoInitShutdown]
  1115. public void PathAnnotation_Box ()
  1116. {
  1117. GraphView gv = GraphViewTests.GetGraph ();
  1118. var path = new PathAnnotation ();
  1119. path.Points.Add (new PointF (1, 1));
  1120. path.Points.Add (new PointF (1, 3));
  1121. path.Points.Add (new PointF (6, 3));
  1122. path.Points.Add (new PointF (6, 1));
  1123. // list the starting point again so that it draws a complete square
  1124. // (otherwise it will miss out the last line along the bottom)
  1125. path.Points.Add (new PointF (1, 1));
  1126. gv.Annotations.Add (path);
  1127. gv.LayoutSubViews ();
  1128. gv.Draw ();
  1129. var expected =
  1130. @"
  1131. │......
  1132. ┤. .
  1133. ┤......
  1134. 0┼┬┬┬┬┬┬┬┬
  1135. 0 5";
  1136. DriverAssert.AssertDriverContentsAre (expected, _output);
  1137. // Shutdown must be called to safely clean up Application if Init has been called
  1138. Application.Shutdown ();
  1139. }
  1140. [Fact]
  1141. [AutoInitShutdown]
  1142. public void PathAnnotation_Diamond ()
  1143. {
  1144. GraphView gv = GraphViewTests.GetGraph ();
  1145. var path = new PathAnnotation ();
  1146. path.Points.Add (new PointF (1, 2));
  1147. path.Points.Add (new PointF (3, 3));
  1148. path.Points.Add (new PointF (6, 2));
  1149. path.Points.Add (new PointF (3, 1));
  1150. // list the starting point again to close the shape
  1151. path.Points.Add (new PointF (1, 2));
  1152. gv.Annotations.Add (path);
  1153. gv.LayoutSubViews ();
  1154. gv.Draw ();
  1155. var expected =
  1156. @"
  1157. │ ..
  1158. ┤.. ..
  1159. ┤ ...
  1160. 0┼┬┬┬┬┬┬┬┬
  1161. 0 5";
  1162. DriverAssert.AssertDriverContentsAre (expected, _output);
  1163. // Shutdown must be called to safely clean up Application if Init has been called
  1164. Application.Shutdown ();
  1165. }
  1166. [Theory]
  1167. [AutoInitShutdown]
  1168. [InlineData (true)]
  1169. [InlineData (false)]
  1170. public void ViewChangeText_RendersCorrectly (bool useFill)
  1171. {
  1172. // create a wide window
  1173. var mount = new View { Width = 100, Height = 100 };
  1174. var top = new Toplevel ();
  1175. try
  1176. {
  1177. // Create a view with a short text
  1178. var view = new View { Text = "ff", Width = 2, Height = 1 };
  1179. // Specify that the label should be very wide
  1180. if (useFill)
  1181. {
  1182. view.Width = Dim.Fill ();
  1183. }
  1184. else
  1185. {
  1186. view.Width = 100;
  1187. }
  1188. //put label into view
  1189. mount.Add (view);
  1190. //putting mount into Toplevel since changing size
  1191. top.Add (mount);
  1192. Application.Begin (top);
  1193. // render view
  1194. //view.Scheme = new Scheme ();
  1195. Assert.Equal (1, view.Height);
  1196. mount.SetNeedsDraw ();
  1197. mount.Draw ();
  1198. // should have the initial text
  1199. DriverAssert.AssertDriverContentsAre ("ff", null);
  1200. // change the text and redraw
  1201. view.Text = "ff1234";
  1202. mount.SetNeedsDraw ();
  1203. top.SetClipToScreen ();
  1204. mount.Draw ();
  1205. // should have the new text rendered
  1206. DriverAssert.AssertDriverContentsAre ("ff1234", null);
  1207. }
  1208. finally
  1209. {
  1210. top.Dispose ();
  1211. Application.Shutdown ();
  1212. }
  1213. }
  1214. [Fact]
  1215. [AutoInitShutdown]
  1216. public void XAxisLabels_With_MarginLeft ()
  1217. {
  1218. var gv = new GraphView { Viewport = new Rectangle (0, 0, 10, 7) };
  1219. gv.CellSize = new PointF (1, 0.5f);
  1220. gv.AxisY.Increment = 1;
  1221. gv.AxisY.ShowLabelsEvery = 1;
  1222. gv.Series.Add (
  1223. new ScatterSeries { Points = { new PointF (1, 1), new PointF (5, 0) } }
  1224. );
  1225. // reserve 3 cells of the left for the margin
  1226. gv.MarginLeft = 3;
  1227. gv.MarginBottom = 1;
  1228. gv.LayoutSubViews ();
  1229. gv.SetNeedsDraw ();
  1230. gv.Draw ();
  1231. var expected =
  1232. @$"
  1233. 2┤
  1234. 1┤{Glyphs.Dot}
  1235. 0┼┬┬┬┬{Glyphs.Dot}┬
  1236. 0 5
  1237. ";
  1238. DriverAssert.AssertDriverContentsAre (expected, _output);
  1239. // Shutdown must be called to safely clean up Application if Init has been called
  1240. Application.Shutdown ();
  1241. }
  1242. [Fact]
  1243. [AutoInitShutdown]
  1244. public void YAxisLabels_With_MarginBottom ()
  1245. {
  1246. var gv = new GraphView { Viewport = new Rectangle (0, 0, 10, 7) };
  1247. gv.CellSize = new PointF (1, 0.5f);
  1248. gv.AxisY.Increment = 1;
  1249. gv.AxisY.ShowLabelsEvery = 1;
  1250. gv.Series.Add (
  1251. new ScatterSeries { Points = { new PointF (1, 1), new PointF (5, 0) } }
  1252. );
  1253. // reserve 3 cells of the console for the margin
  1254. gv.MarginBottom = 3;
  1255. gv.MarginLeft = 1;
  1256. gv.LayoutSubViews ();
  1257. gv.SetNeedsDraw ();
  1258. gv.Draw ();
  1259. var expected =
  1260. @$"
  1261. 1┤{Glyphs.Dot}
  1262. 0┼┬┬┬┬{Glyphs.Dot}┬┬┬
  1263. 0 5
  1264. ";
  1265. DriverAssert.AssertDriverContentsAre (expected, _output);
  1266. // Shutdown must be called to safely clean up Application if Init has been called
  1267. Application.Shutdown ();
  1268. }
  1269. }
  1270. public class AxisIncrementToRenderTests
  1271. {
  1272. [Fact]
  1273. public void AxisIncrementToRenderTests_Constructor ()
  1274. {
  1275. var render = new AxisIncrementToRender (Orientation.Horizontal, 1, 6.6f);
  1276. Assert.Equal (Orientation.Horizontal, render.Orientation);
  1277. Assert.Equal (1, render.ScreenLocation);
  1278. Assert.Equal (6.6f, render.Value);
  1279. }
  1280. }