|
|||||||||||||||||||||
|
|||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionThe Simple Performance Chart is a The purpose of this article is not only to provide necessary information on how to use this simple control, but also to show how a few interesting issues have been solved. It's neither intended for covering all drawing techniques, nor to provide a ready solution for a production environment. Features
Background
I found many chart drawing projects on the web, but most of them were intended for displaying complete data
records in countless possible variations. I could not find a good control
that could display a "real-time"
chart of my server's performance, which I collected with the Using the codeSet up the Chart ControlUsing the control is pretty easy. Simply link the required assembly reference ("SpPerfChart.dll"),
compile your project, choose the Provide performance dataOther than the ability to Points of InterestNow that we know how to use the control, let's take a look at some interesting issues and how they have been solved. Drawing Methods
The actual drawing of the graph is one of the easier parts of the project. I decided to work with two So let's take a look at the essential parts of the drawing method: /// <summary>
/// Draws the chart (w/o background or grid, but with border) to the
/// Graphics <paramref name="g"/>
/// </summary>
/// <param name="g">Graphics</param>
private void DrawChart(Graphics g) {
visibleValues = Math.Min(this.Width / valueSpacing, drawValues.Count);
if (scaleMode == ScaleMode.Relative)
currentMaxValue = GetHighestValueForRelativeMode();
//"trick": initialize the first previous Point outside the bounds
Point previousPoint = new Point(Width + valueSpacing, Height);
Point currentPoint = new Point();
// Only draw average line when possible (visibleValues)
//and needed (style setting)
// [...]
// Connect all visible values with lines
for (int i = 0; i < visibleValues; i++) {
currentPoint.X = previousPoint.X - valueSpacing;
currentPoint.Y = CalcVerticalPosition(drawValues[i]);
// Actually draw the line
g.DrawLine(perfChartStyle.ChartLinePen.Pen, previousPoint,
currentPoint);
previousPoint = currentPoint;
}
// Draw current relative maximum value string
// [...]
// Draw Border on top
ControlPaint.DrawBorder3D(g, 0, 0, Width, Height, b3dstyle);
}
This looks quite straightforward, and it really is! Take a look at the following source code to learn how to draw a background gradient and the grid. I won't waste many words on it. /// <summary>
/// Draws the background gradient and the grid into Graphics
/// <paramref name="g"/>
/// </summary>
/// <param name="g">Graphic</param>
private void DrawBackgroundAndGrid(Graphics g) {
// Draw the background Gradient rectangle
Rectangle baseRectangle = new Rectangle(0, 0, this.Width, this.Height);
using (Brush gradientBrush = new LinearGradientBrush(
baseRectangle, perfChartStyle.BackgroundColorTop,
perfChartStyle.BackgroundColorBottom, LinearGradientMode.Vertical))
{
g.FillRectangle(gradientBrush, baseRectangle);
}
// Draw all visible, vertical gridlines (if wanted)
if (perfChartStyle.ShowVerticalGridLines) {
for (int i = Width - gridScrollOffset; i >= 0; i -= GRID_SPACING) {
g.DrawLine(perfChartStyle.VerticalGridPen.Pen, i, 0, i, Height);
}
}
// Draw all visible, horizontal gridlines (if wanted)
if (perfChartStyle.ShowHorizontalGridLines) {
for (int i = 0; i < Height; i += GRID_SPACING) {
g.DrawLine(perfChartStyle.HorizontalGridPen.Pen,
0, i, Width, i);
}
}
}
You probably noticed the Quick and flicker-free drawing: Double BufferingAll animated elements (or just regions) must redraw on each change, which can cause ugly flickering, depending on the redraw speed and size of the canvas to be redrawn. (You could limit the region to reduce the flickering effect, but since it's scrolling, this is not an option for this chart control.) Basically I found two different options which helped in this case: You can enable double buffering for a control using the following (recommended) method: this.SetStyle(ControlStyles.UserPaint, true);
this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
Optionally, you simply can set the control's this.DoubleBuffered = true;
I tested the options without a clear conclusion - the results were satisfying in both cases.
The first method is
more specific on how to handle drawing and thus more optimized, which is why I recommend
it. You can find more
information on double-buffering in the References section.
Relative Value ScalingAn essential issue was the capability to display arbitrary values in a proper relationship, limited
to the actual viewing range. Defining a fixed The number of visible values is calculated from the control's /// <summary>
/// Calculates the vertical Position of a value in relation the chart size,
/// Scale Mode and, if ScaleMode is Relative, to the current maximum value
/// </summary>
/// <param name="value">performance value</param>
/// <returns>vertical Point position in pixels</returns>
private int CalcVerticalPosition(decimal value) {
decimal result = Decimal.Zero;
if (scaleMode == ScaleMode.Absolute)
result = value * this.Height / 100;
else if (scaleMode == ScaleMode.Relative)
result = (currentMaxValue > 0) ?
(value * this.Height / currentMaxValue) : 0;
result = this.Height - result;
return Convert.ToInt32(Math.Round(result));
}
As you can see, the method is quite simple. The currentMaxValue > 0 condition prevents the method
from generating a division by zero. It could have been combined with the ScaleMode comparison in the
else if clause,
but this more structured scheme looked "safer" for future extensions.
Synchronized Value Display
We don't have to care about the display synchronization as long we can be sure that our "performance provider" (actually a class
supplying the values) is providing all values at regular intervals. For example, this is very suitable for displaying a
CPU usage chart, where we measure the data once a second, controlled by our custom But sometimes, we can't tell how regular the intervals can be, or how many values we receive each second. The Simple Performance
Chart offers three synchronization options to address this problem: TimerMode: SimpleCase example: Web server request duration in a testing environment
We want each single request duration to be visible on the chart, so that we can track down conspicuously long-lasting
requests. We prefer the TimerMode: SynchronizedAverageCase example: Web server download file size statistics
Like in the previous case, the values are reported in real-time. Every starting request triggers the TimerMode: SynchronizedSumCase example: Web server simultaneous users load
Let's assume that most conditions can be derived from the previous case. But this time, we are interested in the actual
amount of users requesting our web server per second. (The actual method parameter can even be a hard-coded
The Demo Application offers dynamic value generators where all behaviors can be tested and comprehended. Visual StylingLet's now cover the styling possibilities of our Performance Chart Library. It's quite an easy matter to allow extensive formatting possibilities for each single drawn element. You can
modify dozens of properties for each single line (like Style Properties Object Located in a Sub-Property
I decided to allow different formatting options per element: The lines (main chart line, optional average-level line, optional grid lines)
can have custom
It wouldn't be a good approach to put all these possible properties directly into the
control class itself, so I
created a simple class called Now, this was the resulting Designer Property Bar:
So what's that? The property was not declared as "read-only". Nevertheless, the forms designer doesn't allow a closer look into the style class. Or rather, it doesn't know about it yet. That is what we need to tell the designer. First attempt: [TypeConverterAttribute(typeof(ExpandableObjectConverter))]
public class PerfChartStyle
{
private ChartPen verticalGridPen;
private ChartPen horizontalGridPen;
// [...]
A
Now that we set up the right [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
[Category("Appearance"), Description("Appearance and Style")]
public PerfChartStyle PerfChartStyle {
get { return perfChartStyle; }
set { perfChartStyle = value; }
}
The first line does the whole trick. Using Here is the final result:
There we go! Expandable style properties, with persisted changes. References and further reading
Conclusion
This Performance Chart project is a good example of how to create a basic Ideas for possible improvements
There are countless possibilities to improve this control. Here are some ideas,
if you are planning to extend the
...and many more features. License and DisclaimerSource Code WarrantyThe usual Warranty Disclaimer COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES THAT THE COVERED CODE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED CODE IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER. License
History
| ||||||||||||||||||||