38209 Gauge

Draws gauges in VFP applications

38236

Project Manager: Doug Hennig

vfpxreleasesmall.png Latest Release of Gauge

Introduction

People like visual images. Most people would rather see a chart than columns of raw numbers because it’s easier to see the relationships between items visually. Adding analysis tools like charts and gauges to your applications make them much more valuable to your users.

What is a gauge? A gauge is an image that shows how a single value compares to a maximum or goal value. The two values can be anything: current sales compared to budget, volume to date compared to the maximum allowable volume, and so on. For example, many charitable organizations show the current status of their fund raising campaigns as a thermometer. The top of the thermometer represents the value of the fund raising goal and the height of the bar inside the thermometer represents how much money has been raised so far.

The most common type of gauge looks like a speedometer in a car; for example:

gauge.png

The end of the gauge represents the maximum value and the position of the needle represents the current value. Color bands around the outside edge of the gauge show the ranges of certain categories. For example, in the figure above, the red band indicates where sales are too low, the yellow band where they’re acceptable but not great, and the green band where sales should be to make the boss happy.

The Gauge class

There’s just a single class used to draw gauges: Gauge in Gauge.VCX (additional components are also required as I’ll discuss later). It’s a subclass of Custom so it has no visible appearance at runtime. How does the gauge appear then? After you call the DrawGauge method, the cImage property is set to the bytes for the gauge image. You can use FILETOSTR() to write the contents of cImage to a file, such as if you want to use the gauge in a report, or set the PictureVal property of an Image object if you want it to appear in a form. For example, SampleGauge.SCX, one of the forms included in the downloads, uses this code to have the Gauge object referenced in the oGauge property draw a gauge in an Image named imgGauge:

with This
    .oGauge.nSize = .imgGauge.Width
    .oGauge.DrawGauge()
    .imgGauge.PictureVal = .oGauge.cImage
endwith

Here are the properties of the Gauge class.

Property Description
cDialText The text for the dial
cDialTextFontName The font for the dial text
cErrorMessage The text of any error that occurs
cFormat The format for the labels in .NET syntax: {0:#,##0} by default
cImage The gauge image
cLabelFontName The font for the labels
lAdjustLabelSize .T. to adjust the label size based on the gauge size, .F. to use the size specified in nLabelFontSize
lDialTextFontBold .T. if the dial text is bold
lDialTextFontItalic .T. if the dial text is italics
lDisplayDigitalValue .T. to display the value in a digital display
lLabelFontBold .T. if the label text is bold
lLabelFontItalic .T. if the label text is italics
lShowGoalMarker .T. to display a marker for the goal value
lValuesAsPercentages .T. to use percentages for values, .F. to use amounts for values
nBackColor The background color or the starting color for a background gradient
nBackColor2 The end color for a background gradient; if it's the same as nBackColor, there is no gradient
nBackColorAlpha The alpha for the background color
nBackGradientMode The mode for a background gradient: 0 = left to right, 1 = top to bottom, 2 = from top left, 3 = from top right
nBand1Color The color for band 1
nBand1End The ending position for band 1
nBand2Color The color for band 2
nBand2End The ending position for band 2
nBand3Color The color for band 3
nDialAlpha The alpha for the dial color
nDialColor The color to use for the dial
nDialTextColor The color for the dial text
nDialTextFontSize The font size for the dial text
nDigitsColor The color for digital digits
nGlossiness The glossiness value (0 – 100)
nGoalMarkerColor The color to use for the goal marker
nGoalPosition The goal value for the gauge
nLabelColor The color to use for labels
nLabelDistance The distance between labels and major tick marks
nLabelFactor The factor to use for labels: 1, 1000, 10000, etc.
nLabelFontSize The font size for the labels
nMajorTickColor The color of major tick marks
nMajorTickCount The number of major ticks
nMaxValue The maximum value for the gauge
nMinorTickColor The color of minor tick marks
nMinorTickCount The number of minor ticks
nMinValue The minimum value for the gauge
nSize The height and width of the gauge (it's a square so they're the same)
nValue The current value for the gauge
oBridge A reference to a wwDotNetBridge object
oGauge A reference to a .NET GaugeControl object

As you can see, there are quite a few of them. Most of them affect the appearance of the gauge. The best way to check out how the various properties work is by running SampleGauge.SCX:

sample.png

Each control has a tooltip specifying which property it controls. Changing any setting immediately redraws the gauge so you can instantly see the effect.

Here are comments about some of the properties:
  • nGlossiness controls how bright the “glossy” ellipse that appears at the top of the gauge is. This ellipse gives the illusion of reflected light, as if the gauge was made of glass or plastic.
  • The gauge size is determined by the nSize property; since a gauge is drawn as a square, the height and width of the image are both set to nSize.
  • By default, the labels indicating the values around the gauge are sized based on the size of the gauge: the larger the gauge, the bigger the labels. Set lAdjustLabelSize to .F. if you want to use the font size specified in nLabelFontSize instead.
  • cFormat indicates how the labels are formatted. It has to use .NET syntax for number formats for reasons that will be obvious later. .NET syntax uses “#” as an optional digit placeholder, “0” as a digit placeholder that displays 0 if there is no digit, “.” for a decimal separator, and “,” to use a thousands separator (unlike VFP, only one is needed in the format even when numbers exceed one million). The format string is surrounded with “{0:” and “}”, so the default of “{0:#,##0}” specifies no leading zeros, no decimal places, and thousands separators.
  • nBand1End indicates where on the outer rim of the gauge the first color band appears, such as the red band in Figure 1. Only the end value is needed, since the band starts at 0. Similarly, nBand2End and nBand3End indicate the ending positions of the second and third color bands, with the starting positions being the end of the previous band.
  • By default, nBand1End, nBand2End, and nBand3End are assumed to be percentages, so a value of 35 indicates an ending position of 35% of the gauge arc. If you want to use amounts instead, such as 25,000, set lValuesAsPercentages to .F.
  • Since the gauge can’t go above the 100% or below 0%, the needle is pegged at those values when nValue is greater than nMaxValue or less than nMinValue. Because you may want the maximum value to be a goal that could be exceeded (for example, a salesperson’s monthly quota may be $10,000 but they certainly could sell more than that), you can set nGoalPosition to a lower value than 100. For example, if you set it to 75, then the nMaxValue value appears at 75%.
  • nMinValue can be larger than nMaxValue; for example, you may want a gauge than runs from 100 to 0.
  • For larger numbers (over 1,000), the labels can overlap the major tick marks, so increase the value of nLabelDistance accordingly. Alternatively, you can set nLabelFactor to a value to divide the labels by so smaller numbers are shown. For example, if you set nLabelFactor to 10000, the 5,000 position on the gauge appears as “5.”

Creating a dashboard

Dashboards are all the rage these days. A dashboard is a form displaying multiple panels of information, such as charts, reports, and of course gauges. Another sample form that comes with the downloads, Dashboard.SCX, is a simple demo of how a dashboard might work.

dashboard.png

This form is actually quite simple. Its Init method adds eight Image controls and sizes and positions them so they take up two rows of four images. A Timer on the form calls the form’s DrawGauges method, which uses a single Gauge control to do the drawing. DrawGauges sets the properties of the Gauge control to different values for each gauge (different dial colors and different current and maximum values), draws the gauge, and sets the PictureVal property of each Image control to the resulting image. Just for fun, the timer fires every 2 seconds and shows a random value so you can see the needles move.

How Gauge works

The Gauge class is actually a wrapper for a .NET DLL that does all the work. I’ll discuss the .NET class later.
To avoid COM registration and other issues, I use Rick Strahl’s wwDotNetBridge utility (https://west-wind.com/wwDotnetBridge.aspx). As you can see in the code below, the Init method of Gauge instantiates wwDotNetBridge into the oBridge property. Since you usually only want a single instance of wwDotNetBridge in an application, you can pass an existing instance to Init instead. Init also loads the Gauge.DLL .NET assembly and instantiates the Gauge.GaugeControl class into the oGauge property.

Since the .NET DLL does all the work, all the DrawGauge method of the Gauge class has to do is populate the properties of the .NET object with the values of its own properties, call the .NET object’s DrawGauge method, and put the return value, which is the bytes of the gauge image, into cImage.

The only complication in DrawGauge is that VFP color values don’t match up with .NET color values: the .NET values have the red, green, and blue components reversed, and also support an alpha, or transparency, value. So, DrawGauge calls a helper method named GetColor, which pulls out the color components and puts them into the order needed for .NET.

The .NET component

GaugeControl.cs, included with the downloads, is the source code for the .NET gauge component in Gauge.DLL. I started with code created by Ambalavanar Thirugnanam, available from http://www.ucancode.net/Visual_C_MFC_Samples/CSharp_Example_Free_DOTNET_Gauge_Control_Draw_Source_Code.htm, and made a number of changes to it:
  • I modified it to be a simple .NET class that returns an image as a string rather than a Windows Forms User Control that displays the gauge. This allows the image to be written to a file or displayed in a VFP Image control without having to worry about registering the .NET control as an ActiveX control and adding it to a VFP form.
  • I added properties for various colors, such as the background color, rather than using hard-coded values.
  • I added support for a background gradient in addition to a solid color.
  • Because it’s difficult to create a .NET Font object in VFP, even with wwDotNetBridge, I added properties for the name, size, bold, and italics settings of fonts used for the dial text and labels. GaugeControl uses these properties to instantiate a Font object with the specified settings.
If you’re interested in how the .NET component works, I recommend reading the article mentioned above as it discusses the logic and math involved in drawing the gauge. Then examine the C# source code in GaugeControl.cs to see how it’s implemented.

If you build the Gauge solution that includes GaugeControl.cs, you’ll find that it has a post-build event that copies the DLL to the parent folder of the solution, which is the same folder as Gauge.VCX is located. Note that if you’ve used the VFP Gauge control and VFP is still open, you have to close VFP before building the .NET solution because the .NET DLL is still open in VFP.

Since GaugeControl.cs uses GDI+ to do all of the drawing, why didn’t I convert the C# code to VFP code using the VFPX GDIPlusX project? After all, that would give us a 100% VFP solution with no need for wwDotNetBridge or Gauge.DLL. The reason I didn’t is two-fold:
  • Why reinvent the wheel? It would’ve taken several hours to convert the C# code into the equivalent VFP code and there’d be lots of debugging to make sure it works the same.
  • I’ve run into some performance issues with GDIPlusX on some machines. In fact, this is what prompted me to look at this solution in the first place. I was using the VFPX FoxCharts project to draw gauges but found that on some systems, it was taking a minute or more to draw the gauge. In tracking the problem down, I found that on those systems, some of the GDI+ function calls were taking an order of magnitude longer to execute, and these functions were called thousands of times for each gauge. I don’t know why some systems have this performance problem with GDIPlusX but the .NET component has no such problems on those systems.

Deploying Gauge

Deploying Gauge is straightforward:
  • Add Gauge.VCX and wwDotNetBridge.PRG to your project.
  • Use the Gauge class as you see fit: to create image files for reports (cImage is in PNG format) or for the source of images in forms.
  • Include wwDotNetBridge.DLL, ClrHost.DLL, and Gauge.DLL in your installer or copy those files to the client’s system. No registration is required for any of these components.
Gauge.DLL requires version 2.0 of the .NET framework. Windows Vista and later come with .NET 2.0 so this is only an issue for Windows XP and earlier. If you use Inno Setup as your application installer, you can make your installer detect whether .NET 2.0 is missing and automatically download and install it by adding #INCLUDE DotNet2Install.iss to your Inno script file. DotNet2Install.iss is included in the downloads, as is Isxdl.DLL, a component used by DotNet2Install.iss.

Last edited Mar 24 at 8:13 PM by DougHennig, version 10

Comments

hbgmail Apr 21 at 7:24 AM 
Hi Doug,
the fourth band is because the users are used to "trafic lights" (red-yellow-green) and then the fourth will be (light) red again if limit is exceeded, , the "wrong green" is the problem.

I will try with marker for goal.

Best regards
tom

DougHennig Apr 20 at 6:48 PM 
Hi Tom.

I don't think it would be too hard to add a fourth band, but then I foresee requests for a fifth, a sixth, etc. What about using two bands for the standard and the third band for values over the max?

Doug

hbgmail Apr 15 at 8:24 AM 
Hello Doug,

again : GREAT.

"offer" : I created a qud little form based on your sample which on click generates code to set the propertys (or only the ones differing from default) to be copied to clipboard. It also shows a gridsample and a form sample. If you are interested, I can mail it to you.

Just one idea : Could there be a fourth color/band if value is bigger then max value ? Users prefer red-yellow-green (like traffic light) for standard but woul be glad to have a fourth color if value is bigger then goal value.

Again THANKS for your work
Tom

AndrewNickless Mar 26 at 1:58 AM 
Perfect !

Thank you Doug

Regards
Andrew

DougHennig Mar 24 at 9:50 PM 
Hi Andrew.

OK, I added the ability to display a marker (a diamond shape) at the goal position.

Doug

AndrewNickless Mar 22 at 8:04 PM 
Sorry I wasn't very clear.

An example would be a Sales person who has :

Sales Value - Main needle
Lowest Month value -band
Monthly Average Value - band
Highest Month value - band
Sales Target (extra Needle or Marker on the Gauge )
I have seen a lot of gauges with a Marker which can be placed by value on the circumference say an arrow , but thought a second thin needle of a different colour would achieve the same and maybe easier.

Andrew

DougHennig Mar 22 at 7:04 PM 
Hi Andrew.

Why not just use a second gauge?

Doug

AndrewNickless Mar 21 at 8:14 PM 
Hi Doug

I have being doing a lot of charts and adding gauges , very nice! Thank you.

Would it be easy to add an option for a second needle nValue2 and needle colour(s) ?

Regards Andrew

DougHennig Mar 18 at 11:26 PM 
Hi Tom.

That error occurs in wwDotNetBridge.prg, which is a component created by Rick Strahl. It looks like the IsDotNet function is missing from that prg. I've added it and uploaded an updated version.

Doug

hbgmail Mar 18 at 4:44 PM 
Hello Doug,

just found one problem and do not really know where to search for :

When I set sample.scx as mainfile and try to compile I get an error : wwdotnetbridge.prg unknown ISDOTNET - undefined. Same happens if I use it in a testproject. I cannot find isdotnet()

Can you help me get rid of that error ?

Thanks in advance
tom

DougHennig Mar 11 at 8:45 PM 
Hi Brett.

I didn't really intend for you to use the sample forms as is; they're just intended to show how you can use the control. I'm glad you found BINDEVENT to handle Click but you can also do it by creating your own dashboard form with image controls on the form at design time, and then add code to the Click method of those images.

Doug

BrettHudson Mar 11 at 3:58 AM 
Hi Doug, Brett again. Got it all working.

Add the following line of code to the Drawgauges method in the Dashboard form immediately before the NEXT lnI that closes out the FOR ENDFOR loop.

BINDEVENT(THIS.&lcname, "Click", THIS, "m_click_gauges")

Then create a new method in the form called "m_click_gauges" with the following code. When you run the form and click on one of the gauges you will be able to pick up the click and the clicked gauge name will be displayed.

AEVENTS(laevents, 0)
IF PEMSTATUS(laevents[1,1],[Name],5)
obj_name=laevents[1,1].NAME
=MESSAGEBOX(EVALUATE("thisform."+obj_name+".NAME"))
ENDIF

BrettHudson Mar 10 at 9:09 PM 
Hi Doug. Brett again (for hopefully the last time).

As mentioned on my previous comment, I was able to access and setup the Tooltiptext object for the multiple images in the Dashboard form but I am still having issues trying to access the Click method. I understand that I can directly setup code in the Click Method of the Image object in the SampleGauge form but how do I do likewise for the images on the Dashboard form when each Image is being created using the ADDOBJECT command? If we were able to provide access to the click method for each of the images that are displayed in the Dashboard form, then we could really open up the process to users by putting in code on the click method of the multiple images so that a user could click on any one of the Gauge images displayed on the Dashboard form and then drill down into the underlying data.

Thanks again, Brett

BrettHudson Mar 10 at 2:18 AM 
Hi Doug, Brett again. Excellent.

Thank you very much for the advice. Got it all working. Just added in the line of code below in the 'DrawGauges' method of the 'Dashboard' form immediately between the 'EndCase' statementand the ".Drawgauge()" command. Note that I set the .ShowTips property to True in the Dashboard form.

THIS.&lcname..TOOLTIPTEXT = "My tool tips"

DougHennig Mar 9 at 10:03 PM 
Hi Brett.

Not the Gauge class; the Image object you use to display the image created by the Gauge class. For example, in SampleGauge.scx, the gauge image is displayed in imgGauge, which is just an ordinary VFP Image object.

Doug

BrettHudson Mar 9 at 9:29 PM 
Hi Doug, Brett again.
Thanks for responding to my comment. Excuse my lack of knowledge on this, but I'm just not sure how to set up the tool tips in this instance. I checked the properties and methods for the Gauge class and there isn't any Tool tips type property or Click type methods that I can see that would allow some type of interaction with a mouse click or a mouse hover on the gauge.

DougHennig Mar 9 at 2:04 PM 
Hi Brett.

The VFP part is just an Image control, so do what you want with it, such as setting the ToolTipText property.

Doug

BrettHudson Mar 9 at 12:15 AM 
Hi Doug. Really great work and simple to use. I have setup some local development here to test it out and am finding it opens up allot of opportunities.
Question.
Is it all possible to be able to get some kind of tool tips displayed as you hover the mouse over the gauge. For example, if you are showing a dashboard with many gauges on the screen, being able to hover over an individual gauge and have some additional info displayed about the data on the gauge would be helpful.

hbgmail Mar 7 at 7:57 AM 
Hello, Doug

EXCELLENT ! Thanks a lot.
I wish some of our expensive commercial tools would have such a support.

Best regards
tom

DougHennig Mar 6 at 4:56 PM 
I added support for nMinValue being 100 and nMaxValue being 0 plus increased the size of the spinner.

hbgmail Mar 3 at 8:07 AM 
Hi Doug,
very, very good.

Question :
we have situations where the start is 100 and the GOAl is 0, so 100 should be on left and 0 should be on right side. I tried with setting the band color from left to right to green,yellow, red , but it looks "strange".
Any chance to reverse the way of drawing without too much work :-) ?

And just a cosmetic hint : The spinner for transparency in sampleform is a little bit small showing **

Thanks for sharing
regards
tom

DougHennig Feb 16 at 6:10 PM 
Hi Richard.

No, I am definitely not replacing FoxCharts. I had an issue with FoxCharts drawing gauges, which it wasn't intended to do but can be hacked into doing.

Doug

rkaye Feb 16 at 2:40 PM 
Very cool, Doug!

There's an implication in your description that you are planning to perhaps replace FoxCharts? Or am I reading too much into this?