GraphViewTests.cs 44 KB

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