Sunday, June 2, 2013

WPF Manhattan Bar Chart - Tutorial #1

Introduction

3D graphics within a UI can often be gimmicky and fail to provide any real added insight. They look good when putting together a brochure, but users find these controls awkward and confusing. I've made several attempts to integrate 3D ideas into charts, and it rarely pays off. Users prefer the cleaner 2D interface. That said, 3D bar charts can be both functional and visually appealing. A simple 3D bar chart can be constructed with relative ease using the WPF 3D capabilities.

Over the next several tutorials, I will construct a Manhattan bar chart that will aim to be an easy to use WPF control that can be dropping into any project. These tutorials will cover the creation of the control itself, the data formatting, the 3D rendering, the labeling, and various other components that will aid in reusability.

Creating the control

To create this control, I've decided to use the Custom Control (WPF) template. I opted for this over the User Control (WPF) template for a couple reasons.
  1. A user control is generally used as a grouping of already existing controls. That's not what we're doing here.
  2. The custom control sets up the /Themes/Generic.xaml, which allows for a default style containing the ControlTemplate to be defined for the control we're creating.
Custom Control (WPF) can be found in the WPF templates.
To add the control, go to "Add New Item" within a C# project a select "Custom Control". Name the control "ManhattanBarChart.cs" and add it to the project. This will create the following class.
public class ManhattanBarChart : Control
{
    static ManhattanBarChart()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(ManhattanBarChart), new FrameworkPropertyMetadata(typeof(ManhattanBarChart)));
    }
}

You can see that we've got our ManhattanBarChart class, and it inherits directly from Control. In WPF, a control is lookless by nature. To give it a visual, a ControlTemplate is necessary. So how do we define a ControlTemplate for our control? That's where theme styles come into play. A theme style is just like a regular style, except it's set implicitly. So by default, your control will have all properties set to the values indicated by the theme style. This is different than the Style property on the Control, which is known as the explicit style. Setting an explicit style will replace any values you set in the themes style with the settings defined on the explicit style.

So to give our control a visual, we need a themes style. Looking back at our class, notice that a static constructor has been created, and in it the DefaultStyleKeyProperty.OverrideMetadata method is called. Calling the OverrideMetadata method is a way of indicating which style should be used as the themes style for ManhattanBarChart. The default parameters set the themes style as the default style for type ManhattanBarChart. Now lets take a look at Generic.xaml, located in the Themes directory.
<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:ManhattanBarChartDemo">


    <Style TargetType="{x:Type local:ManhattanBarChart}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:ManhattanBarChart}">
                    <Border Background="{TemplateBinding Background}"
                            BorderBrush="{TemplateBinding BorderBrush}"
                            BorderThickness="{TemplateBinding BorderThickness}">
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

There is our default style, already created for us and ready for use. As mentioned before, to give the control a visual, Control.Template must be set to a ControlTemplate. This is done on lines 7-16. The default template is a simple border, with template bindings that will apply properties set on the ManhattanBarChart to the controls that make up the template. This ControlTemplate is where we'll do all of our XAML work.

Viewport3D

You may be familiar with drawing in WPF using the Canvas. The Canvas provides a simple way to draw almost anything within the specified boundaries. All you have to do is add the items to the Children collection on the Canvas, and it magically appears. It's great, but limited. The biggest limitation is that all drawing is 2D. Tapping into 3D drawing capabilites is a bit more invovled. But it's not all bad. WPF does provide some high level classes that make working within 3D space less painful that past experiences you may have had (working directly with Direct3D or OpenGL, for example).  The starting point for any 3D drawing within WPF is Viewport3D

Viewport3D inherits FrameworkElement, and therefore can be added anywhere within your UI. It's responsible for transforming your 3D scene into 2D space. Much like Canvas, it also has a Children collection. But this is a collection of Visual3D. These items make up the scene, but we'll take a closer look at that in Tutorial #2. For now, lets focus on the setup of the view.

Camera
Every viewport has a camera. Conceptually, the camera is exactly what it sounds like. If you take a camera, point it at a tree, and take a picture, you'll end up with a photograph of a tree. In that scenario, the viewport is the resulting photograph. It displays what's captured by the camera. There are three types of camera's that can be used.
  1. PerspectiveCamera - This is arguably the most widely used camera. It applies perspective to the scene so that objects further from the camera appear smaller. This gives depth, or perspective.
  2. OrthographicCamera - Much like the PerspectiveCamera, except no perspective is applied. Objects are the same size, regardless of distance from the camera.
  3. MatrixCamera - This is a more advanced camera that allows the developer to specify the individual view and projection matrices.
We'll be using PerspectiveCamera with our viewport. To setup the camera, we need to set a position within the scene, the direction that the camera is looking, and the up axis.

<PerspectiveCamera Position="0,0,1" LookDirection="0,0,-1" UpDirection="0,1,0" />
With these settings the camera is sitting at 0,0,1 in the scene and looking down the Z-axis at the scene origin. The up axis is set as the Y-axis (0,1,0), indicating the Y-axis is our vertical axis. That's all we have to do for now. This camera is ready for use.

Adding Viewport3D to the ControlTemplate

It's now time to add to our ControlTemplate that resided in Generic.xaml. Recall that this template will represent the how our control is viewed, as it's set within the default style. Lets add the Viewport3D we've been working on.
<ControlTemplate TargetType="{x:Type local:ManhattanBarChart}">
    <Border Background="{TemplateBinding Background}"
            BorderBrush="{TemplateBinding BorderBrush}"
            BorderThickness="{TemplateBinding BorderThickness}">
        <Viewport3D Name="viewport">
            <Viewport3D.Camera>
                <PerspectiveCamera Position="0,0,1" LookDirection="0,0,-1" UpDirection="0,1,0" />
            </Viewport3D.Camera>
        </Viewport3D>
    </Border>
</ControlTemplate>

Not much there yet, but we've got our Viewport3D and set the camera. Our control is setup and ready for 3D rendering!

In the next tutorial, we'll setup the rendering of the bars.

Source code for tutorial #1

No comments:

Post a Comment