GraphViewTests.cs 47 KB

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