Nugi
Nugi

Reputation: 872

How to fill area between 2 data series with color using WPF Toolkit Data Visualization Controls?

So far I've successfully created a data series using WPF Toolkit Data Visualization Controls, like this:

Custom area chart

As you can see, there is 3 data series (top, middle, bottom). I'm using AreaSeries for each and apply this style:

<Style x:Key="TopAreaSeriesStyle" TargetType="{x:Type chart:AreaSeries}">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type chart:AreaSeries}">
                        <Canvas x:Name="PlotArea">
                            <Path StrokeThickness="1.5" Stroke="Black" Fill="Yellow" Opacity=".4">
                                <Path.Style>
                                    <Style TargetType="{x:Type Path}">
                                        <Setter Property="Data">
                                            <Setter.Value>
                                                <MultiBinding Converter="{StaticResource geoExclusionConverter}">
                                                    <Binding ElementName="TopAreaSeries" Path="Geometry"/>
                                                    <Binding ElementName="MiddleAreaSeries" Path="Geometry"/>
                                                </MultiBinding>
                                            </Setter.Value>
                                        </Setter>
                                    </Style>
                                </Path.Style>
                            </Path>
                        </Canvas>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
        <Style x:Key="MiddleAreaSeriesStyle" TargetType="{x:Type chart:AreaSeries}"...>
        <Style x:Key="BottomAreaSeriesStyle" TargetType="{x:Type chart:AreaSeries}"...>

I have to create this style so that colored area is rendered only between area series. For each area series, I must apply this style, so there is 3 styles that differ in where area of clipping occurs. For example, Area of the top AreaSeries that collided with area of the middle AreaSeries is clipped and so does the middle AreaSeries to the bottom AreaSeries.

The clipping process is handled by a converter class which consumed 2 parameters defined in MultiBinding tag. First parameter takes the geometry that will be clipped, and second parameter take the geometry that is used to clip.

public class GeometryExclusionConverter : IMultiValueConverter
  {
    public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
      if (values[0] == null && values[1] == null)
        return new PathGeometry();
      else
      {
        var geo1 = values[0] as Geometry;
        var geo2 = values[1] as Geometry;
        return new CombinedGeometry(GeometryCombineMode.Exclude, geo1, geo2);
      }
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
    {
      throw new NotImplementedException();
    }
  }

By using this method, I can get all I need. But the problem is, there can be more than 7 data series, so that I must create a lot of styles that differs only in parameters that get passed. And it makes a lot of dependency (style and converter). It would be nice if all this stuff can be handled in one place.

I've searched on how to create a style with parameters so I can somehow apply a style by passing parameters on it, but none of them seemed working.

UPDATE

Using advice proposed by @AnjumSKhan, I am able to simplify the styling, by putting those code on OnInitialized event.

public class ClippedAreaSeries : AreaSeries
{    
    public string ClippedArea
    {
      get { return (string)GetValue(ClippedAreaProperty); }
      set { SetValue(ClippedAreaProperty, value); }
    }

    public static readonly DependencyProperty ClippedAreaProperty =
    DependencyProperty.Register("ClippedArea", typeof(string), typeof(ClippedAreaSeries), new PropertyMetadata(null));

    protected override void OnInitialized(EventArgs e)
    {
      base.OnInitialized(e);

      string templateXml =
            @"
            <ControlTemplate xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'
                        xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'                             
                        >
                    <Canvas x:Name='PlotArea'>
                        <Path StrokeThickness='1.5' Stroke='Black' Fill='Yellow' Opacity='.25'>
                            <Path.Style>
                                <Style TargetType='{x:Type Path}'>
                                    <Setter Property='Data'>
                                        <Setter.Value>
                                            <MultiBinding Converter='{StaticResource geoExclusionConverter}'>
                                                <Binding ElementName='{0}' Path='Geometry'/>
                                                <Binding ElementName='{1}' Path='Geometry'/>
                                            </MultiBinding>
                                        </Setter.Value>
                                    </Setter>
                                </Style>
                            </Path.Style>
                        </Path>
                    </Canvas>
                </ControlTemplate>
                ";

     templateXml = templateXml.Replace("{0}", this.Name).Replace("{1}", this.ClippedArea);
     //so on...
  }
}

Notice that, it still depends on a converter class that must be defined in Application.Resources. It would be nice if this control doesn't depend on them, so there is no need to define the converter as resources. Any ideas are appreciated.

Upvotes: 1

Views: 628

Answers (2)

AnjumSKhan
AnjumSKhan

Reputation: 9827

  1. You can easily set the ControlTemplate using code by using System.Windows.Markup.XamlReader.Load(System.IO.Stream stream) method.

Below code shows how to change the Control template of Button :

string templateXml =
         @"<ControlTemplate xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'
                            xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
            <Canvas Background='Purple'>
                <TextBlock Text='custom btn'/>
            </Canvas>
            </ControlTemplate>";

            ControlTemplate template;
            using (var stringReader = new System.IO.StringReader(templateXml))
            using (XmlReader xmlReader = XmlReader.Create(stringReader))
            {
                template = (ControlTemplate)XamlReader.Load(xmlReader);
            }

            Btn1.Template = template;

You can use this approach for your scenario, and replace necessary values in the string.

  1. But now if we try to apply converters, the above approach creates problems. So, we now go for pure-code approach, and construct all elements ourselves. This approach involves using FrameworkElementFactory class. For example, if we want to change ControlTemplate of Button and apply a converter to Text property of TextBlock like below :

    <ControlTemplate TargetType="Button">
            <Canvas Background='Red'>
                <TextBlock>
                    <TextBlock.Text>
                        <Binding Path='Content' RelativeSource="{RelativeSource Mode=TemplatedParent}" >
                            <Binding.Converter>
                                <converter:ContentConverter/>
                            </Binding.Converter>
                        </Binding>
                    </TextBlock.Text>
                </TextBlock>
            </Canvas>
        </ControlTemplate>
    

We will write the corresponding code as follows :

FrameworkElementFactory canvas = new FrameworkElementFactory(typeof(Canvas));
canvas.SetValue(Canvas.BackgroundProperty, new SolidColorBrush(Colors.Red));

FrameworkElementFactory tb = new FrameworkElementFactory(typeof(TextBlock));
Binding binding = new Binding("Content");
binding.RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent);
binding.Converter = new Converters.ContentConverter();
tb.SetBinding(TextBlock.TextProperty, binding);

canvas.AppendChild(tb);

ControlTemplate ct = new ControlTemplate(typeof(Button));
ct.VisualTree = canvas;

Btn1.Template = ct;

Based on above approach, the control-template style which you have posted would be written as :

FrameworkElementFactory canvas = new FrameworkElementFactory(typeof(Canvas));
            canvas.Name = "PlotArea";

            FrameworkElementFactory path = new FrameworkElementFactory(typeof(Path));
            {                
                path.SetValue(Path.StrokeThicknessProperty, 1.5);
                path.SetValue(Path.StrokeProperty, new SolidColorBrush(Colors.Green));
                path.SetValue(Path.OpacityProperty, 0.25);

                MultiBinding binding = new MultiBinding();
                // create your converter properly below
                binding.Converter = new Converters.GeoConverter();

                Binding bindingItem1 = new Binding();
                bindingItem1.ElementName = "AreaPlus1SD";
                bindingItem1.Path = new PropertyPath("Geometry");

                Binding bindingItem2 = new Binding();
                bindingItem2.ElementName = "AreaMedian";
                bindingItem2.Path = new PropertyPath("Geometry");

                binding.Bindings.Add(bindingItem1);
                binding.Bindings.Add(bindingItem2);

                path.SetBinding(Path.DataProperty, binding);
            }

            canvas.AppendChild(path);

            ControlTemplate ct = new ControlTemplate(typeof(ChartingToolkit.AreaSeries));
            ct.VisualTree = canvas;

           AreaSeries1.Template = ct;

Upvotes: 1

galakt
galakt

Reputation: 1439

You can do it like this:

    var customStyle = new Style(typeof (Button));
    customStyle.Setters.Add(new Setter{Property = Button.BackgroundProperty, Value = new SolidColorBrush(){Color = Colors.Red}});
    ButtonTest.Style = customStyle;

Upvotes: 0

Related Questions