Introduction
Way back in 1995, there was a buzz about a new markup language called VRML. As many of you remember, VRML was intended to bring 3D to the web. At first, we all looked at this new capability and said "Cool! Now we can put 3D stuff on the internet." Then we thought about it a bit more. Eventually, we said "Umm... so what do we do with it?"
Fast forward to 2007 where Microsoft is set to release Vista to the general public. Part of Vista is the new Windows Presentation Foundation (formerly Avalon). WPF brings all kinds of cool transformations, effects, and 3D stuff to Windows. With this we can now easily add 3D rendering to our applications. But some of us can't help thinking "Umm... so what do we do with it?"
The point I'm trying to make here is that beyond games, it's hard to come up with valid uses for 3D in normal business applications. If you try to tell your employer that you want to spend a few weeks to add some useless 3D stuff to your interface, well, use your imagination. Especially when you consider that you'll have to create each and every triangle in a mesh by painstakingly mapping it out by hand. Microsoft didn't even bother to give us any solid primitives to play with.
But there are good uses, and ones that don't require a full staff of CAD designers (Microsoft doesn't seem to understand that not every company has one of these). One of those uses is to create 3D graphs and charts. It's not that it couldn't be done before, it's just that it was never this easy. With animations, lighting, and texturing, among other features, there are a lot of ways to spice up graphs.
This library is an attempt to show you what's possible for creating 3D graphs in WPF. I am not a graphing expert and have not created an all-inclusive package. I created this and thought it would be fun to show to the community. If you have any suggestions, please post them at the bottom of this article because I'd love to expand the functionality of this library. It is also my hope that programmers looking to play around with WPF will find this article and see how easy it is to work with.
The Basic Principles
I'm a big fan of the KISS principle and tried to keep that in mind for this library. The library will try to shield you from all of the difficulties of creating the graph. My idea was to allow you to pass in some set of data and the code would determine how to scale and display the graph to include all the information.
There are three kinds of input that the graphs take: Lists, Dictionaries, and DataRows. Everything is boiled down to a Dictionary
in the end anyway. The reason for doing this was so that each piece of the graph has a key and a value. That way, when the user interacts with the graph, there is a way of communicating what part of the graph they are playing with.
I recently did some major refactoring to make the graphs easier to work with and extend. I'm also trying to clear the path to create more graphs easily. There is now a BaseGraph
class that houses some commonly used functions such as the hit-testing. Also, there is more of a focus on user interaction with the graphs since that is a big advantage to using WPF.
Bar Graphs
To create a bar graph, you simply create a new BarGraph
object:
BarGraph bg = new BarGraph(dict, Colors.CadetBlue, Colors.BurlyWood,
new TimeSpan(0, 0, 0, 0, 500));
The parameters go as follows:
- An
IList
, IDictionary
, or DataRow
object. This holds the data that is to be shown in the graph. The graph will attempt to convert each value to a double
. If it can't, it will skip that data and move on.
- Color to apply to each bar
- Highlight color. When the user mouses over a bar, it will change to this color.
- Highlight duration. This states how long it should take to make the color change between the bar's color and the highlight color, and back again.
After that we grab our Viewport3D
object, which can be added anywhere in the window:
Viewport3D v = bg.GetViewport();
In the example, the viewport is added to a grid. It's just as simple as adding a label or button to an interface.
Pie Graphs
Pie graphs are just as simple as bar graphs. The data passed in is reasoned over to determine how to split up the pie. You also create the graph in very similar fashion to the bar graph:
PieGraph pg = new PieGraph(dr, Colors.BurlyWood, new TimeSpan(0, 0, 0, 0, 500));
The parameters go as follows:
- An
IList
, IDictionary
, or DataRow
object. This holds the data that is to be shown in the graph. The graph will attempt to convert each value to a double
. If it can't, it will skip that data and move on.
- Highlight color. When the user mouses over a piece of the pie, it will change to this color.
- Highlight duration. This states how long it should take to make the color change between the pie piece's color and the highlight color, and back again.
The colors for each of the pie pieces are dictated by an array declared in the PieGraph
class. This array follows the ROYGBIV pattern and will just loop through that depending on how many pieces it has to show. Feel free to change the colors shown; it won't hurt my feelings; that much.
User Interaction
There are a number of ways that the user can interact with the graph and that you can respond to it. Both graphs now have events for MouseOver
, LeftClicked
, and RightClicked
. You can attach to these events and get the key and value associated with the piece of the graph that the user interacted with. For example:
BarGraph bg = new BarGraph(...);
bg.MouseOver += new GraphActionDelegate(BarGraphMouseOver);
...
void BarGraphMouseOver(BaseGraph sender, object key, object value)
{
}
Another option is to choose from a set of pre-programmed responses available in the graph. These are enumerated per graph. Here's an example of what you can do:
PieGraph pg = new PieGraph(...);
pg.LeftClickAction = PieGraphUserActions.Focus;
pg.MouseOverAction = PieGraphUserActions.Highlight;
This instructs the pie graph to highlight a piece of the pie when the user mouses over. On a left click, the graph will focus on that piece, which will raise the piece out of the rest of the pie. These default actions can be used in addition to standard event handling.
Highlighting
One cool feature of WPF is the ability to animate almost any property, including the color. So, when we get a mouse-over, we can animate a bar or pie piece to change it's color to indicate that it's been highlighted. One feature that I knew people would want is to identify which piece of the graph the user was highlighting. To indicate this, there is an event that is part of both graphs called GraphHighlightChanged
. It can be used in the following way:
PieGraph pg = new PieGraph(...);
pg.GraphHighlightChanged += new GraphHighlightChangedDelegate(myHandler);
...
void myHandler(IGraph sender, object key, object value)
{
...
}
The currently highlighted pieces can be read from either the parameters passed in the event handler method or through the HighlightKey
and HighlightValue
properties on the graph.
Focusing on a Piece of Pie
I thought it would be neat to show that graphs can respond to click events and animate other things besides color. One application was to let the user click on a piece of the pie graph and then raise that pie piece above the other ones. You can listen to the focus event by attaching to GraphFocusChanged
. It is very similar to the GraphHighlightChanged
event. Also, you can poll the FocusKey
and FocusValue
to get information on the piece that is currently focused.
Outlining
The 3DTools library has a class called ScreenSpaceLines3D
which gives us the ability to create lines with constant width. This is important because without it we wouldn't be able to draw such lines without them changing size depending on how close they are to the camera. It's another one of those little things that Microsoft should have given us but didn't.
The image above shows the difference that outlining makes. You can turn outlining on or off with a simple property. Be sure though to adjust this property before you call GetViewport()
.
PieGraph pg = new PieGraph(dr);
pg.Outlining = false;
Viewport3D v = pg.GetViewport();
Tooltips
One new feature I've recently added is tooltips for the graphs. There were a lot of issues with making this possible. One problem was that showing clear text on top of the Viewport3D
caused me to lose mouse over notifications. That simply wasn't an option when user interaction is clearly important in this graphing library.
The next problem encountered was how difficult it is to show text in a 3D space. The text literally has to be painted as a texture on a mesh and inserted into the scene. Which brings up the next problem, how do you get the text to always face the camera and always appear the same size? This led me to create the ToolTip3D
class.
The way the ToolTip3D
works is by using the information on the PerspectiveCamera
that is viewing the scene. The camera's up vector, look-at vector, position, and near-plane distance are used to calculate the position of the tooltip. Basically, the tooltip is calculated to be moved directly in front of the camera just a little bit beyond its near-plane. Here's a screenshot of it working on the bar graph:
It isn't quite perfect. As you can see, there is some skewing. My guess is that it has to do with using double
values and not properly accounting for any distortion associated with the perspective camera. This is something that I'll keep working on over time but I figured I'd post it here to see if anyone can improve on the code.
Completed Enhancements
- Specular highlights - Using some different material and lights, the graphs look a bit better
- Outlining - Thanks to Daniel Lehenbauer's[^] 3DTools[^], the pie graph pieces now have outlines
- Code comments - I stopped being lazy and put in the XML comments
- Refactoring code for reuse and easier user interaction
- Tooltips - Works, but needs polish
Future Enhancements
Here is a list of some of the ideas I want to implement in the near future:
- Use the lines from 3DTools to show scaling in the bar graph
- Allow the user to change the values behind the graphs and animate those changes
- Add camera animations
- Better databinding so that the graphs can be seen in the designer window
Summary
I hope you enjoy this library. It was a lot of fun to create and should show how easy it is to use the 3D capabilities of WPF. I hope I've also made a case for how 3D can be used for business applications. Over time, I want to improve this library to constantly add features and I appreciate your suggestions and comments.