This one is a little hard to describe but I am sure you have seen it before in magazines or newspapers. Although our API allows you to set brushes in code we will try to do all it XAML as it will allow more tweaking later. Here is our first attempt at showing an image on each bar
<cfx:Chart Gallery="Bar">
<cfx:Chart.Series>
<cfx:SeriesAttributes/>
</cfx:Chart.Series>
<cfx:Chart.AllSeries>
<cfx:AllSeriesAttributes>
<cfx:AllSeriesAttributes.Template>
<DataTemplate>
<Canvas>
<Rectangle Width="{Binding Path=Width}" Height="{Binding Path=Height}">
<Rectangle.Fill>
<ImageBrush ImageSource="C:\Temp\WorldMap.png"/>
</Rectangle.Fill>
</Rectangle>
</Canvas>
</DataTemplate>
</cfx:AllSeriesAttributes.Template>
</cfx:AllSeriesAttributes>
</cfx:Chart.AllSeries>
</cfx:Chart>
Nothing earth shattering, we are binding width and height of the rectangle to the Width and Height of the logical items we will create, note that we do not need to worry about Left and Top, this is handled internally. We are creating a Canvas because we plan to add more visual elements later. And this is what we get.
Obviously this is not what we are looking for, actually there is no feature that I am aware of in WPF that allows you to draw a rectangle and specify the “region” of the ImageBrush/DrawingBrush that you would like to use for the visual. Even if we try to set Stretch to UniformToFill we will have all bars showing the same part of the world.
So the idea is to make sure all bars actually draw the bitmap in the full plot area but clipped to the geometry of the bar. Our logical items support a PlotWidth and PlotHeight that return the full dimensions of the plot area but because our canvas is automatically moved to the bar position we will have to undo this movement. Our logical items expose Left and Top but not as negative so we will have to build a simple converter
public class NegateConverter : IValueConverter
{
object IValueConverter.Convert (object value, Type targetType,
object parameter, CultureInfo culture)
{
if (value is double)
return -((double) value);
else
return value;
}
object IValueConverter.ConvertBack (object value, Type targetType,
object parameter, CultureInfo culture)
{
if (value is double)
return -((double) value);
else
return value;
}
}
And modify our template as follows
<DataTemplate>
<Canvas>
<Canvas.Resources>
<local:NegateConverter x:Key="negateConverter"/>
</Canvas.Resources>
<Rectangle Canvas.Left="{Binding Path=Left,
Converter={StaticResource negateConverter}}"
Canvas.Top="{Binding Path=Top,
Converter={StaticResource negateConverter}}"
Width="{Binding Path=PlotWidth}" Height="{Binding Path=PlotHeight}"
Clip="{Binding Path=BoundsGeometry}">
<Rectangle.Fill>
<ImageBrush Stretch="UniformToFill" ImageSource="C:\Temp\WorldMap.png"/>
</Rectangle.Fill>
</Rectangle>
</Canvas>
</DataTemplate>
We are also setting the Clip property of the Rectangle to a brand new property in our logical item that will return a Geometry, to use this you need to be using ChartFX build 3488 or later, this gets us very close to our final look.
If you have read other posts in this blog you know we like to refine our designs incrementally, so first we will add a shadow to each bar (this is why we started with a Canvas instead of using the Rectangle as the only visual in the template).
<DataTemplate>
<Canvas>
<Canvas.Resources>
<local:NegateConverter x:Key="negateConverter"/>
</Canvas.Resources>
<Rectangle Width="{Binding Path=Width}" Height="{Binding Path=Height}"
Fill="#404040" Stroke="#404040"
StrokeThickness="{Binding Path=StrokeThickness}">
<Rectangle.BitmapEffect>
<DropShadowBitmapEffect/>
</Rectangle.BitmapEffect>
</Rectangle>
<Rectangle Canvas.Left="{Binding Path=Left,
Converter={StaticResource negateConverter}}"
Canvas.Top="{Binding Path=Top,
Converter={StaticResource negateConverter}}"
Width="{Binding Path=PlotWidth}" Height="{Binding Path=PlotHeight}"
Clip="{Binding Path=BoundsGeometry}">
<Rectangle.Fill>
<ImageBrush Stretch="UniformToFill" ImageSource="C:\Temp\WorldMap.png"/>
</Rectangle.Fill>
</Rectangle>
</Canvas>
</DataTemplate>
Which gives us this
If you asked me I would stop here but a coworker in the ChartFX for WPF team felt that depending on the value of the bars you might get to see too little of your bitmap, e.g. if the last 3 values were smaller than 20 you would not get to see much of the world map and maybe even lose the effect. If that is the case we could use the same bitmap in the plot area probably playing with the Opacity.
<cfx:Chart.PlotArea>
<cfx:PlotAreaAttributes>
<cfx:PlotAreaAttributes.Background>
<ImageBrush Opacity="0.35" Stretch="UniformToFill"
ImageSource="C:\Temp\WorldMap.png" />
</cfx:PlotAreaAttributes.Background>
</cfx:PlotAreaAttributes>
</cfx:Chart.PlotArea>
JuanC