1. using System;
  2. using System.Collections.Generic;
  3. using Microsoft.Xna.Framework;
  4. using Microsoft.Xna.Framework.Graphics;
  5.  
  6. namespace XNATestGame
  7. {
  8.     public class Graph
  9.     {
  10.         public enum GraphType
  11.         {
  12.             Line,
  13.             Fill
  14.         }
  15.  
  16.         /// <summary>
  17.         /// Determines whether the drawn graph will be line only, or filled
  18.         /// </summary>
  19.         public GraphType Type { get; set; }
  20.  
  21.         /// <summary>
  22.         /// The bottom left position of the graph
  23.         /// </summary>
  24.         public Vector2 Position { get; set; }
  25.         /// <summary>
  26.         /// The size of the graph.
  27.         /// The graph values will be scaled horizontally to fill width (Size.X)
  28.         /// Vertically, the values will be scaled based on MaxValue property, where the position of the value that is equal to MaxValue will be Size.Y
  29.         /// </summary>
  30.         public Point Size { get; set; }
  31.  
  32.         /// <summary>
  33.         /// Determines the vertical scaling of the graph.
  34.         /// The value that is equal to MaxValue will be displayed at the top of the graph (at point Size.Y)
  35.         /// </summary>
  36.         public float MaxValue { get; set; }
  37.  
  38.         private Vector2 _scale = new Vector2(1.0f, 1.0f);
  39.  
  40.         BasicEffect _effect;
  41.         short[] lineListIndices;
  42.         short[] triangleStripIndices;
  43.  
  44.         public Graph(GraphicsDevice graphicsDevice, Point size)
  45.         {
  46.             _effect = new BasicEffect(graphicsDevice);
  47.             _effect.View = Matrix.CreateLookAt(Vector3.Backward, Vector3.Zero, Vector3.Up);
  48.             _effect.Projection = Matrix.CreateOrthographicOffCenter(0, (float)graphicsDevice.Viewport.Width, (float)graphicsDevice.Viewport.Height, 0, 1.0f, 1000.0f);
  49.             _effect.World = Matrix.Identity;
  50.  
  51.             _effect.VertexColorEnabled = true;
  52.  
  53.             this.MaxValue = 1;
  54.             this.Size = size;
  55.             if (size.Y <= 0)
  56.                 Size = new Point(size.X, 1);
  57.             if (size.X <= 0)
  58.                 Size = new Point(1, size.Y);
  59.  
  60.             this.Type = Graph.GraphType.Line;
  61.         }
  62.  
  63.         void UpdateWorld()
  64.         {
  65.             _effect.World = Matrix.CreateScale(_scale.X, _scale.Y, 1.0f)
  66.                           * Matrix.CreateRotationX(MathHelper.Pi) //flips the graph so that the higher values are above. Makes bottom left the graph origin.
  67.                           * Matrix.CreateTranslation(new Vector3(this.Position, 0));
  68.         }
  69.  
  70.         /// <summary>
  71.         /// Draws the values in given order, with specific color for each value
  72.         /// </summary>
  73.         /// <param name="values">Value/color pairs to draw, in order from left to right</param>
  74.         public void Draw(List<Tuple<float, Color>> values)
  75.         {
  76.             if (values.Count < 2)
  77.                 return;
  78.  
  79.             //creates scaling (for the transformation) based on the number of points to draw
  80.             float xScale = this.Size.X / (float)values.Count;
  81.             float yScale = this.Size.Y / MaxValue;
  82.  
  83.             _scale = new Vector2(xScale, yScale);
  84.             UpdateWorld();
  85.  
  86.             //different point lists for different types of graphs
  87.             if (this.Type == GraphType.Line)
  88.             {
  89.                 VertexPositionColor[] pointList = new VertexPositionColor[values.Count];
  90.                 for (int i = 0; i < values.Count; i++)
  91.                 {
  92.                     pointList[i] = new VertexPositionColor(new Vector3(i, values[i].Item1 < this.MaxValue ? values[i].Item1 : this.MaxValue, 0), values[i].Item2);
  93.                 }
  94.  
  95.                 DrawLineList(pointList);
  96.             }
  97.             else if (this.Type == GraphType.Fill)
  98.             {
  99.                 VertexPositionColor[] pointList = new VertexPositionColor[values.Count * 2];
  100.                 for (int i = 0; i < values.Count; i++)
  101.                 {
  102.                     //The vertices are created so that the triangles are inverted (back facing). When rotated they will become front facing.
  103.                     //This is done to avoid changing rasterizer state to CullMode.CullClockwiseFace.
  104.                     pointList[i * 2 + 1] = new VertexPositionColor(new Vector3(i, values[i].Item1 < this.MaxValue ? values[i].Item1 : this.MaxValue, 0), values[i].Item2);
  105.                     pointList[i * 2] = new VertexPositionColor(new Vector3(i, 0, 0), values[i].Item2);
  106.                 }
  107.  
  108.                 DrawTriangleStrip(pointList);
  109.             }
  110.         }
  111.  
  112.         /// <summary>
  113.         /// Draws the values in given order, in specified color
  114.         /// </summary>
  115.         /// <param name="values">Values to draw, in order from left to right</param>
  116.         /// <param name="color">Color of the entire graph</param>
  117.         public void Draw(List<float> values, Color color)
  118.         {
  119.             if (values.Count < 2)
  120.                 return;
  121.  
  122.             float xScale = this.Size.X / (float)values.Count;
  123.             float yScale = this.Size.Y / MaxValue;
  124.  
  125.             _scale = new Vector2(xScale, yScale);
  126.             UpdateWorld();
  127.  
  128.             if (this.Type == GraphType.Line)
  129.             {
  130.                 VertexPositionColor[] pointList = new VertexPositionColor[values.Count];
  131.                 for (int i = 0; i < values.Count; i++)
  132.                 {
  133.                     pointList[i] = new VertexPositionColor(new Vector3(i, values[i] < this.MaxValue ? values[i] : this.MaxValue, 0), color);
  134.                 }
  135.  
  136.                 DrawLineList(pointList);
  137.             }
  138.             else if (this.Type == GraphType.Fill)
  139.             {
  140.                 VertexPositionColor[] pointList = new VertexPositionColor[values.Count * 2];
  141.                 for (int i = 0; i < values.Count; i++)
  142.                 {
  143.                     pointList[i * 2 + 1] = new VertexPositionColor(new Vector3(i, values[i] < this.MaxValue ? values[i] : this.MaxValue, 0), color);
  144.                     pointList[i * 2] = new VertexPositionColor(new Vector3(i, 0, 0), color);
  145.                 }
  146.  
  147.                 DrawTriangleStrip(pointList);
  148.             }
  149.         }
  150.  
  151.         void DrawLineList(VertexPositionColor[] pointList)
  152.         {
  153.             //indices updated only need to be updated when the number of points has changed
  154.             if (lineListIndices == null || lineListIndices.Length != ((pointList.Length * 2) - 2))
  155.             {
  156.                 lineListIndices = new short[(pointList.Length * 2) - 2];
  157.                 for (int i = 0; i < pointList.Length - 1; i++)
  158.                 {
  159.                     lineListIndices[i * 2] = (short)(i);
  160.                     lineListIndices[(i * 2) + 1] = (short)(i + 1);
  161.                 }
  162.             }
  163.  
  164.             foreach (EffectPass pass in _effect.CurrentTechnique.Passes)
  165.             {
  166.                 pass.Apply();
  167.                 _effect.GraphicsDevice.DrawUserIndexedPrimitives<VertexPositionColor>(
  168.                     PrimitiveType.LineList,
  169.                     pointList,
  170.                     0,
  171.                     pointList.Length,
  172.                     lineListIndices,
  173.                     0,
  174.                     pointList.Length - 1
  175.                 );
  176.             }
  177.         }
  178.  
  179.         void DrawTriangleStrip(VertexPositionColor[] pointList)
  180.         {
  181.             if (triangleStripIndices == null || triangleStripIndices.Length != pointList.Length)
  182.             {
  183.                 triangleStripIndices = new short[pointList.Length];
  184.                 for (int i = 0; i < pointList.Length; i++)
  185.                     triangleStripIndices[i] = (short)i;
  186.             }
  187.  
  188.             foreach (EffectPass pass in _effect.CurrentTechnique.Passes)
  189.             {
  190.                 pass.Apply();
  191.                 _effect.GraphicsDevice.DrawUserIndexedPrimitives<VertexPositionColor>(
  192.                     PrimitiveType.TriangleStrip,
  193.                     pointList,
  194.                     0,
  195.                     pointList.Length,
  196.                     triangleStripIndices,
  197.                     0,
  198.                     pointList.Length - 2
  199.                 );
  200.             }
  201.         }
  202.     }
  203. }