623 lines
16 KiB
C#
623 lines
16 KiB
C#
using System;
|
|
using System.Drawing;
|
|
using System.Drawing.Drawing2D;
|
|
using System.Web.UI.DataVisualization.Charting;
|
|
using System.Collections;
|
|
|
|
namespace System.Web.UI.DataVisualization.Charting.Utilities
|
|
{
|
|
/// <summary>
|
|
/// .
|
|
/// </summary>
|
|
public class ChartDataTableHelper
|
|
{
|
|
#region Members
|
|
protected System.Web.UI.DataVisualization.Charting.Chart ChartObj = null;
|
|
protected ArrayList ChartAreas = null;
|
|
protected bool AddTableTotals = false;
|
|
protected System.Drawing.Color tableColor = Color.White;
|
|
protected System.Drawing.Color borderColor = Color.Black;
|
|
protected bool enabled = true;
|
|
protected bool Initialized = false;
|
|
|
|
#endregion
|
|
|
|
#region Properties
|
|
|
|
/// <summary>
|
|
/// Enables or Disables the painting of the Data Table.
|
|
/// </summary>
|
|
public bool Enabled
|
|
{
|
|
get
|
|
{
|
|
return Enabled;
|
|
}
|
|
set
|
|
{
|
|
enabled = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets or gets the Chart object.
|
|
/// </summary>
|
|
public System.Web.UI.DataVisualization.Charting.Chart Chart
|
|
{
|
|
get
|
|
{
|
|
return ChartObj;
|
|
}
|
|
set
|
|
{
|
|
ChartObj = value;
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Sets or gets the Table Color that will be painted.
|
|
/// </summary>
|
|
public System.Drawing.Color TableColor
|
|
{
|
|
get
|
|
{
|
|
return tableColor;
|
|
}
|
|
set
|
|
{
|
|
tableColor = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets or gets the Table Border Color that will be painted.
|
|
/// </summary>
|
|
public System.Drawing.Color BorderColor
|
|
{
|
|
get
|
|
{
|
|
return borderColor;
|
|
}
|
|
set
|
|
{
|
|
borderColor = value;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Constructors
|
|
/// <summary>
|
|
/// Construct a ChartDataTableHelper instance.
|
|
/// </summary>
|
|
public ChartDataTableHelper()
|
|
{
|
|
ChartObj = null;
|
|
ChartAreas = new ArrayList();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Construct a ChartDataTableHelper instance and Initialize all ChartAreas with a table.
|
|
/// </summary>
|
|
public ChartDataTableHelper(System.Web.UI.DataVisualization.Charting.Chart chartObj)
|
|
{
|
|
ChartAreas = new ArrayList();
|
|
Initialize(chartObj);
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Construct a ChartDataTableHelper instance and Initialize the specified ChartArea with a table.
|
|
/// </summary>
|
|
public ChartDataTableHelper(System.Web.UI.DataVisualization.Charting.Chart chartObj, string chartAreaName)
|
|
{
|
|
ChartAreas = new ArrayList();
|
|
Initialize(chartObj, chartAreaName);
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Construct a ChartDataTableHelper instance, Initialize the specified ChartArea with a table and
|
|
/// set a boolean to show or hide total columns.
|
|
/// </summary>
|
|
public ChartDataTableHelper(System.Web.UI.DataVisualization.Charting.Chart chartObj, string chartAreaName, bool addTableTotals)
|
|
{
|
|
ChartAreas = new ArrayList();
|
|
Initialize(chartObj, chartAreaName, addTableTotals);
|
|
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Initialization Methods
|
|
/// <summary>
|
|
/// Initialize all ChartAreas with a table.
|
|
/// </summary>
|
|
public void Initialize(System.Web.UI.DataVisualization.Charting.Chart chartObj)
|
|
{
|
|
ChartObj = chartObj;
|
|
|
|
foreach(ChartArea area in ChartObj.ChartAreas)
|
|
{
|
|
AddDataTable(area.Name);
|
|
}
|
|
|
|
if(!Initialized)
|
|
ChartObj.PostPaint +=new EventHandler<ChartPaintEventArgs>(this.Chart_PostPaint);
|
|
|
|
Initialized = true;
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// Initialize all ChartAreas with a table and
|
|
/// set a boolean to show or hide total columns.
|
|
/// </summary>
|
|
public void Initialize(System.Web.UI.DataVisualization.Charting.Chart chartObj, bool addTableTotals)
|
|
{
|
|
AddTableTotals = addTableTotals;
|
|
Initialize(chartObj);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initialize the specified ChartArea with a table.
|
|
/// </summary>
|
|
public void Initialize(System.Web.UI.DataVisualization.Charting.Chart chartObj, string chartAreaName)
|
|
{
|
|
ChartObj = chartObj;
|
|
|
|
AddDataTable(chartAreaName);
|
|
|
|
if(!Initialized)
|
|
ChartObj.PostPaint +=new EventHandler<ChartPaintEventArgs>(this.Chart_PostPaint);
|
|
|
|
Initialized = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initialize the specified ChartArea with a table and
|
|
/// set a boolean to show or hide total columns.
|
|
/// </summary>
|
|
public void Initialize(System.Web.UI.DataVisualization.Charting.Chart chartObj, string chartAreaName, bool addTableTotals)
|
|
{
|
|
ChartObj = chartObj;
|
|
AddTableTotals = addTableTotals;
|
|
|
|
AddDataTable(chartAreaName);
|
|
|
|
if(!Initialized)
|
|
ChartObj.PostPaint +=new EventHandler<ChartPaintEventArgs>(this.Chart_PostPaint);
|
|
|
|
Initialized = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initialize the specified ChartArea with a table.
|
|
/// </summary>
|
|
public void AddDataTable(string chartAreaName)
|
|
{
|
|
if(ChartObj == null || ChartAreas.IndexOf(chartAreaName) >= 0)
|
|
return;
|
|
|
|
// add this chart area to the list of chart areas that need to
|
|
// have a data table attached
|
|
ChartAreas.Add(chartAreaName);
|
|
|
|
int Row = 0;
|
|
|
|
|
|
if(AddTableTotals)
|
|
{
|
|
// create a dummy series that will not be shown but is used for
|
|
// showing the totals in the data table
|
|
ChartObj.Series.Add("DUMMY");
|
|
ChartObj.Series["DUMMY"].ChartArea = chartAreaName;
|
|
ChartObj.Series["DUMMY"].Enabled = false;
|
|
ChartObj.Series["DUMMY"].Color = Color.Gainsboro;
|
|
}
|
|
|
|
|
|
// for each of the series that are attached to this
|
|
// named chart area, create a custom axis label.
|
|
// All tables lines will be drawn on a paint event.
|
|
// ****************************************************
|
|
// NOTE: ALL SERIES MUST HAVE THE SAME NUMBER OF POINTS!
|
|
// ****************************************************
|
|
foreach(Series ser in ChartObj.Series)
|
|
{
|
|
if(chartAreaName == ser.ChartArea)
|
|
{
|
|
if(AddTableTotals)
|
|
{
|
|
// shadows must be turned off otherwise
|
|
// they will still show up for the transparent points
|
|
ser.ShadowOffset = 0;
|
|
|
|
// adjust the series values to ensure they are not
|
|
// indexed and they are sorted... plus adding each point
|
|
// to make the dummy series data
|
|
AdjustXValues(ser);
|
|
}
|
|
|
|
|
|
Row++;
|
|
double From = 0.0;
|
|
double To = 0.0;
|
|
bool firstPoint = true;
|
|
|
|
double YValueTotal = 0;
|
|
|
|
foreach(DataPoint dp in ser.Points)
|
|
{
|
|
if(AddTableTotals)
|
|
YValueTotal += dp.YValues[0];
|
|
|
|
if(firstPoint && dp.XValue == 0)
|
|
From = 0.5;
|
|
else if(firstPoint)
|
|
From = dp.XValue - 0.5;
|
|
|
|
if(firstPoint)
|
|
{
|
|
ChartObj.ChartAreas[chartAreaName].AxisX.Minimum = From;
|
|
ChartObj.ChartAreas[chartAreaName].AxisX.MajorGrid.Interval = 1;
|
|
ChartObj.ChartAreas[chartAreaName].AxisX.MajorTickMark.Interval = 1;
|
|
ChartObj.ChartAreas[chartAreaName].AxisX.LabelStyle.Interval = 1;
|
|
ChartObj.ChartAreas[chartAreaName].AxisX.MajorGrid.IntervalOffset = 0.5;
|
|
ChartObj.ChartAreas[chartAreaName].AxisX.MajorTickMark.IntervalOffset = 0.5;
|
|
ChartObj.ChartAreas[chartAreaName].AxisX.LabelStyle.IntervalOffset = 0.5;
|
|
}
|
|
|
|
To = From + 1;
|
|
|
|
ChartObj.ChartAreas[chartAreaName].AxisX.CustomLabels.Add(
|
|
From, To,
|
|
" ", // space used as a placeholder
|
|
Row, LabelMarkStyle.None, GridTickTypes.None
|
|
);
|
|
|
|
firstPoint = false;
|
|
From += 1;
|
|
}
|
|
|
|
if(AddTableTotals)
|
|
ser.Points[ser.Points.Count-1].YValues[0] = YValueTotal;
|
|
|
|
ChartObj.ChartAreas[chartAreaName].AxisX.Maximum = To;
|
|
|
|
}
|
|
}
|
|
|
|
if(AddTableTotals)
|
|
AdjustYMaximum(chartAreaName);
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// With the addition of Totals, the chart will try to set the maximum
|
|
/// values according to these totals. This will cause some series to be
|
|
/// barely visible. Since the points are transparent this is a poor behavior.
|
|
/// This method will find and explicitly set the YAxis maximum to something
|
|
/// a little more with the user expectations.
|
|
/// </summary>
|
|
private void AdjustYMaximum(string chartAreaName)
|
|
{
|
|
double MaxYValue = 0;
|
|
|
|
// find the max YValue from all points in all series
|
|
foreach(Series ser in ChartObj.Series)
|
|
{
|
|
if(chartAreaName == ser.ChartArea && ser.Enabled)
|
|
{
|
|
// check agains all points except the last point
|
|
// which is the totals column
|
|
for(int index = 0; index < ser.Points.Count-1; index++)
|
|
{
|
|
DataPoint pt = ser.Points[index];
|
|
if(pt.YValues[0] > MaxYValue)
|
|
MaxYValue = pt.YValues[0];
|
|
}
|
|
}
|
|
}
|
|
|
|
double LogValue = (int)(Math.Log10(MaxYValue)) + 1;
|
|
double NewMaxYValue = Math.Pow(10, LogValue);
|
|
double ratio = MaxYValue / NewMaxYValue;
|
|
double divisor = 1;
|
|
|
|
if(ratio <= 0.1)
|
|
divisor = 10;
|
|
else if(ratio < 0.2)
|
|
divisor = 5;
|
|
else if(ratio < 0.25)
|
|
divisor = 4;
|
|
else if(ratio < 0.4)
|
|
divisor = 2.5;
|
|
else if(ratio < 0.5)
|
|
divisor = 2;
|
|
else if(ratio < 0.8)
|
|
divisor = 1.25;
|
|
|
|
ChartObj.ChartAreas[chartAreaName].AxisY.Maximum = NewMaxYValue / divisor;
|
|
ChartObj.ChartAreas[chartAreaName].AxisY.RoundAxisValues();
|
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// A cleanup method that ensures the XValues are sorted accordingly and set explicitly.
|
|
/// It will also create the totals for the DUMMY series.
|
|
/// </summary>
|
|
private void AdjustXValues(System.Web.UI.DataVisualization.Charting.Series series)
|
|
{
|
|
bool AddDummyPoints = true;
|
|
|
|
if(series.Name == "DUMMY")
|
|
return;
|
|
else if(ChartObj.Series["DUMMY"].Points.Count > 0)
|
|
AddDummyPoints = false;
|
|
|
|
// sort the series
|
|
series.Sort(PointSortOrder.Ascending, "X");
|
|
|
|
bool IsIndexed = false;
|
|
|
|
if(series.IsXValueIndexed)
|
|
IsIndexed = true;
|
|
else
|
|
{
|
|
bool IsFirstPoint = true;
|
|
bool IsLastPointZero = false;
|
|
|
|
// the series X values must be set and greater than zero
|
|
foreach(DataPoint pt in series.Points)
|
|
{
|
|
if(pt.XValue == 0 && !IsFirstPoint && IsLastPointZero)
|
|
{
|
|
IsIndexed = true;
|
|
break;
|
|
}
|
|
else if (pt.XValue == 0 && IsFirstPoint)
|
|
IsLastPointZero = true;
|
|
|
|
IsFirstPoint = false;
|
|
}
|
|
}
|
|
|
|
if(IsIndexed)
|
|
{
|
|
series.IsXValueIndexed = false;
|
|
int XValue = 0;
|
|
|
|
foreach(DataPoint pt in series.Points)
|
|
pt.XValue = ++XValue;
|
|
}
|
|
|
|
series.Points.AddXY(series.Points[series.Points.Count-1].XValue + 1, 0);
|
|
series.Points[series.Points.Count-1].AxisLabel = "Total";
|
|
series.Points[series.Points.Count-1].Color = Color.Transparent;
|
|
series.Points[series.Points.Count-1].BorderColor = Color.Transparent;
|
|
|
|
int index = 0;
|
|
foreach(DataPoint pt in series.Points)
|
|
{
|
|
if(AddDummyPoints)
|
|
{
|
|
ChartObj.Series["DUMMY"].Points.AddXY(pt.XValue, pt.YValues[0]);
|
|
}
|
|
else
|
|
{
|
|
ChartObj.Series["DUMMY"].Points[index].YValues[0] += pt.YValues[0];
|
|
}
|
|
|
|
index++;
|
|
}
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
#region Remove Table
|
|
|
|
public void RemoveDataTable(string chartAreaName)
|
|
{
|
|
if (ChartObj.ChartAreas.IndexOf(chartAreaName) >= 0)
|
|
ChartObj.ChartAreas[chartAreaName].AxisX.CustomLabels.Clear();
|
|
|
|
if(ChartAreas.IndexOf(chartAreaName) >= 0)
|
|
{
|
|
ChartAreas.RemoveAt(ChartAreas.IndexOf(chartAreaName));
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Paint Event Handling
|
|
|
|
/// <summary>
|
|
/// Chart Paint event handler.
|
|
/// </summary>
|
|
private void Chart_PostPaint(object sender, System.Web.UI.DataVisualization.Charting.ChartPaintEventArgs e)
|
|
{
|
|
if( e.ChartElement is ChartArea )
|
|
{
|
|
ChartArea area = (ChartArea)e.ChartElement;
|
|
// call the paint method.
|
|
if(ChartAreas.IndexOf(area.Name) >= 0 && enabled)
|
|
{
|
|
PaintDataTable(sender, e);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// This method does all the work for the painting of the data table.
|
|
/// </summary>
|
|
private void PaintDataTable(object sender, System.Web.UI.DataVisualization.Charting.ChartPaintEventArgs e)
|
|
{
|
|
ChartArea area = (ChartArea)e.ChartElement;
|
|
|
|
// get the rect of the chart area
|
|
RectangleF rect = e.ChartGraphics.GetAbsoluteRectangle( area.Position.ToRectangleF() );
|
|
|
|
// get the inner plot position
|
|
ElementPosition elemPos = area.InnerPlotPosition;
|
|
|
|
// find the coordinates of the inner plot position
|
|
float x = rect.X + (rect.Width / 100 * elemPos.X);
|
|
float y = rect.Y + (rect.Height / 100 * elemPos.Y);
|
|
float ChartAreaBottomY = rect.Y + rect.Height;
|
|
|
|
float width = (rect.Width / 100 * elemPos.Width);
|
|
float height = (rect.Height / 100 * elemPos.Height);
|
|
|
|
// find the height of the font that will be used
|
|
Font axisFont = area.AxisX.LabelStyle.Font;
|
|
string testString = "ForFontHeight";
|
|
SizeF axisFontSize = e.ChartGraphics.Graphics.MeasureString(testString, axisFont);
|
|
|
|
// find the height of the font that will be used
|
|
Font titleFont = area.AxisX.TitleFont;
|
|
testString = area.AxisX.Title;
|
|
SizeF titleFontSize = e.ChartGraphics.Graphics.MeasureString(testString, titleFont);
|
|
|
|
int seriesCount = 0;
|
|
|
|
// for each series that is attached to the chart area,
|
|
// draw some boxes around the labels in the color provided
|
|
for(int i = e.Chart.Series.Count-1; i >= 0; i--)
|
|
{
|
|
if(area.Name == e.Chart.Series[i].ChartArea)
|
|
{
|
|
seriesCount++;
|
|
}
|
|
}
|
|
|
|
// now, if a box was actually drawn, then draw
|
|
// the verticle lines to separate the columns of the table.
|
|
if(seriesCount > 0)
|
|
{
|
|
for(int i = 0; i < e.Chart.Series.Count; i++)
|
|
{
|
|
if(area.Name == e.Chart.Series[i].ChartArea)
|
|
{
|
|
double min = area.AxisX.Minimum;
|
|
double max = area.AxisX.Maximum;
|
|
|
|
// modify the min value for the current axis view
|
|
if(area.AxisX.ScaleView.Position-1 > min)
|
|
min = area.AxisX.ScaleView.Position-1;
|
|
|
|
// modify the max value for the currect axis view
|
|
if( (area.AxisX.ScaleView.Position + area.AxisX.ScaleView.Size + 0.5) < max)
|
|
max = area.AxisX.ScaleView.Position + area.AxisX.ScaleView.Size + 0.5;
|
|
|
|
|
|
// find the starting point that will be display.
|
|
// this is dependent on the current axis view.
|
|
// this sample assumes the same number of points in each
|
|
// series so always take from the zeroth series
|
|
int pointIndex = 0;
|
|
foreach(DataPoint pt in ChartObj.Series[0].Points)
|
|
{
|
|
if(pt.XValue > min)
|
|
break;
|
|
|
|
pointIndex++;
|
|
}
|
|
|
|
bool TableLegendDrawn = false;
|
|
|
|
for(double AxisValue = min; AxisValue < max; AxisValue++)
|
|
{
|
|
float pixelX = (float)e.ChartGraphics.GetPositionFromAxis(area.Name, AxisName.X, AxisValue);
|
|
float nextPixelX = (float)e.ChartGraphics.GetPositionFromAxis(area.Name, AxisName.X, AxisValue + 1);
|
|
float pixelY = ChartAreaBottomY - titleFontSize.Height - (seriesCount * axisFontSize.Height);
|
|
|
|
PointF point1 = PointF.Empty;
|
|
PointF point2 = PointF.Empty;
|
|
|
|
// Set Maximum and minimum points
|
|
point1.X = pixelX;
|
|
point1.Y = 0;
|
|
|
|
// Convert relative coordinates to absolute coordinates.
|
|
point1 = e.ChartGraphics.GetAbsolutePoint(point1);
|
|
point2.X = point1.X;
|
|
point2.Y = ChartAreaBottomY - titleFontSize.Height;
|
|
point1.Y = pixelY;
|
|
|
|
// Draw connection line
|
|
e.ChartGraphics.Graphics.DrawLine(new Pen(borderColor), point1,point2);
|
|
|
|
|
|
point2.X = nextPixelX;
|
|
point2.Y = 0;
|
|
point2 = e.ChartGraphics.GetAbsolutePoint(point2);
|
|
|
|
StringFormat format = new StringFormat();
|
|
format.Alignment = StringAlignment.Center;
|
|
format.LineAlignment = StringAlignment.Center;
|
|
|
|
// for each series draw one value in the column
|
|
int row = 0;
|
|
foreach(Series ser in ChartObj.Series)
|
|
{
|
|
if(area.Name == ser.ChartArea)
|
|
{
|
|
if(!TableLegendDrawn)
|
|
{
|
|
// draw the series color box
|
|
e.ChartGraphics.Graphics.FillRectangle(new SolidBrush(ser.Color),
|
|
x-10, row*(axisFont.Height)+(point1.Y), 10, axisFontSize.Height);
|
|
|
|
e.ChartGraphics.Graphics.DrawRectangle(new Pen(borderColor),
|
|
x-10, row*(axisFont.Height)+(point1.Y), 10, axisFontSize.Height);
|
|
|
|
e.ChartGraphics.Graphics.FillRectangle(new SolidBrush(tableColor),
|
|
x,
|
|
row*(axisFont.Height)+(point1.Y),
|
|
width,
|
|
axisFontSize.Height);
|
|
|
|
e.ChartGraphics.Graphics.DrawRectangle(new Pen(borderColor),
|
|
x,
|
|
row*(axisFont.Height)+(point1.Y),
|
|
width,
|
|
axisFontSize.Height);
|
|
|
|
}
|
|
|
|
if(pointIndex < ser.Points.Count)
|
|
{
|
|
string label = ser.Points[pointIndex].YValues[0].ToString();
|
|
RectangleF textRect = new RectangleF(point1.X, row*(axisFont.Height)+(point1.Y+1), point2.X-point1.X, axisFont.Height);
|
|
e.ChartGraphics.Graphics.DrawString(label, axisFont, new SolidBrush(area.AxisX.LabelStyle.ForeColor), textRect, format);
|
|
}
|
|
|
|
row++;
|
|
|
|
}
|
|
}
|
|
|
|
TableLegendDrawn = true;
|
|
|
|
pointIndex++;
|
|
}
|
|
|
|
// do this only once so break!
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
}
|
|
} |