TimNew
------------
To Realize your potential
Posted via email from 米良的实验室
Posted via email from 米良的实验室
My WPF Charting Comparisons
I have recently been looking for some graphing/charting functionality for a home project I am working on. My requirements are fairly simple:
- handle data quantities in the region of thousands and tens of thousands of rows/items
- be able to display line charts with or without data points (there will be so many data points that they can become noise)
- be able to display multiple sets of data to be able to compare data
- free or cheap
- xcopy install
Now as the Charting products I wanted to compare were all going to be in WPF I assumed that these requirements were just a given but apparently not, so let me specify them as well
- be able to bind the data from my own view model (i.e. I don’t want to have controls littering my View Model)
- have the graph update as the data changes
Now to see the list of contenders:
- WPF Toolkit Charting (aka System.Windows.Controls.DataVisualization.Toolkit)
- AmCharts
- VisiFire
- DynamicDataDisplay (aka D3)
So for the really quick review of each
WPF Toolkit Charting
This is the CodePlex project from some of the lads at Microsoft. This is presumable of a lesser quality than the rest of the Toolkit as the Charting component is in preview. The WPF Toolkit allows for great looking charts by utilising the power of WPF Styles. It is one of those balancing acts that must be difficult to make when designing software; extensibility vs. simplicity. The WPF Toolkit leans more towards the extensible option. Extending the charts to look the way you want can be done but many will find it fiddly and frustrating, but once done can be very rewarding and the Graphs can look amazing. The WPF Toolkit also utilises the power of WPF binding by allowing me to bind to my ViewModel. So it looks like a good start, however, the clear and painful problem with the WPF Toolkit is performance. When loading up even hundreds of rows/items the performance is fairly poor. When I tried to throw just over a thousand items at a Line Series the performance was completely unacceptable. One other problem I have is that I get intermittent lock ups. When updating the data, the charting code will run off into a loop and not come out of it, freezing the UI. Hmmm another cross.
Positives:
- Extensibility allows for beautiful graphs
- Charts bind to ViewModel
- Free
Negatives:
- Woeful performance
- Random lock ups.
AmCharts
AmCharts appears to be a Charting solution aimed at the financial industry. The chart control that I thought would best fit my needs was the Stock chart. This chart had a great feature that allowed zooming on the X-axis by providing a range slider. Performance was great when I threw ~1500 items at the control. An odd problem I had was the graph would only appear once I resized my window. I think this has to do with binding to a ViewModel as the Demo does not have this problem but it also directly interacts with the control from the Code behind. I want to avoid “messing with controls” from my ViewModel. A more real problem I have is that while the performance is great, the binding seems to be a once off event. Changes to the values in my collection are not reflected by any change to the chart.
Positives
- Good performance
- Charts bind to ViewModel
- Zoom functionality
- Good samples
- Smallest DLL size (223KB)
Negatives
- One time data binding
- Odd problem with Chart not rendering until i resized the window.
Visifire
VisiFire charts looked to be a great option. They were very easy to get up and running, had some good samples like the AmCharts. My first play with the Visifire charts provided me with a good looking chart. My problems came when I went to bind the Charts to my ViewModel…Visifire does not support data binding! I’m not even sure why someone would write a WPF control that does not support data binding. I wasted plenty off time writing some adapters so that I could get data binding working. Data binding is in the wish list for version 3 (how it didn't make it into the wish list for the 1st version I don’t know). Performance of Visfire was pretty good (not spectacular) and sat in between AmCharts and the hopelessly slow WPF Toolkit.
Positives
- Easy to get up and running
- Pretty good looking default charts
- Moderate performance
Negatives
- You cant bind a data series to a collection!
Dynamic Data Display
The Dynamic Data Display (aka D3) is another Microsoft project on Codeplex from a Microsoft research team in Russia. D3 authors claim outstanding performance even with massive amounts of data. Sounds like a sure fire winner! The control library also supports different types of charts to the other libraries like Maps and Isolines ( I have no idea what an isoline is). The samples show some good stuff with smooth moving animated graphs with dynamic data points. The big fail on the project is again, no data binding. All manipulation of the charts needs to be done in C# code and needs to be very imperative. There are some guys, however, who have made posts creating an extension to the controls to support data-binding. Either way, while this looked to be a good set of controls, the authors don’t appear to have followed the Pit-of-Success principle. I would go in to details, but the fact it took me hours of reading forums, looking at samples and coding to just get my Model showing on the screen. When it did get on to the screen it was fast, but didn’t update when the underlying data changed. This is a very immature set of controls but may have a bright future if the team can get some fundamentals right.
Positives
- High performance
- Easy to scroll and zoom data
Negatives
- Hardest set of controls to work with. Everything has to be done in code. Authors seem to miss the point of WPF entirely. Presentation and logic feel very much couple together.
- After all my mucking around the chart didn’t update with my changes to the data.
In summary, I am pretty disappointed with the state of all of these charting controls. What I did manage to get working to a satisfactory state was the WPF Toolkit. As the only real problem I had with the WPF Toolkit charting controls was their performance; I decided that an easy way to get some better performance out of the control was to only show as many data points as there were available pixels. If I only have 400 pixels to show my data it becomes a bit silly to try and get the graph to render 1400 data points. I created a Custom Control that extends CollectionViewSource by having a MaxItemCount property that can be set to effectively filter the amount of data the CollectionViewSource reveals to the Charting controls. The performance was better but I was able to further tweak the performance by adding a DivisionConverter to further reduce the collection size by the parameter specified (10 in my case). This means I only show a data point for every 10 pixels wide the chart is. This ended up being a great compromise….except for the random lock ups. If I play on the Chart for long enough changing the data to update the chart, eventually the program just falls in to a loop. If I can solve this bug I may have a winner on my hands. Ed:—Playing around more I may have got rid of this problem. Still pops up sometimes straight after a build, but a restart fixes it. This may be to do with my build of Win7 (pre release that I am still running). This throws the WPF Toolkit +the 2 tiny bits of filter code clearly into the lead as it can be made to look great and handle tens of thousands of rows.
If any one is interested in the code I used to test/play with each of these libraries you can find a zip of the VS2008 solution here. To see any of the spikes, just set it as the start up project and run or Right click on the project and “Debug”—> “Start new instance”. Only the MyDomain project wont run as that is the Class library that has the small part of the domain to test the charts with.
ChartingPlaygournd.zip – Source code for my tests.
Posted via email from 米良的实验室
No matter how fast things are, they never seem to be fast enough. Even if we had the world's most optimized code in the Silverlight/WPF Data Visualization assembly, I bet there would still be a couple of people who wanted better performance. :) Unfortunately, we have don't have the world's most optimized code, and performance concerns represent one of the most common customer issues with Charting. While I wish we had the resources to commit to a few weeks of focused performance work, things just haven't panned out like that so far.
Instead, I've got the next best thing: a collection of simple changes anyone can make to noticeably improve the performance of common scenarios with today's bits! To demonstrate the impact of each of these tips, I've created a new "Performance Tweaks" tab in my
DataVisualizationDemos
sample application. The controls on this new page let you pick-and-choose which optimizations you'd like to see - then allow you to run simple scenarios to get a feel for how effective those tweaks are. And becauseDataVisualizationDemos
compiles for and runs on Silverlight 3, Silverlight 4, WPF 3.5, and WPF 4, it's easy to get a feel for how much benefit you can expect to see on any supported platform.For each of the seven tips below, I list simple steps that show the performance benefit of the tip using the new sample page. Performance improvements are best experienced in person, so I encourage interested readers to download the demo and follow along at home! :)
Tip: Use fewer data points
Okay, this first tip is really obvious - but it's still valid! Fewer data points means less to process, less to manage, and less to render - all of which mean that scenarios with few points to tend to be faster and smoother than those with many points. You can often reduce the number of points in a scenario by plotting fewer values, aggregating similar values together, or by showing subsets of the whole data. This approach isn't always practical, but when it is, it's usually a big win - and has the added benefit that the resulting chart is less cluttered and can even be easier to understand!
Aside: Typical performance guidance for Silverlight and WPF recommends capping the total number of UI elements in the low- to mid-hundreds. Given that each of Charting'sDataPoint
instances instantiates around 5 UI elements, it's easy to see why rendering a chart with 1000 data points can start to bog the system down.Slow:
Reset
, check onlySimplified Template
,Create Chart
,Add Series
,1000
points,Populate
Fast:
Reset
, check onlySimplified Template
,Create Chart
,Add Series
,50
points,Populate
Tip: Turn off the fade in/out VSM animations
By default, data points fade in and fade out over the period of a half second. This fade is controlled by a Visual State Manager state transition in the usual manner and therefore each
DataPoint
instance runs its own private animation. When there are lots of data points coming and going, the overhead of all these parallel animations can start to slow things down. Fortunately, theDataPoint
classes are already written to handle missing states, so getting rid of these animations is a simple matter of modifying the defaultTemplate
to remove the "RevealStates"/"Shown" and/or "RevealStates"/"Hidden" states.Slow:
Reset
, uncheck everything,Create Chart
,Add Series
,100
points,Populate
Fast:
Reset
, check onlyNo VSM Transition
,Create Chart
,Add Series
,100
points,Populate
Tip: Change to a simpler DataPoint Template
I mentioned above that overwhelming the framework with lots of UI elements can slow things down. So in cases where it's not possible to display fewer points, it's still possible to display fewer elements by creating a custom
Template
that's simpler than the default. There is a lot of room here to creatively balance simplicity (speed) and visual appeal (attractiveness) here, but for the purposes of my demonstration, I've gone with something that's about as simple as it gets:Style x:Key="SimplifiedTemplate" TargetType="charting:ScatterDataPoint"> Setter Property="Template"> Setter.Value> ControlTemplate TargetType="charting:ScatterDataPoint"> Grid Width="5" Height="5" Background="{TemplateBinding Background}"/> ControlTemplate> Setter.Value> Setter> Style>Aside: This tip and the previous one are the only two tips that are mutually exclusive (because they both involve providing a customDataPointStyle
for the series). Otherwise, you have complete freedom to mix-and-match whatever tweaks work well for your scenario!Slow:
Reset
, uncheck everything,Create Chart
,Add Series
,100
points,Populate
Fast:
Reset
, check onlySimplified Template
,Create Chart
,Add Series
,100
points,Populate
Tip: Specify fixed ranges for the axes
For convenience and ease-of-use, Charting's axes automatically analyze the data that's present in order to provide reasonable default values for their minimum, maximum, and interval. This works quite well in practice and you should hardly ever have to override the automatic range. However, the code that determines the automatic axis ranges isn't free. This cost isn't significant for static data, but if the underlying values are changing a lot, the small cost can accumulate and become noticeable. If you're fortunate enough to know the ranges over which your data will vary, explicitly specifying the axes and giving them fixed ranges will completely eliminate this overhead.
Slow:
Silverlight 3
,Reset
, uncheck everything,Create Chart
,Add Series
,100
points,Populate
,Change Values
Fast:
Silverlight 3
,Reset
, check onlySet Axis Ranges
,Create Chart
,Add Series
,100
points,Populate
,Change Values
Tip: Add the points more efficiently
Silverlight/WPF Charting is built around a model where any changes to the data are automatically shown on the screen. This is accomplished by detecting classes that implement the INotifyPropertyChanged interface and collections that implement the INotifyCollectionChanged interface and registering to find out about changes as they occur. This approach is incredibly easy for developers because it means all they have to touch is their own data classes - and Charting handles everything else! However, this system can be counterproductive in one scenario: starting with an empty collection and adding a bunch of data points all at once. By default, each new data point generates a change notification which prompts Charting to re-analyze the data, re-compute the axis properties, re-layout the visuals, etc.. It would be more efficient to add all the points at once and then send a single notification to Charting that its data has changed. Unfortunately, the otherwise handy ObservableCollection class doesn't offer a good way of doing this. Fortunately, it's pretty easy to add:
// Custom class adds an efficient AddRange method for adding many items at once // without causing a CollectionChanged event for every item public class AddRangeObservableCollection : ObservableCollection { private bool _suppressOnCollectionChanged; public void AddRange(IEnumerable items) { if (null == items) { throw new ArgumentNullException("items"); } if (items.Any()) { try { _suppressOnCollectionChanged = true; foreach (var item in items) { Add(item); } } finally { _suppressOnCollectionChanged = false; OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } } } protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { if (!_suppressOnCollectionChanged) { base.OnCollectionChanged(e); } } }Slow:
Reset
, uncheck everything,Create Chart
,Add Series
,500
points,Populate
Fast:
Reset
, check onlyEfficient Collection
,Create Chart
,Add Series
,500
points,Populate
Tip: Disable the data change animations
Because people perceive changes better when they're able to see the change happening, Charting animates all value changes to the underlying data points. So instead of a bar in a bar chart suddenly getting longer when its bound data value changes, the bar smoothly animates from its old value to its new value. This approach has another benefit: it calls attention to the value that's changed in a way that an instantaneous jump wouldn't. However, animating value changes can take a toll when there are lots of changes happening at the same time or when there are a continuous stream of changes over a long time. In cases like these, it can be helpful to lessen the default duration of the animation (a half second) by lowering the value of the series's
TransitionDuration
property - all the way down to 0 if that's what it takes.Slow:
Silverlight 3
,Reset
, uncheck everything,Create Chart
,Add Series
,100
points,Populate
,Change Values
Fast:
Silverlight 3
,Reset
, check onlyZero Transition Duration
,Create Chart
,Add Series
,100
points,Populate
,Change Values
Tip: Use a different platform or version
Though they offer basically identical APIs, Silverlight and WPF are implemented very differently under the covers - and what performs poorly on one platform may run quite well on the other. Even staying with the same platform, Silverlight 4 contains a number of improvements relative to Silverlight 3 (as does WPF 4 vs. WPF 3.5). Therefore, if you have the freedom to choose your target platform, a bit of prototyping early on may help to identify the best choice for your scenario.
Slow:
Silverlight 3
,Reset
, uncheck everything,Create Chart
,Add Series
,100
points,Populate
,Change Values
Fast:
WPF 3.5
,Reset
, uncheck everything,Create Chart
,Add Series
,100
points,Populate
,Change Values
Posted via email from 米良的实验室
Introduction
Hardware accelerated effects for WPF were first introduced in .NET 3.5 SP1. Very complex effects and graphically rich applications can be created with little impact on performance, thanks to the huge computing power of modern graphic cards. However, if you want to take advantage of this feature, you first need to learn a thing or two. The purpose of this article is to provide all the information you need to get started with Effects.
What is an Effect?
Effects are an easy-to-use API to create (surprisingly) graphical effects. For example, if you want a button to cast a shadow, there are several ways to accomplish the task, but the simplest and most efficient method is to assign the "
Effect
" property of the button, either from code or in XAML:CollapseMyButton.Effect = new DropShadowEffect() { ... };Collapse<Button Name="MyButton" ... > <Button.Effect> <DropShadowEffect ... /> </Button.Effect> </Button>As you can see, effects are so easy to use that you don't need any further explanation. The fun starts when you decide to write your own effects...
BitmapEffect, Effect, ShaderEffect... What?
First of all, there are several .NET classes that share the "
Effect
" suffix, and to make it even more confusing, they are all in the System.Windows.Media.Effects namespace. However, not all of those classes are useful when it comes to hardware acceleration, in fact some of them are completely useless.BitmapEffect
The BitmapEffect class and its subclasses were originally supposed to provide the functionality of effects. However, this API doesn't use any hardware acceleration and it has been marked obsolete in .NET 4.0. It's strongly recommended to avoid using the BitmapEffect class or any of its subclasses!
Effect and its Derived Classes
As stated above, you apply an effect to a control by assigning the control's Effect property (the property is actually inherited from UIElement, just in case you needed to know). Now the question is... What needs to be assigned to the
Effect
property? The answer is as simple as it can be - it's an object of typeEffect
.The Effect class is the base class of all hardware accelerated effects. It has three subclasses:
BlurEffect
,DropShadowEffect
andShaderEffect
. The first two are ready-to-use effects included directly in the .NET library. The ShaderEffect class is the base class of all custom effects.Why BlurEffect and DropShadowEffect?
Why are there only 2 fully implemented effects in the library and why don't these 2 effects derive from
ShaderEffect
? I can't answer the first question, but I can tell you what makesBlurEffect
andDropShadowEffect
so special.Both
DropShadowEffect
andBlurEffect
are using complex algorithms that require multiple passes, but multi-pass effects are not normally possible. However, the guys at Microsoft probably did a few dirty hacks deep inside the unmanaged core of the WPF rendering engine and created these two effects.Note: It is possible to create a single-pass blurring algorithm, but such algorithm is terribly slow compared to multi-pass blurring. Anyway, there are probably more reasons why these 2 effects are implemented in a special way.
How Does It Work?
If you want to take advantage of hardware acceleration, you first need to know how the whole thing works.
A Few Words about the GPU Architecture
The architecture of Graphic Processing Units (GPUs) is different than the architecture of CPUs. GPUs are not general-purpose, they are designed to perform simple operations on large data sets. The operations are executed with high amount of parallelism, which results in great performance.
Modern GPUs are becoming more and more programmable and the range of tasks that can be executed on GPUs is growing (although there are several restrictions described below). Small programs executed on GPU are called shaders. There are several kinds of shaders - vertex shaders and geometry shaders are used when rendering 3D objects (not used by WPF Effects) and pixel shaders are used to perform simple operations on pixels.
There are even attempts to use the sheer computing power of GPUs for general purpose programming... Unfortunately there are several restrictions, such as limited number of instructions in one program, no ability to work with advanced data structures, limited memory management abilities, etc. Amazing speed comes with several trade-offs...
Pixel Shaders
A pixel shader is a short program that defines a simple operation executed on each pixel of the output image. That's pretty much all you need to create all kinds of interesting pixel-based effects.
Before You Write your First Effect...
WPF objects, including
Effects
, are rendered using the DirectX engine. DirectX shaders are written in High Level Shader Language (HLSL) and then compiled into bytecode. Therefore HLSL is one of the things you need to learn to write your own Effects (more about HLSL further in this article).Some people will tell you that you need to download and install the entire DirectX SDK in order to compile HLSL code. Fortunately, this is not true. All you need is to download a Visual Studio add-in written by Greg Schechter and Gerhard Schneider. It is called Shader Effects BuildTask and you can get it from the CodePlex WPF site. This add-in reportedly works with Visual Studio 2008 and 2010.
Once the add-in is installed, a new project template called "WPF Shader Effect Library" will appear in Visual Studio. The best thing about this add-in is that you can write HLSL code directly in Visual Studio (without intellisense support and syntax-highlighting though) and all your shaders will be compiled automatically when you build your project.
The First Simple Effect
Let's get started! If you have already downloaded and installed the Shader Effects BuildTask mentioned above, you can open the project attached to this article.
Each effect has 2 parts: a pixel shader written in the HLSL language (a file with the .fx extension) and class derived from
ShaderEffect
(a .cs file), which serves as a managed wrapper of the pixel shader. When you build your project, all .fx files are compiled and the resulting pixel shaders (with the extension .ps) are included in the assembly.If you select an .fx file and open the "Properties" window, you will see that the "Build Task" of this file is set to "Effect" (see the image on the right). This ensures that the effect will be properly compiled. Important: When you add a new effect to your project, it's Build Task property is not set automatically and you have to change it manually!
The effect I am going to describe is simple - it is called "
Transparency
" and it has one parameter "Opacity
". It makes a control semi-transparent depending on this parameter. Please ignore the fact that such effect is completely useless...Creating the Pixel Shader
Let's start with the difficult part: the Pixel Shader first. "Transparency.fx" contains the following code:
Collapsesampler2D implicitInputSampler : register(S0); float opacity : register(C0); float4 main(float2 uv : TEXCOORD) : COLOR { float4 color = tex2D(implicitInputSampler, uv); return color * opacity; }The first two lines contain pixel shader constants. The first constant is of type
sampler2D
and it refers to the image this effect is applied to. Yes, I know the effect is applied to a control (not necessarily anImage
), but the word "image" refers to the visual representation of the target control...The other constant is our custom input parameter (called "
opacity
") and it is of typefloat
. Although the value of this parameter can change in time, it is considered to be a constant in the scope of the pixel shader. As I have mentioned above, the pixel shader is executed once for each pixel and all pixels in one frame need the same input parameters - that's why "opacity
" is considered to be a constant.The
register
keyword is used to associate each of the constants with the registers where input values are stored. There are several "image registers" that contain the input image data and these registers are namedS0
,S1
,S2
, etc. (most pixel shaders only use one such register). There are also "floating point registers" namedC0
,C1
,C2
, etc. and these registers store the values of other input parameters.The rest of the shader is the algorithm itself. There is a method called
main
, the entry point of our shader program. This method accepts one parameter of typefloat2
and returnsfloat4
(a method with this name and signature must be in every pixel shader). Return type of this method is a vector of 4 floating-point values that represents RGBA color. The method argument is a 2-dimensional vector and you can think about it as "the x and y coordinates of the current pixel". In fact the values are not pixel-based: the coordinates of the upper-left corner are (0, 0) and the lower-right corner is represented by (1, 1).The body of the method is quite simple - the source color is found by a call to the
tex2D
method, it is multiplied by opacity and returned. When the "*" operator is used to multiply a scalar value with a vector, all components of that vector are multiplied by the scalar. You might think that only the alpha channel should be multiplied to get the correct result. However, DirectX shaders work with premultiplied alpha channel, which means that the values of RGB channels are always multiplied by alpha channel.The Effect Class
Let's take a look at the other part of the "
Transparency
" effect. "Transparency.cs" contains the following class:Collapsepublic class Transparency : ShaderEffect { static Transparency() { // Associate _pixelShader with our compiled pixel shader _pixelShader.UriSource = Global.MakePackUri("Transparency.ps"); } private static PixelShader _pixelShader = new PixelShader(); public Transparency() { this.PixelShader = _pixelShader; UpdateShaderValue(InputProperty); UpdateShaderValue(OpacityProperty); } public Brush Input { get { return (Brush)GetValue(InputProperty); } set { SetValue(InputProperty, value); } } public static readonly DependencyProperty InputProperty = ShaderEffect.RegisterPixelShaderSamplerProperty("Input", typeof(Transparency), 0); public double Opacity { get { return (double)GetValue(OpacityProperty); } set { SetValue(OpacityProperty, value); } } public static readonly DependencyProperty OpacityProperty = DependencyProperty.Register("Opacity", typeof(double), typeof(Transparency), new UIPropertyMetadata(1.0d, PixelShaderConstantCallback(0))); }As you can see, it's pretty much an ordinary class with a few dependency properties. However, there are several important things I have to point out.
The pixel shader is stored in a
private static
field_pixelShader
. This field isstatic
, because one instance of the compiled shader code is enough for the whole class. There is astatic
constructor that initializes theUriSource
property of_pixelShader
- it basically lets_pixelShader
know where to look for the compiled shader bytecode. TheGlobal.MakePackUri()
method is a helper method that converts the file name to a full uri path, which looks approximately like this:"pack://application:,,,/[assemblyname];component/Transparency.ps"
. (I'm sure you understand why this needs a helper method).There has to be a property of type
Brush
called "Input
". This property contains the input image and it is usually not set directly - it is set automatically when our effect is applied to a control. The corresponding dependency property is not initialized by callingDependencyProperty.Register()
, theShaderEffect.RegisterPixelShaderSamplerProperty()
method must be used instead. Note the last parameter of this method: it is an integer and it corresponds to theS0
pixel shader register.The other property is our custom parameter called "
Opacity
". It is declared like any other dependency property, the only difference is the value ofPropertyChangedCallback
inUIPropertyMetadata
constructor. The value must bePixelShaderConstantCallback()
and the integer parameter must be the number of the corresponding floating point register (note that the value "0
" corresponds to the register nameC0
).The last important thing that needs explanation is the constructor of our class. It sets the
PixelShader
property (which is required) and forces the shader to update all input values.A Few Notes about HLSL
As you can see, the HLSL language is a simple C-based language. Common C operators (+, -, *, / etc.) can be used as well as many math functions. Code flow control statements (such as
if
,while
orfor
) can be used as well, a complete list can be found here.The most common type used in WPF pixel shaders is
float
andfloat
-based vectors (float2
,float3
andfloat4
). A detailed description of HLSL vectors (and how to work with them) can be found here. There is noint
orbool
type in the current version of WPF pixel shaders (see table below).Accepted Parameter Types
The following table shows all allowed input types (as defined in the
ShaderEffect
class) and the corresponding HLSL types (as defined in the pixel shader). Only floating-point values are currently allowed.
.NET type HLSL type System.Boolean
(C# keywordbool
)Not Available System.Int32
(C# keywordint
)Not Available System.Double
(C# keyworddouble
)float
System.Single
(C# keywordfloat
)float
System.Windows.Size
float2
System.Windows.Point
float2
System.Windows.Vector
float2
System.Windows.Media.Media3D.Point3D
float3
System.Windows.Media.Media3D.Vector3D
float3
System.Windows.Media.Media3D.Point4D
float4
System.Windows.Media.Color
float4
Taking It a Step Further
If you have read all the above, you understand the basics of effects. Here are a few important things you might want to know before you start creating your own effects:
Animations
Effects are
DependencyProperty
-based and they can be animated just like any other WPF element.Displacements
Effects can do much more than change the color of a pixel. See the following example:
Collapsefloat4 main(float2 uv : TEXCOORD) : COLOR { uv = uv / 2; float4 color = tex2D(implicitInputSampler, uv); return color; }The
uv
value is divided by 2 before getting the source color, which will stretch the top-left quarter of the target control to the whole area of the control. Much more complicated transformations can be used to create interesting effects.You can even create a displacement effect that will respond to user input correctly (such as mouse-over etc.) - all you need to do is to set the
Transform
property of your effect class.Multi-input Effects
Effects can have several input images to execute advanced blending operations. This is beyond the scope of this article, but you can find a detailed description of this technique on Greg Schechter's blog.
Common Mistakes
The way pixel shaders are compiled from Visual Studio introduces several potential bugs. These bugs do not cause any compile-time errors and are sometimes difficult to find.
- First of all, when you add a new Shader Effect to your library, don't forget to set the "Build Task" of the new .fx file to "
Effect
". If you forget to do this, your shader will not be compiled and your application will crash as soon as it attempts to use the effect.- Another possible problem is the way compiled effects are included in your managed assembly. If you change the file structure of your project, or if you rename an effect source file, don't forget to change the file path in the associated
PixelShader
constructor, otherwise your application will crash when it attempts to create an instance of the effect.- If you add a new parameter to your effect, don't forget to add a new
UpdateShaderValue
method call to the constructor of your effect class. Otherwise, your effect might use wrong default values.- Be careful when defining default values of effect parameters, because the default value type must match the parameter type exactly. If a property is of type
double
, you can't simply use an integer literal (such as "1
") as its default value, you have to use double values such as "1.0
" or "1d
".Recommended Resources
Greg Schechter wrote a brilliant series of articles about Shader Effects, by far the best resource I have found. The series includes several examples, explains how to create multi-input effects and more.
Walt Ritscher created a great tool called Shazzam, an interactive development tool for Shader Effects. Shazzam lets you write an effect, apply it to any image and change all input parameters interactively. It even generates the associated C#/VB code for you. See the official Shazzam page to learn more. (Thanks a lot to Sacha Barber for bringing Shazzam to my attention.)
Nick Darnell wrote WPF ShaderEffect Generator, a Visual Studio plugin that lets you write and compile shaders directly in VS, a great alternative to BuildTask from Greg Schechter and Gerhard Schneider. The main difference is that Nick Darnell's plugin generates all C# classes automatically from the finished HLSL code (a functionality very similar to Shazzam). Thanks to U-P-G-R-A-Y-E-D-D for posting a link to this project!
Tamir Khason created a small program called HLSL Tester. This application is a great help if you are completely new to the HLSL language. It lets you load a bitmap image and then write simple pixel shaders, debug them and apply them to your image interactively. The only disadvantage is that this application requires the DirectX SDK to be installed.
WPF Pixel Shader Effects Library is an open-source library of high quality ready-to-use effects. The library (including source code) can be downloaded from CodePlex: http://wpffx.codeplex.com/
Sample Project
The Transparency effect with a simple WPF test application can be downloaded here.
The End
That's it. I hope this article gave you all you need to create your own amazing hardware-accelerated effects. I recommend that you pay attention to the links mentioned in the "Recommended Resources" section above.
I will be happy to answer all your questions and any feedback is highly appreciated.
Version History
- Edited 2010-07-24: Added links to the Recommended Resources section
License
This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)
About the Author
Kubajzz
Czech RepublicMember
I'm sort of lazy...
Posted via email from 米良的实验室
Introduction
I’m currently working on a few Data Visualization projects and am using WPF most of the time. Charting controls are very useful for the one related to statistics and data handling. WPF toolkit is free and open source, however is used by few because of its limited charting support. In my opinion, it is quite useful and straightforward to use.
Here, I’m just demonstrating the basic charting controls and setting data for display. For articles related to this in future, I shall demonstrate advanced features of WPF Toolkit.
Beginning
No prior knowledge of WPF is required. You just need to be aware of HTML (which I’m pretty sure everyone is, nowadays). XAML coding is pretty fun.
Firstly, I’ll mention the installation steps and then will dive into coding XAML and related C# files for visualizing static set of data.
First Step – Install WPF Toolkit
Install WPF Toolkit from this site:
(Please check the installation and usage instructions as mentioned here.)
Add new WPF application in Visual Studio.
If you are not able to view chart controls in Toolbox, right click Toolbox and select Choose Items. Then click on WPF components and select chart controls (the ones mentioned in the title). This will add the controls to your toolbox and you should be able to drag and drop them on the XAML form.
Second Step – XAML Coding for Charting Controls
XAML (Extensible Application Markup Language) is a markup language for declarative application programming. If you are interested in knowing more about XAML, please refer to the MSDN documentation at http://msdn.microsoft.com/en-us/library/ms747122.aspx.
As you can see in the following MainWindow.xaml code, there are a lot of
tags, each one refers to the 5 different charting controls that we are going to use.
CollapseWindow x:Class="WpfToolkitChart.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="1031" Width="855" xmlns:chartingToolkit= "clr-namespace:System.Windows.Controls.DataVisualization.Charting; assembly=System.Windows.Controls.DataVisualization.Toolkit"> ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto" Margin="0,-28,0,28"> Grid Height="921"> chartingToolkit:Chart Height="262" HorizontalAlignment="Left" Margin="33,0,0,620" Name="columnChart" Title="Column Series Demo" VerticalAlignment="Bottom" Width="360"> chartingToolkit:ColumnSeries DependentValuePath="Value" IndependentValuePath="Key" ItemsSource="{Binding}" /> /chartingToolkit:Chart> chartingToolkit:Chart Name="pieChart" Title="Pie Series Demo" VerticalAlignment="Top" Margin="449,39,43,0" Height="262"> chartingToolkit:PieSeries DependentValuePath="Value" IndependentValuePath="Key" ItemsSource="{Binding}" IsSelectionEnabled="True" /> /chartingToolkit:Chart> chartingToolkit:Chart Name="areaChart" Title="Area Series Demo" VerticalAlignment="Top" Margin="33,330,440,0" Height="262"> chartingToolkit:AreaSeries DependentValuePath="Value" IndependentValuePath="Key" ItemsSource="{Binding}" IsSelectionEnabled="True"/> /chartingToolkit:Chart> chartingToolkit:Chart Name="barChart" Title="Bar Series Demo" VerticalAlignment="Top" Margin="449,330,43,0" Height="262"> chartingToolkit:BarSeries DependentValuePath="Value" IndependentValuePath="Key" ItemsSource="{Binding}" IsSelectionEnabled="True"/> /chartingToolkit:Chart> chartingToolkit:Chart Name="lineChart" Title="Line Series Demo" VerticalAlignment="Top" Margin="33,611,440,0" Height="254"> chartingToolkit:LineSeries DependentValuePath="Value" IndependentValuePath="Key" ItemsSource="{Binding}" IsSelectionEnabled="True"/> /chartingToolkit:Chart> /Grid> /ScrollViewer> /Window>Beginning with the
tag, you can see that there is an attribute that says
xmlns:chartingToolkit
which is basically a namespace referring to the added WPF Toolkit.I’ve used
tag in order to add horizontal and vertical scrollbars to the XAML page.
Now starting with first charting control,
columnChart
, drag and drop the column series control in toolbox on XAML page and you will see a rectangle with nothing inside. Look in the XAML window (usually below the Designer), and you will see:CollapsechartingToolkit:ColumnSeries DependentValuePath="Value" IndependentValuePath="Key" ItemsSource="{Binding}" />Now all the charting controls needs to be encapsulated in
(which is a good practice). It has different attributes such as height, horizontal alignment, name, title, width, etc. which are just concerned with the way in which it appears on the page.
Our basic concern is understanding the attributes of
here and in all other charting controls. I’m using three attributes.
DependentValuePath
andIndependentValuePath
are related to the Axis of the Chart (i.e. X-axis, Y-axis). “Value
” and “Key
” as assigned to them respectively - this is because I’m usingKeyValuePair
data type in my data model (which hasKey
andValue
). You can also useDictionary
or any other data type by just making sure that you have two parameters that are interdependent for visualization.Itemsource
attribute is used for binding our data to the control.Follow the same as above for all the other controls as mentioned and now we shall assign the data model to the controls.
Third Step – Assigning Data Model to the Controls
As you can see in the MainWindow.xaml.cs file, it is pretty straightforward with the way we are assigning data model.
Collapsenamespace WpfToolkitChart { /// summary> /// Interaction logic for MainWindow.xaml /// /summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); showColumnChart(); } private void showColumnChart() { List> valueList = new List>(); valueList.Add(new KeyValuePair("Developer",60)); valueList.Add(new KeyValuePair("Misc", 20)); valueList.Add(new KeyValuePair("Tester", 50)); valueList.Add(new KeyValuePair("QA", 30)); valueList.Add(new KeyValuePair("Project Manager", 40)); //Setting data for column chart columnChart.DataContext = valueList; // Setting data for pie chart pieChart.DataContext = valueList; //Setting data for area chart areaChart.DataContext = valueList; //Setting data for bar chart barChart.DataContext = valueList; //Setting data for line chart lineChart.DataContext = valueList; } } }I’m using
static
list with 5 entries.DataContext
is the property assigned to charting controls and you can assign the list directly to the controls and you are good to go.Fourth Step – Compile and Run
Compile and run and you should see the following:
Conclusion
I hope this article provides you with enough assistance to keep the work going on for visualizing information. Information Visualization is changing the way people look at data and in my view, it is going to play a key role in future.
I’ll explain advanced features related to assigning complex data model to controls in the future.
History
- 15th May, 2011: Initial post
License
This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)
About the Author
Priyank Kabaria
Student
United StatesMember
Posted via email from 米良的实验室