示例#1
0
        private void btnGraph_Click(object sender, EventArgs e)
        {
            CalculationsRetribution          retCalc  = new CalculationsRetribution();
            CharacterCalculationsRetribution baseCalc = retCalc.GetCharacterCalculations(Character) as CharacterCalculationsRetribution;
            Bitmap   _prerenderedGraph = global::Rawr.Retribution.Properties.Resources.GraphBase;
            Graphics g = Graphics.FromImage(_prerenderedGraph);

            g.SmoothingMode     = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
            g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
            float graphHeight = 700f, graphStart = 100f;

            Color[] colors = new Color[] {
                Color.FromArgb(127, 202, 180, 96),  // Strength
                Color.FromArgb(127, 101, 225, 240), // Agility
                Color.FromArgb(127, 0, 4, 3),       // Attack Power
                Color.FromArgb(127, 123, 238, 199), // Crit Rating
                Color.FromArgb(127, 45, 112, 63),   // Hit Rating
                Color.FromArgb(127, 121, 72, 210),  //Expertise Rating
                Color.FromArgb(127, 217, 100, 54),  // Haste Rating
                Color.FromArgb(127, 210, 72, 195),  // Armor Penetration
                Color.FromArgb(127, 206, 189, 191), // Spell Damage
            };
            Stats[] statsList = new Stats[] {
                new Stats()
                {
                    Strength = 10
                },
                new Stats()
                {
                    Agility = 10
                },
                new Stats()
                {
                    AttackPower = 20
                },
                new Stats()
                {
                    CritRating = 10
                },
                new Stats()
                {
                    HitRating = 10
                },
                new Stats()
                {
                    ExpertiseRating = 10
                },
                new Stats()
                {
                    HasteRating = 10
                },
                new Stats()
                {
                    ArmorPenetration = 66.667f
                },
                new Stats()
                {
                    SpellDamageRating = 11.17f
                },
            };

            for (int index = 0; index < statsList.Length; index++)
            {
                Stats   newStats = new Stats();
                Point[] points   = new Point[100];
                for (int count = 0; count < 100; count++)
                {
                    newStats = newStats + statsList[index];

                    CharacterCalculationsRetribution currentCalc = retCalc.GetCharacterCalculations(Character, new Item()
                    {
                        Stats = newStats
                    }) as CharacterCalculationsRetribution;
                    float overallPoints = currentCalc.DPSPoints - baseCalc.DPSPoints;

                    if ((graphHeight - overallPoints) > 16)
                    {
                        points[count] = new Point(Convert.ToInt32(graphStart + count * 5), (Convert.ToInt32(graphHeight - overallPoints)));
                    }
                    else
                    {
                        points[count] = points[count - 1];
                    }
                }
                Brush statBrush = new SolidBrush(colors[index]);
                g.DrawLines(new Pen(statBrush, 3), points);
            }

            #region Graph Ticks
            float graphWidth = 500f;// this.Width - 150f;
            float graphEnd   = graphStart + graphWidth;
            //float graphStartY = 16f;
            float   maxScale = 100f;
            float[] ticks    = new float[] { (float)Math.Round(graphStart + graphWidth * 0.5f),
                                             (float)Math.Round(graphStart + graphWidth * 0.75f),
                                             (float)Math.Round(graphStart + graphWidth * 0.25f),
                                             (float)Math.Round(graphStart + graphWidth * 0.125f),
                                             (float)Math.Round(graphStart + graphWidth * 0.375f),
                                             (float)Math.Round(graphStart + graphWidth * 0.625f),
                                             (float)Math.Round(graphStart + graphWidth * 0.875f) };
            Pen          black200   = new Pen(Color.FromArgb(200, 0, 0, 0));
            Pen          black150   = new Pen(Color.FromArgb(150, 0, 0, 0));
            Pen          black75    = new Pen(Color.FromArgb(75, 0, 0, 0));
            Pen          black50    = new Pen(Color.FromArgb(50, 0, 0, 0));
            Pen          black25    = new Pen(Color.FromArgb(25, 0, 0, 0));
            StringFormat formatTick = new StringFormat();
            formatTick.LineAlignment = StringAlignment.Far;
            formatTick.Alignment     = StringAlignment.Center;
            Brush black200brush = new SolidBrush(Color.FromArgb(200, 0, 0, 0));
            Brush black150brush = new SolidBrush(Color.FromArgb(150, 0, 0, 0));
            Brush black75brush  = new SolidBrush(Color.FromArgb(75, 0, 0, 0));
            Brush black50brush  = new SolidBrush(Color.FromArgb(50, 0, 0, 0));
            Brush black25brush  = new SolidBrush(Color.FromArgb(25, 0, 0, 0));

            g.DrawLine(black200, graphStart - 4, 20, graphEnd + 4, 20);
            g.DrawLine(black200, graphStart, 16, graphStart, _prerenderedGraph.Height - 16);
            g.DrawLine(black200, graphEnd, 16, graphEnd, 19);
            g.DrawLine(black200, ticks[0], 16, ticks[0], 19);
            g.DrawLine(black150, ticks[1], 16, ticks[1], 19);
            g.DrawLine(black150, ticks[2], 16, ticks[2], 19);
            g.DrawLine(black75, ticks[3], 16, ticks[3], 19);
            g.DrawLine(black75, ticks[4], 16, ticks[4], 19);
            g.DrawLine(black75, ticks[5], 16, ticks[5], 19);
            g.DrawLine(black75, ticks[6], 16, ticks[6], 19);
            g.DrawLine(black75, graphEnd, 21, graphEnd, _prerenderedGraph.Height - 4);
            g.DrawLine(black75, ticks[0], 21, ticks[0], _prerenderedGraph.Height - 4);
            g.DrawLine(black50, ticks[1], 21, ticks[1], _prerenderedGraph.Height - 4);
            g.DrawLine(black50, ticks[2], 21, ticks[2], _prerenderedGraph.Height - 4);
            g.DrawLine(black25, ticks[3], 21, ticks[3], _prerenderedGraph.Height - 4);
            g.DrawLine(black25, ticks[4], 21, ticks[4], _prerenderedGraph.Height - 4);
            g.DrawLine(black25, ticks[5], 21, ticks[5], _prerenderedGraph.Height - 4);
            g.DrawLine(black25, ticks[6], 21, ticks[6], _prerenderedGraph.Height - 4);
            g.DrawLine(black200, graphStart - 4, _prerenderedGraph.Height - 20, graphEnd + 4, _prerenderedGraph.Height - 20);

            Font tickFont = new Font("Calibri", 11);
            g.DrawString((0f).ToString(), tickFont, black200brush, graphStart, 16, formatTick);
            g.DrawString((maxScale).ToString(), tickFont, black200brush, graphEnd, 16, formatTick);
            g.DrawString((maxScale * 0.5f).ToString(), tickFont, black200brush, ticks[0], 16, formatTick);
            g.DrawString((maxScale * 0.75f).ToString(), tickFont, black150brush, ticks[1], 16, formatTick);
            g.DrawString((maxScale * 0.25f).ToString(), tickFont, black150brush, ticks[2], 16, formatTick);
            g.DrawString((maxScale * 0.125f).ToString(), tickFont, black75brush, ticks[3], 16, formatTick);
            g.DrawString((maxScale * 0.375f).ToString(), tickFont, black75brush, ticks[4], 16, formatTick);
            g.DrawString((maxScale * 0.625f).ToString(), tickFont, black75brush, ticks[5], 16, formatTick);
            g.DrawString((maxScale * 0.875f).ToString(), tickFont, black75brush, ticks[6], 16, formatTick);

            g.DrawString((0f).ToString(), tickFont, black200brush, graphStart, _prerenderedGraph.Height - 16, formatTick);
            g.DrawString((maxScale).ToString(), tickFont, black200brush, graphEnd, _prerenderedGraph.Height - 16, formatTick);
            g.DrawString((maxScale * 0.5f).ToString(), tickFont, black200brush, ticks[0], _prerenderedGraph.Height - 16, formatTick);
            g.DrawString((maxScale * 0.75f).ToString(), tickFont, black150brush, ticks[1], _prerenderedGraph.Height - 16, formatTick);
            g.DrawString((maxScale * 0.25f).ToString(), tickFont, black150brush, ticks[2], _prerenderedGraph.Height - 16, formatTick);
            g.DrawString((maxScale * 0.125f).ToString(), tickFont, black75brush, ticks[3], _prerenderedGraph.Height - 16, formatTick);
            g.DrawString((maxScale * 0.375f).ToString(), tickFont, black75brush, ticks[4], _prerenderedGraph.Height - 16, formatTick);
            g.DrawString((maxScale * 0.625f).ToString(), tickFont, black75brush, ticks[5], _prerenderedGraph.Height - 16, formatTick);
            g.DrawString((maxScale * 0.875f).ToString(), tickFont, black75brush, ticks[6], _prerenderedGraph.Height - 16, formatTick);
            #endregion

            Graph graph = new Graph(_prerenderedGraph);
            graph.Show();
        }
示例#2
0
        public GraphForm(Character character, bool bStats)
        {
            InitializeComponent();

            if (!bStats)
            {
                Text = "Effective Cooldown Graph";
            }

            // Create ARGB bitmap matching the size of the picturebox and associate with picturebox
            bmp = new Bitmap(pictureBoxGraph.Size.Width, pictureBoxGraph.Size.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
            pictureBoxGraph.Image = bmp;

            // Greate & initialise GDI+ graphics object via bitmap
            gfx = Graphics.FromImage(bmp);
            gfx.SmoothingMode     = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
            gfx.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;

            #region Coordinates and scaling.

            /*
             *   +-----------------------------------------------------+-rcBmp  ---
             *   |                                                     |         |EndY
             *   |    ^                                                |        ---
             *   |    |                                                |
             *   |    |                                                |
             *   |    |                                                |
             *   |    |                                                |
             *   |    |                                                |
             *   |    |                                                |
             *   |    |                                                |
             *   |    |                                                |
             *   |    |                                                |
             *   |    |                                                |
             *   |    |                                                |
             *   |    |                                                |
             *   |    |                                                |
             *   |    +--------------------------------------------->  | ---
             *   |                                                     |  |  OrgY
             *   +-----------------------------------------------------+ ---
             *
             *   |----|OrgX                                         |--|EndX
             *
             *
             *
             */

            // Apply a translateion transformation. We'll move the 0,0 to the 0,0 of the chart we want to display
            // This will make most of the rest of the charting easier since all translation are then done by GDI+
            // It also makes working with 'y' a bit easier as it's just flipped in sign rather than translated also.
            int OrgX = 50;  // Leftmost edge of char
            int OrgY = 50;  // Bottom edge of char
            int EndX = 15;  // Margin right of chart
            int EndY = 15;  // Margin top of chart
            gfx.TranslateTransform(OrgX, pictureBoxGraph.Size.Height - OrgY);

            // Bitmap coordinates translated back into transform
            Rectangle rcBmp = new Rectangle(0, 0, pictureBoxGraph.Size.Width, pictureBoxGraph.Size.Height);
            rcBmp.Offset(-OrgX, -(pictureBoxGraph.Size.Height - OrgY));

            // rcChart is image rectangle, watch out, Y has positive value !
            Rectangle rcChart      = new Rectangle(0, 0, pictureBoxGraph.Size.Width - OrgX - EndX, pictureBoxGraph.Size.Height - OrgY - EndY);
            int       YAxisValueX  = -5; // Right alignment of values on the Y axis
            int       XAxisValueY  = +5; // Top margin of values on the X axis
            int       YChartMargin = 5;
            #endregion

            #region Define and calculate charts
            ChartData[] aCharts;
            float       DpsMax;
            float       DpsMin;
            float       DpsScaling;
            if (bStats)
            {
                aCharts = new ChartData[] {
                    new ChartData(rcChart.Width, Color.FromArgb(255, 192, 0, 0), "1 Strength", new Stats()
                    {
                        Strength = 1
                    }),
                    new ChartData(rcChart.Width, Color.FromArgb(255, 192, 192, 96), "1.167 Spell Power", new Stats()
                    {
                        SpellPower = 7 / 6
                    }),
                    new ChartData(rcChart.Width, Color.FromArgb(255, 0, 0, 192), "1 Armor Pen.", new Stats()
                    {
                        ArmorPenetrationRating = 1
                    }),
                    new ChartData(rcChart.Width, Color.FromArgb(255, 192, 0, 192), "1 Hit Rating", new Stats()
                    {
                        HitRating = 1
                    }),
                    new ChartData(rcChart.Width, Color.FromArgb(255, 0, 192, 0), "1 Expertise Rating", new Stats()
                    {
                        ExpertiseRating = 1
                    }),
                    new ChartData(rcChart.Width, Color.FromArgb(255, 192, 127, 96), "1 Agility", new Stats()
                    {
                        Agility = 1
                    }),
                    new ChartData(rcChart.Width, Color.FromArgb(255, 192, 127, 0), "2 Attack Power", new Stats()
                    {
                        AttackPower = 2
                    }),
                    new ChartData(rcChart.Width, Color.FromArgb(255, 96, 127, 192), "1 Crit Rating", new Stats()
                    {
                        CritRating = 1
                    }),
                    new ChartData(rcChart.Width, Color.FromArgb(255, 0, 0, 0), "1 Haste Rating", new Stats()
                    {
                        HasteRating = 1
                    }),
                };

                // Calculate charts
                CalculationsRetribution Calc = new CalculationsRetribution();
                DpsMax = 0;
                DpsMin = 999999999;
                foreach (ChartData cd in aCharts)
                {
                    for (int count = 0; count < rcChart.Width; count++)
                    {
                        Stats chartstats = cd.stats.Clone();
                        chartstats *= count - (rcChart.Width / 2);

                        CharacterCalculationsRetribution chartCalc = Calc.GetCharacterCalculations(character, new Item()
                        {
                            Stats = chartstats
                        }) as CharacterCalculationsRetribution;
                        float Dps = chartCalc.DPSPoints;

                        if (Dps > DpsMax)
                        {
                            DpsMax = Dps;
                        }
                        if (Dps < DpsMin)
                        {
                            DpsMin = Dps;
                        }

                        cd.dps[count] = Dps;
                    }
                }
                DpsScaling = (rcChart.Height - 2 * YChartMargin /* some extra margin*/) / (DpsMax - DpsMin);
            }
            else
            {
                aCharts = new ChartData[] {
                    new ChartData(rcChart.Width, Color.FromArgb(255, 192, 0, 0), "White", new Stats()
                    {
                        HasteRating = 1
                    }),
                    new ChartData(rcChart.Width, Color.FromArgb(255, 192, 192, 96), "CS", new Stats()
                    {
                        HasteRating = 1
                    }),
                    new ChartData(rcChart.Width, Color.FromArgb(255, 0, 0, 192), "J", new Stats()
                    {
                        HasteRating = 1
                    }),
                    new ChartData(rcChart.Width, Color.FromArgb(255, 192, 0, 192), "DS", new Stats()
                    {
                        HasteRating = 1
                    }),
                    new ChartData(rcChart.Width, Color.FromArgb(255, 0, 192, 0), "Cons", new Stats()
                    {
                        HasteRating = 1
                    }),
                    new ChartData(rcChart.Width, Color.FromArgb(255, 192, 127, 96), "Exo", new Stats()
                    {
                        HasteRating = 1
                    }),
                    //new ChartData(rcChart.Width, Color.FromArgb(255, 192, 127, 0), "HoW", new Stats() { HasteRating = 1 }),
                };

                // Calculate charts
                CalculationsRetribution Calc = new CalculationsRetribution();
                DpsMax = 0;
                DpsMin = 999999999;
                Stats stats = new Stats()
                {
                    HasteRating = 1
                };
                for (int count = 0; count < rcChart.Width; count++)
                {
                    Stats chartstats = stats.Clone();
                    chartstats *= count - (rcChart.Width / 2);

                    CharacterCalculationsRetribution chartCalc = Calc.GetCharacterCalculations(character, new Item()
                    {
                        Stats = chartstats
                    }) as CharacterCalculationsRetribution;
                    float Ecd;

                    Ecd = chartCalc.AttackSpeed;
                    if (Ecd > DpsMax)
                    {
                        DpsMax = Ecd;
                    }
                    if (Ecd < DpsMin)
                    {
                        DpsMin = Ecd;
                    }
                    aCharts[0].dps[count] = Ecd;

                    Ecd = 1 / chartCalc.Solution.GetAbilityUsagePerSecond(Ability.CrusaderStrike);
                    if (Ecd > 50)  // infinity fix
                    {
                        Ecd = 0;
                    }
                    if (Ecd > DpsMax)
                    {
                        DpsMax = Ecd;
                    }
                    if (Ecd < DpsMin)
                    {
                        DpsMin = Ecd;
                    }
                    aCharts[1].dps[count] = Ecd;

                    Ecd = 1 / chartCalc.Solution.GetAbilityUsagePerSecond(Ability.Judgement);
                    if (Ecd > 50)
                    {
                        Ecd = 0;
                    }
                    if (Ecd > DpsMax)
                    {
                        DpsMax = Ecd;
                    }
                    if (Ecd < DpsMin)
                    {
                        DpsMin = Ecd;
                    }
                    aCharts[2].dps[count] = Ecd;

                    Ecd = 1 / chartCalc.Solution.GetAbilityUsagePerSecond(Ability.DivineStorm);
                    if (Ecd > 50)
                    {
                        Ecd = 0;
                    }
                    if (Ecd > DpsMax)
                    {
                        DpsMax = Ecd;
                    }
                    if (Ecd < DpsMin)
                    {
                        DpsMin = Ecd;
                    }
                    aCharts[3].dps[count] = Ecd;
                    Ecd = 1 / chartCalc.Solution.GetAbilityUsagePerSecond(Ability.Consecration);
                    if (Ecd > 50)
                    {
                        Ecd = 0;
                    }
                    if (Ecd > DpsMax)
                    {
                        DpsMax = Ecd;
                    }
                    if (Ecd < DpsMin)
                    {
                        DpsMin = Ecd;
                    }
                    aCharts[4].dps[count] = Ecd;
                    Ecd = 1 / chartCalc.Solution.GetAbilityUsagePerSecond(Ability.Exorcism);
                    if (Ecd > 50)
                    {
                        Ecd = 0;
                    }
                    if (Ecd > DpsMax)
                    {
                        DpsMax = Ecd;
                    }
                    if (Ecd < DpsMin)
                    {
                        DpsMin = Ecd;
                    }
                    aCharts[5].dps[count] = Ecd;

                    /*Ecd = 1 / chartCalc.Solution.GetAbilityUsagePerSecond(Ability.HammerOfWrath);
                     * if (Ecd > 50)
                     *  Ecd = 0;
                     * if (Ecd > DpsMax)
                     *  DpsMax = Ecd;
                     * if (Ecd < DpsMin)
                     *  DpsMin = Ecd;
                     * aCharts[6].dps[count] = Ecd;     */
                }
                DpsScaling = (rcChart.Height - 2 * YChartMargin /* some extra margin*/) / (DpsMax - DpsMin);
            }
            #endregion

            #region Fonts, Pens, Brushes and other helper variables
            Font fntValue = new Font("Arial", 8);
            Font fntTitle = new Font("Arial", 16);

            Pen penBlack  = new Pen(Color.FromArgb(255, 0, 0, 0));
            Pen penGray50 = new Pen(Color.FromArgb(255, 127, 127, 127));
            Pen penGray75 = new Pen(Color.FromArgb(255, 196, 196, 196));
            Pen penGray90 = new Pen(Color.FromArgb(255, 230, 230, 230));
            Pen penRed    = new Pen(Color.FromArgb(255, 255, 0, 0));

            Brush brBlack = new SolidBrush(Color.FromArgb(255, 0, 0, 0));
            Brush brWhite = new SolidBrush(Color.FromArgb(255, 255, 255, 255));
            Brush brRed   = new SolidBrush(Color.FromArgb(255, 255, 0, 0));

            StringFormat fmtRAlignCenter = new StringFormat();
            fmtRAlignCenter.Alignment     = StringAlignment.Far;
            fmtRAlignCenter.LineAlignment = StringAlignment.Center;

            StringFormat fmtRAlignBottom = new StringFormat();
            fmtRAlignBottom.Alignment     = StringAlignment.Far;
            fmtRAlignBottom.LineAlignment = StringAlignment.Far;

            StringFormat fmtLAlignTop = new StringFormat();
            fmtLAlignTop.Alignment     = StringAlignment.Near;
            fmtLAlignTop.LineAlignment = StringAlignment.Near;

            StringFormat fmtCAlignTop = new StringFormat();
            fmtCAlignTop.Alignment     = StringAlignment.Center;
            fmtCAlignTop.LineAlignment = StringAlignment.Near;
            #endregion

            // Clear bitmap to all white.
            gfx.FillRectangle(brWhite, rcBmp);

            #region Draw Axis and titles
            // Stats ticks (draw this first, so other axis information is drawn on top)
            const int Steps = 10; // Chart is per 1 stat, typically, 20 stats go in a iLevel.
            for (int i = 1; i < (rcChart.Width / 2) / Steps; i++)
            {
                Pen pen;
                if (i % 5 == 0)
                {
                    pen = penGray75;
                    gfx.DrawString((-i * 10).ToString(), fntValue, brBlack, rcChart.Width / 2 - i * 10, XAxisValueY, fmtCAlignTop);
                    gfx.DrawString((i * 10).ToString(), fntValue, brBlack, rcChart.Width / 2 + i * 10, XAxisValueY, fmtCAlignTop);
                }
                else
                {
                    pen = penGray90;
                }

                gfx.DrawLine(pen, rcChart.Width / 2 - i * 10, 0, rcChart.Width / 2 - i * 10, -rcChart.Height);
                gfx.DrawLine(pen, rcChart.Width / 2 + i * 10, 0, rcChart.Width / 2 + i * 10, -rcChart.Height);
            }
            // DPS ticks (Draw this first, so other axis information is drawn on top)
            const int MaxDpsLines = 25;
            float     DpsDelta    = DpsMax - DpsMin;
            int[]     Ranges      = { 5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000, 25000, 50000, };
            int       Range       = 1;
            foreach (int r in Ranges)
            {
                if (MaxDpsLines * r > DpsDelta)
                {
                    Range = r;
                    break;
                }
            }
            int Y = (int)(Math.Floor(DpsMin / Range) * Range);
            do
            {
                float Y2 = (Y - DpsMin) * DpsScaling;
                if (Y2 > 0)
                {
                    gfx.DrawLine(penGray75, 0, -Y2, rcChart.Width - 5, -Y2);
                    gfx.DrawString(Y.ToString(), fntValue, brBlack, YAxisValueX, -Y2, fmtRAlignCenter);
                }

                Y += Range;
            }while (Y < DpsMax);

            // Y Axis
            gfx.DrawLine(penBlack, 0, 0, 0, -rcChart.Height);
            gfx.DrawString(bStats ? "DPS" : "ECD", fntTitle, brBlack, 5, -(rcChart.Height - 5), fmtLAlignTop);

            // X-axis
            gfx.DrawLine(penBlack, 0, 0, rcChart.Width, 0);
            gfx.DrawString(bStats ? "Stats" : "Haste Rating", fntTitle, brBlack, rcChart.Width - 5, -5, fmtRAlignBottom);

            // Center Y Axis.
            gfx.DrawLine(penBlack, rcChart.Width / 2, 0, rcChart.Width / 2, -rcChart.Height);
            gfx.DrawString("0", fntValue, brBlack, rcChart.Width / 2, XAxisValueY, fmtCAlignTop);

            // "Experimental" text
            gfx.DrawString("Experimental", fntTitle, brRed, rcChart.Width - 5, OrgY, fmtRAlignBottom);
            #endregion

            // Draw charts
            int l = 0;
            foreach (ChartData cd in aCharts)
            {
                for (int count = 0; count < rcChart.Width - 1; count++)
                {
                    gfx.DrawLine(cd.pen, count, -(cd.dps[count] - DpsMin) * DpsScaling - YChartMargin, count + 1, -(cd.dps[count + 1] - DpsMin) * DpsScaling - YChartMargin);
                }
                gfx.DrawString(cd.name, fntValue, cd.brush, 5, -(rcChart.Height - fntTitle.GetHeight() - 5 - l * fntValue.GetHeight()));
                l++;
            }
            Invalidate();
            Update();
        }
示例#3
0
        public GraphForm(Character character, bool bStats)
        {
            InitializeComponent();

            if (!bStats)
                Text = "Effective Cooldown Graph";

            // Create ARGB bitmap matching the size of the picturebox and associate with picturebox
            bmp = new Bitmap(pictureBoxGraph.Size.Width, pictureBoxGraph.Size.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
            pictureBoxGraph.Image = bmp;

            // Greate & initialise GDI+ graphics object via bitmap
            gfx = Graphics.FromImage(bmp);
            gfx.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
            gfx.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
            
            #region Coordinates and scaling.
            /* 
             *   +-----------------------------------------------------+-rcBmp  ---
             *   |                                                     |         |EndY
             *   |    ^                                                |        ---
             *   |    |                                                |
             *   |    |                                                |
             *   |    |                                                |
             *   |    |                                                |
             *   |    |                                                |
             *   |    |                                                |
             *   |    |                                                |
             *   |    |                                                |
             *   |    |                                                |
             *   |    |                                                |
             *   |    |                                                |
             *   |    |                                                |
             *   |    |                                                |
             *   |    +--------------------------------------------->  | ---
             *   |                                                     |  |  OrgY
             *   +-----------------------------------------------------+ ---
             *   
             *   |----|OrgX                                         |--|EndX
             * 
             * 
             * 
             */

            // Apply a translateion transformation. We'll move the 0,0 to the 0,0 of the chart we want to display
            // This will make most of the rest of the charting easier since all translation are then done by GDI+
            // It also makes working with 'y' a bit easier as it's just flipped in sign rather than translated also.
            int OrgX = 50;  // Leftmost edge of char
            int OrgY = 50;  // Bottom edge of char
            int EndX = 15;  // Margin right of chart
            int EndY = 15;  // Margin top of chart
            gfx.TranslateTransform(OrgX, pictureBoxGraph.Size.Height - OrgY);

            // Bitmap coordinates translated back into transform
            Rectangle rcBmp = new Rectangle(0, 0, pictureBoxGraph.Size.Width, pictureBoxGraph.Size.Height);
            rcBmp.Offset(-OrgX, -(pictureBoxGraph.Size.Height - OrgY));

            // rcChart is image rectangle, watch out, Y has positive value !
            Rectangle rcChart = new Rectangle(0, 0, pictureBoxGraph.Size.Width - OrgX - EndX, pictureBoxGraph.Size.Height - OrgY - EndY);
            int YAxisValueX = - 5; // Right alignment of values on the Y axis
            int XAxisValueY = + 5; // Top margin of values on the X axis
            int YChartMargin = 5;
            #endregion

            #region Define and calculate charts
            ChartData[] aCharts;
            float DpsMax;
            float DpsMin;
            float DpsScaling;
            if (bStats)
            {
                aCharts = new ChartData[] {
                    new ChartData(rcChart.Width, Color.FromArgb(255, 192, 0, 0), "1 Strength", new Stats() { Strength = 1 }),
                    new ChartData(rcChart.Width, Color.FromArgb(255, 192, 192, 96), "1.167 Spell Power", new Stats() { SpellPower = 7/6 }),
                    new ChartData(rcChart.Width, Color.FromArgb(255, 0, 0, 192), "1 Armor Pen.", new Stats() { ArmorPenetrationRating = 1 }),
                    new ChartData(rcChart.Width, Color.FromArgb(255, 192, 0, 192), "1 Hit Rating", new Stats() { HitRating = 1 }),
                    new ChartData(rcChart.Width, Color.FromArgb(255, 0, 192, 0), "1 Expertise Rating", new Stats() { ExpertiseRating = 1 }),
                    new ChartData(rcChart.Width, Color.FromArgb(255, 192, 127, 96), "1 Agility", new Stats() { Agility = 1 }),
                    new ChartData(rcChart.Width, Color.FromArgb(255, 192, 127, 0), "2 Attack Power", new Stats() { AttackPower = 2 }),
                    new ChartData(rcChart.Width, Color.FromArgb(255, 96, 127, 192), "1 Crit Rating", new Stats() { CritRating = 1 }),
                    new ChartData(rcChart.Width, Color.FromArgb(255, 0, 0, 0), "1 Haste Rating", new Stats() { HasteRating = 1 }),
                };

                // Calculate charts
                CalculationsRetribution Calc = new CalculationsRetribution();
                DpsMax = 0;
                DpsMin = 999999999;
                foreach (ChartData cd in aCharts)
                {
                    for (int count = 0; count < rcChart.Width; count++)
                    {
                        Stats chartstats = cd.stats.Clone();
                        chartstats *= count - (rcChart.Width / 2);

                        CharacterCalculationsRetribution chartCalc = Calc.GetCharacterCalculations(character, new Item() { Stats = chartstats }) as CharacterCalculationsRetribution;
                        float Dps = chartCalc.DPSPoints;

                        if (Dps > DpsMax)
                            DpsMax = Dps;
                        if (Dps < DpsMin)
                            DpsMin = Dps;

                        cd.dps[count] = Dps;
                    }
                }
                DpsScaling = (rcChart.Height - 2 * YChartMargin/* some extra margin*/) / (DpsMax - DpsMin);
            }
            else 
            {
                aCharts = new ChartData[] {
                    new ChartData(rcChart.Width, Color.FromArgb(255, 192, 0, 0), "White", new Stats() { HasteRating = 1 }),
                    new ChartData(rcChart.Width, Color.FromArgb(255, 192, 192, 96), "CS", new Stats() { HasteRating = 1 }),
                    new ChartData(rcChart.Width, Color.FromArgb(255, 0, 0, 192), "J", new Stats() { HasteRating = 1 }),
                    new ChartData(rcChart.Width, Color.FromArgb(255, 192, 0, 192), "DS", new Stats() { HasteRating = 1 }),
                    new ChartData(rcChart.Width, Color.FromArgb(255, 0, 192, 0), "Cons", new Stats() { HasteRating = 1 }),
                    new ChartData(rcChart.Width, Color.FromArgb(255, 192, 127, 96), "Exo", new Stats() { HasteRating = 1 }),
                    //new ChartData(rcChart.Width, Color.FromArgb(255, 192, 127, 0), "HoW", new Stats() { HasteRating = 1 }),
                };

                // Calculate charts
                CalculationsRetribution Calc = new CalculationsRetribution();
                DpsMax = 0;
                DpsMin = 999999999;
                Stats stats = new Stats() { HasteRating = 1 };
                for (int count = 0; count < rcChart.Width; count++)
                {
                    Stats chartstats = stats.Clone();
                    chartstats *= count - (rcChart.Width / 2);

                    CharacterCalculationsRetribution chartCalc = Calc.GetCharacterCalculations(character, new Item() { Stats = chartstats }) as CharacterCalculationsRetribution;
                    float Ecd;

                    Ecd = chartCalc.AttackSpeed;
                    if (Ecd > DpsMax)
                        DpsMax = Ecd;
                    if (Ecd < DpsMin)
                        DpsMin = Ecd;
                    aCharts[0].dps[count] = Ecd;

                    Ecd = 1 / chartCalc.Solution.GetAbilityUsagePerSecond(Ability.CrusaderStrike);
                    if (Ecd > 50)  // infinity fix
                        Ecd = 0;
                    if (Ecd > DpsMax)
                        DpsMax = Ecd;
                    if (Ecd < DpsMin)
                        DpsMin = Ecd;
                    aCharts[1].dps[count] = Ecd;

                    Ecd = 1 / chartCalc.Solution.GetAbilityUsagePerSecond(Ability.Judgement);
                    if (Ecd > 50)
                        Ecd = 0;
                    if (Ecd > DpsMax)
                        DpsMax = Ecd;
                    if (Ecd < DpsMin)
                        DpsMin = Ecd;
                    aCharts[2].dps[count] = Ecd;

                    Ecd = 1 / chartCalc.Solution.GetAbilityUsagePerSecond(Ability.DivineStorm);
                    if (Ecd > 50)
                        Ecd = 0;
                    if (Ecd > DpsMax)
                        DpsMax = Ecd;
                    if (Ecd < DpsMin)
                        DpsMin = Ecd;
                    aCharts[3].dps[count] = Ecd;
                    Ecd = 1 / chartCalc.Solution.GetAbilityUsagePerSecond(Ability.Consecration);
                    if (Ecd > 50)
                        Ecd = 0;
                    if (Ecd > DpsMax)
                        DpsMax = Ecd;
                    if (Ecd < DpsMin)
                        DpsMin = Ecd;
                    aCharts[4].dps[count] = Ecd;
                    Ecd = 1 / chartCalc.Solution.GetAbilityUsagePerSecond(Ability.Exorcism);
                    if (Ecd > 50)
                        Ecd = 0;
                    if (Ecd > DpsMax)
                        DpsMax = Ecd;
                    if (Ecd < DpsMin)
                        DpsMin = Ecd;
                    aCharts[5].dps[count] = Ecd;
                    /*Ecd = 1 / chartCalc.Solution.GetAbilityUsagePerSecond(Ability.HammerOfWrath);
                    if (Ecd > 50)
                        Ecd = 0;
                    if (Ecd > DpsMax)
                        DpsMax = Ecd;
                    if (Ecd < DpsMin)
                        DpsMin = Ecd;
                    aCharts[6].dps[count] = Ecd;     */
                }
                DpsScaling = (rcChart.Height - 2 * YChartMargin/* some extra margin*/) / (DpsMax - DpsMin);
            }
            #endregion

            #region Fonts, Pens, Brushes and other helper variables
            Font fntValue = new Font("Arial", 8);
            Font fntTitle = new Font("Arial", 16);

            Pen penBlack = new Pen(Color.FromArgb(255, 0, 0, 0));
            Pen penGray50 = new Pen(Color.FromArgb(255, 127, 127, 127));
            Pen penGray75 = new Pen(Color.FromArgb(255, 196, 196, 196));
            Pen penGray90 = new Pen(Color.FromArgb(255, 230, 230, 230));
            Pen penRed = new Pen(Color.FromArgb(255, 255, 0, 0));

            Brush brBlack = new SolidBrush(Color.FromArgb(255, 0, 0, 0));
            Brush brWhite = new SolidBrush(Color.FromArgb(255, 255, 255, 255));
            Brush brRed = new SolidBrush(Color.FromArgb(255, 255, 0, 0));

            StringFormat fmtRAlignCenter = new StringFormat();
            fmtRAlignCenter.Alignment = StringAlignment.Far;
            fmtRAlignCenter.LineAlignment = StringAlignment.Center;

            StringFormat fmtRAlignBottom = new StringFormat();
            fmtRAlignBottom.Alignment = StringAlignment.Far;
            fmtRAlignBottom.LineAlignment = StringAlignment.Far;

            StringFormat fmtLAlignTop = new StringFormat();
            fmtLAlignTop.Alignment = StringAlignment.Near;
            fmtLAlignTop.LineAlignment = StringAlignment.Near;

            StringFormat fmtCAlignTop = new StringFormat();
            fmtCAlignTop.Alignment = StringAlignment.Center;
            fmtCAlignTop.LineAlignment = StringAlignment.Near;
            #endregion

            // Clear bitmap to all white.
            gfx.FillRectangle(brWhite, rcBmp);

            #region Draw Axis and titles
            // Stats ticks (draw this first, so other axis information is drawn on top)
            const int Steps=10; // Chart is per 1 stat, typically, 20 stats go in a iLevel.
            for (int i = 1; i < (rcChart.Width / 2) / Steps; i++)
            {
                Pen pen;
                if (i % 5 == 0)
                {
                    pen = penGray75;
                    gfx.DrawString((-i * 10).ToString(), fntValue, brBlack, rcChart.Width / 2 - i * 10, XAxisValueY, fmtCAlignTop);
                    gfx.DrawString((i * 10).ToString(), fntValue, brBlack, rcChart.Width / 2 + i * 10, XAxisValueY, fmtCAlignTop);
                }
                else
                    pen = penGray90;

                gfx.DrawLine(pen, rcChart.Width / 2 - i * 10, 0, rcChart.Width / 2 - i * 10, -rcChart.Height);
                gfx.DrawLine(pen, rcChart.Width / 2 + i * 10, 0, rcChart.Width / 2 + i * 10, -rcChart.Height);
            }
            // DPS ticks (Draw this first, so other axis information is drawn on top)
            const int MaxDpsLines = 25;
            float DpsDelta = DpsMax - DpsMin;
            int[] Ranges = { 5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000, 25000, 50000, };
            int Range = 1;
            foreach (int r in Ranges)
            {
                if (MaxDpsLines * r > DpsDelta)
                {
                    Range = r;
                    break;
                }
            }
            int Y = (int)(Math.Floor(DpsMin / Range) * Range);
            do
            {
                float Y2 = (Y - DpsMin) * DpsScaling;
                if (Y2 > 0)
                {
                    gfx.DrawLine(penGray75, 0, -Y2, rcChart.Width - 5, -Y2);
                    gfx.DrawString(Y.ToString(), fntValue, brBlack, YAxisValueX, -Y2, fmtRAlignCenter);
                }

                Y += Range;
            }
            while (Y < DpsMax);
            
            // Y Axis
            gfx.DrawLine(penBlack, 0, 0, 0, -rcChart.Height);
            gfx.DrawString(bStats ? "DPS" : "ECD", fntTitle, brBlack, 5, -(rcChart.Height - 5), fmtLAlignTop);

            // X-axis
            gfx.DrawLine(penBlack, 0, 0, rcChart.Width, 0); 
            gfx.DrawString(bStats ? "Stats" : "Haste Rating", fntTitle, brBlack, rcChart.Width - 5, -5, fmtRAlignBottom);

            // Center Y Axis.
            gfx.DrawLine(penBlack, rcChart.Width / 2, 0, rcChart.Width / 2, -rcChart.Height);
            gfx.DrawString("0", fntValue, brBlack, rcChart.Width / 2, XAxisValueY, fmtCAlignTop);
            
            // "Experimental" text
            gfx.DrawString("Experimental", fntTitle, brRed, rcChart.Width-5, OrgY, fmtRAlignBottom);
            #endregion

            // Draw charts
            int l = 0;
            foreach (ChartData cd in aCharts)
            {
                for (int count = 0; count < rcChart.Width - 1; count++)
                    gfx.DrawLine(cd.pen, count, -(cd.dps[count] - DpsMin) * DpsScaling - YChartMargin, count + 1, -(cd.dps[count + 1] - DpsMin) * DpsScaling - YChartMargin);
                gfx.DrawString(cd.name, fntValue, cd.brush, 5, -(rcChart.Height - fntTitle.GetHeight() - 5 - l*fntValue.GetHeight()));
                l++;
            }
            Invalidate();
            Update();
        }