using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Drawing.Design;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace WinFormsBulletGraph
{
/// <summary>
/// A bullet graph control that plots the graphical components of a bullet graph
/// </summary>
[DefaultBindingProperty("FeaturedMeasure")]
public partial class BulletGraph : UserControl
{
/// <summary>
/// Defines a single qualatative range within a bullet graph.
/// </summary>
[TypeConverter(typeof(ExpandableObjectConverter))]
public class QualitativeRange
{
public int? Maximum {get; set;}
public Color Color { get; set; }
public override string ToString()
{
return "Maximum: " + (Maximum.HasValue ? Maximum.ToString() : "<null>") + ", Color: " + Color;
}
}
private QualitativeRange[] qualitativeRanges = new QualitativeRange[] { };
[Description("The qualitative ranges to display")]
[Category("Bullet Graph")]
public QualitativeRange[] QualitativeRanges
{
get { return qualitativeRanges; }
set { qualitativeRanges = value; }
}
private FlowDirection flowDirection = FlowDirection.LeftToRight;
[Description("The rendered direction of this graph")]
[Category("Bullet Graph")]
public FlowDirection FlowDirection
{
get { return flowDirection; }
set { flowDirection = value; }
}
private int graphRange=100;
[Description("The range/maximum of this graph")]
[Category("Bullet Graph")]
public int GraphRange
{
get { return graphRange; }
set { graphRange = value; }
}
private int featuredMeasure = 77;
[Description("The featured measure displayed on this graph")]
[Category("Bullet Graph")]
public int FeaturedMeasure
{
get { return featuredMeasure; }
set {
featuredMeasure = value;
toolTip1.SetToolTip(this.featuredMeasureRect, "Featured Measure: " + Convert.ToString(featuredMeasure));
this.Invalidate(this.featuredMeasureRect.ClientRectangle);
}
}
/// <summary>
/// private accessor for features measure, this method imposes the condition
/// that featured measure should not exceed the range of the graph
/// </summary>
/// <returns></returns>
private int GetFeaturedMeasure()
{
int measure = featuredMeasure;
if (graphRange>0 && measure > graphRange)
{
measure = graphRange;
}
if (graphRange<0 && measure < graphRange)
{
measure = graphRange;
}
return measure;
}
private int comparativeMeasure = 86;
[Description("The comparative measure displayed on this graph")]
[Category("Bullet Graph")]
public int ComparativeMeasure
{
get { return comparativeMeasure; }
set {
comparativeMeasure = value;
toolTip1.SetToolTip(this.comparativeMeasureRect, "Comparative Measure: " + Convert.ToString(comparativeMeasure));
}
}
public BulletGraph()
{
InitializeComponent();
bulletBackground.Paint += new PaintEventHandler(bulletBackground_Paint);
this.Paint += new PaintEventHandler(BulletGraph_Paint);
}
/// <summary>
/// override the paint method to plot the axes
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void BulletGraph_Paint(object sender, PaintEventArgs e)
{
Rectangle rec = bulletBackground.ClientRectangle;
rec.Offset(bulletBackground.Location);
float ratio = (float)rec.Width / (float)graphRange;
// determine the tick spacing
float tickSpacing = graphRange>0 ? 1 : -1;
float[] multipliers = new float[] { 2.5f, 2, 2 };
int multiplierIndex = 0;
while (Math.Abs((float)graphRange / tickSpacing) > 7)
{
tickSpacing *= multipliers[multiplierIndex % multipliers.Length];
multiplierIndex++;
}
// plot the axes
float tickPosition = 0;
while (Math.Abs(tickPosition) <= Math.Abs((float)graphRange))
{
float xPos = rec.Left + tickPosition * ratio;
// bottom tick marks
PointF p1 = new PointF(xPos, rec.Bottom);
PointF p2 = new PointF(xPos, rec.Bottom + 3);
e.Graphics.DrawLine(Pens.Black, transform(p1, rec), transform(p2, rec));
// upper tick marks
p1 = new PointF(xPos, rec.Top);
p2 = new PointF(xPos, rec.Top - 3);
e.Graphics.DrawLine(Pens.Black, transform(p1, rec), transform(p2, rec));
// labels
Rectangle labelRec = transform(new Rectangle((int)xPos - 15, rec.Bottom + 4, 30, 15), rec);
StringFormat drawFormat = new StringFormat();
drawFormat.Alignment = StringAlignment.Center;
e.Graphics.DrawString(Convert.ToString(tickPosition), this.Font, Brushes.Black, labelRec, drawFormat);
tickPosition += tickSpacing;
}
// plot the featured measure
float thirdOfHeight = (float)rec.Height / 3;
int x = rec.Left;
int y = (int) ((float)rec.Top + thirdOfHeight);
int width = (int)((float)GetFeaturedMeasure() * ratio);
int height = (int)thirdOfHeight;
Rectangle controlRec = transform(new Rectangle(x, y, width, height), rec);
featuredMeasureRect.Location = controlRec.Location;
featuredMeasureRect.Size = controlRec.Size;
// plot the comparative measure
float sixthOfHeight = (float)rec.Height / 6;
x = (int)((float)comparativeMeasure * ratio + rec.Left);
y = (int)((float)rec.Top + sixthOfHeight);
width = rec.Width / 80;
height = (int)(sixthOfHeight*4);
controlRec = transform(new Rectangle(x, y, width, height), rec);
comparativeMeasureRect.Location = controlRec.Location;
comparativeMeasureRect.Size = controlRec.Size;
}
PointF transform(PointF point, Rectangle container)
{
if (flowDirection == FlowDirection.LeftToRight)
return point;
else
{
float x = container.X * 2 + container.Width - point.X ;
return new PointF(x, point.Y);
}
}
Rectangle transform(Rectangle rec, Rectangle container)
{
if (flowDirection == FlowDirection.LeftToRight)
return rec;
else
{
int x = container.X * 2 + container.Width - rec.X - rec.Width;
return new Rectangle(x, rec.Y, rec.Width, rec.Height);
}
}
RectangleF transform(RectangleF rec, Rectangle container)
{
if (flowDirection == FlowDirection.LeftToRight)
return rec;
else
{
float x = container.X * 2 + container.Width - rec.X - rec.Width;
return new RectangleF(x, rec.Y, rec.Width, rec.Height);
}
}
void bulletBackground_Paint(object sender, PaintEventArgs e)
{
if (qualitativeRanges == null)
return;
Rectangle rec = bulletBackground.ClientRectangle;
float ratio = (float)rec.Width / (float)graphRange;
float x = rec.Left;
int previousValue = 0;
foreach (QualitativeRange backgroundRegion in qualitativeRanges)
{
float x1 = x;
float x2;
if (backgroundRegion.Maximum.HasValue)
{
x2 = x1 + (float)(backgroundRegion.Maximum.Value-previousValue) * ratio;
previousValue = backgroundRegion.Maximum.Value;
}
else
{
x2 = rec.Right;
}
using (Brush brush = new SolidBrush(backgroundRegion.Color))
{
e.Graphics.FillRectangle(brush, transform(new RectangleF(x1, rec.Top, (x2 - x1), rec.Height), rec));
}
x = x2;
}
}
}
}