Your Privacy Matters: We use our own and third-party cookies to improve your experience on our website. By continuing to use the website we understand that you accept their use. Cookie Policy
360
Axis Labels
posted

Since the DV stuff doesn't support axis labels, I've decided to "roll my own".  Given the nature of rotated elements in Silverlight, I'm planning on using a "layered" approach so that layout isn't adversly affected.  The bottom layer will contain the axis label elements, and the top layer will be the chart element.

Has anyone else come up with any better approach that can share code or comment as to why this isn't a good way to approach it?

 

  • 30692
    Suggested Answer
    Offline posted

    Hi,

    I assume you mean axis titles, which, yes, the XamWebChart doesn't currently natively support. But you can change the XamWebChart's control template to include regions for these values. But, yes, the tricky part is that you really want to do a layout transform (not available in Silverlight out of the box) on the Y Axis title, not a render transform.

    Fortunately, its possible to simulate a layout transform in Silverlight by overriding measure and arrange and calculating the space required if the element were to be rotated (which is really easy for 90, 180 and 270 degrees).

    The Silverlight toolkit provides a panel (LayoutTranformer) that will simulate layout transforms for you, which you may want to investigate. But I've also created a stand-alone sample here that should help you get started. Let me know if it helps!

    The markup, which includes a new style to be applied to the chart:

    <UserControl.Resources>
            <Style x:Key="AxisTitlesChart" TargetType="igChart:XamWebChart" >
                <Style.Setters>
                    <Setter Property="Template">
                        <Setter.Value>
                            <ControlTemplate TargetType="igChart:XamWebChart">
                                <Border BorderBrush="{TemplateBinding BorderBrush}" 
                                        BorderThickness="{TemplateBinding BorderThickness}">
                                    <Border.Resources>
                                        <local:VisibilityConverter x:Key="visibilityConverter" />
                                    </Border.Resources>
                                    
                                    <Grid x:Name="RootElement" 
                                          Background="{TemplateBinding Background}" 
                                          Margin="{TemplateBinding Padding}"
                                          >
                                        <Grid.ColumnDefinitions>
                                            <ColumnDefinition Width="Auto" />
                                            <ColumnDefinition Width="*"/>
                                            <ColumnDefinition Width="Auto" MaxWidth="200"/>
                                        </Grid.ColumnDefinitions>
                                        <Grid.RowDefinitions>
                                            <RowDefinition Height="Auto" />
                                            <RowDefinition Height="*"/>
                                            <RowDefinition Height="Auto" />
                                        </Grid.RowDefinitions>
    
                                        <Grid x:Name="CaptionPanel" Grid.Row="0" Grid.ColumnSpan="3" Grid.Column="0"/>
    
                                        <Grid x:Name="YAxisTitle" Grid.Row="1" 
                                              Grid.Column="0"
                                              Margin="0,0,20,0"
                                              Visibility="{Binding Path=(local:AxisTitleSettings.YAxisTitle), RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource visibilityConverter}}">
                                            <!-- We want a layout transform on this label, not a render transform. -->
                                            <local:RotatedContentStackPanel VerticalAlignment="Center">
                                                <Border Background="Gray" CornerRadius="5"
                                                    VerticalAlignment="Center" Padding="2" >
                                                    <TextBlock Foreground="AntiqueWhite"
                                                               Text="{Binding Path=(local:AxisTitleSettings.YAxisTitle), RelativeSource={RelativeSource TemplatedParent}}"/>
                                                </Border>
                                            </local:RotatedContentStackPanel>
                                        </Grid>
    
                                        <Grid x:Name="ScenePanel" Grid.Column="1" Grid.Row="1" />
                                        <Grid x:Name="LegendPanel" Grid.Column="2" Grid.Row="1" 
                                              Grid.RowSpan="2" MaxWidth="200"/>
    
                                        <Grid x:Name="XAxisTitle" 
                                              Grid.Row="3" Grid.Column="1"
                                              Margin="0,20,0,0"
                                              Visibility="{Binding Path=(local:AxisTitleSettings.XAxisTitle), RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource visibilityConverter}}">
                                            <Border Background="Gray" HorizontalAlignment="Center" 
                                                    CornerRadius="5" Padding="2">
                                                <TextBlock Foreground="AntiqueWhite" 
                                                           Text="{Binding Path=(local:AxisTitleSettings.XAxisTitle), RelativeSource={RelativeSource TemplatedParent}}"/>
                                            </Border>
                                        </Grid>
                                    </Grid>
                                </Border>
                            </ControlTemplate>
                        </Setter.Value>
                    </Setter>
                </Style.Setters>
            </Style>
        </UserControl.Resources>
    
        <Grid x:Name="LayoutRoot">
            <igChart:XamWebChart x:Name="theChart"
                                 Style="{StaticResource AxisTitlesChart}"
                                 local:AxisTitleSettings.XAxisTitle="X Axis"
                                 local:AxisTitleSettings.YAxisTitle="Y Axis">
                <igChart:XamWebChart.Series>
                    <igChart:Series ChartType="Column" >
                        <igChart:Series.DataPoints>
                            <igChart:DataPoint Label="B" Value="29191991"  />
                            <igChart:DataPoint Label="C" Value="19219191"  />
                            <igChart:DataPoint Label="D" Value="19199221"  />
                            <igChart:DataPoint Label="E" Value="33881111"  />
                        </igChart:Series.DataPoints>
                    </igChart:Series>
                </igChart:XamWebChart.Series>
            </igChart:XamWebChart>
        </Grid>
    

    Note it uses attached properties to allow you to specify the axis title values to be used.
    In versions of silverlight earlier than 4 you may need to follow these instructions to get
    around a bug/limitation with binding to attached properties from a template.

    The supporting classes:

    public enum AllowedRotations
        {
            Ninety,
            TwoSeventy
        }
    
        public class RotatedContentStackPanel : Panel
        {
            public static readonly DependencyProperty RotationProperty =
                DependencyProperty.Register(
                "Rotation",
                typeof(AllowedRotations),
                typeof(RotatedContentStackPanel),
                new PropertyMetadata(AllowedRotations.Ninety,
                (o, e) =>
                {
                    (o as RotatedContentStackPanel).OnRotationChanged();
                }));
    
            private void OnRotationChanged()
            {
                InvalidateMeasure();
            }
    
            public AllowedRotations Rotation
            {
                get
                {
                    return (AllowedRotations)GetValue(RotationProperty);
                }
                set
                {
                    SetValue(RotationProperty, value);
                }
            }
    
            protected override Size MeasureOverride(Size availableSize)
            {
                Size desired = new Size(0, 0);
                foreach (var child in Children)
                {
                    child.Measure(
                        new Size(
                            Double.PositiveInfinity,
                            availableSize.Width));
                    desired.Height += child.DesiredSize.Width;
                    desired.Width =
                        Math.Max(
                        desired.Width,
                        child.DesiredSize.Height);
                }
                return desired;
            }
    
            protected void RotateAndShift(UIElement ele, 
                double xOffset, double yOffset)
            {
                TransformGroup tg = new TransformGroup();
                double rotationValue = 90;
    
                double xTranslation = xOffset;
                double yTranslation = 0;
               
                if (Rotation == AllowedRotations.TwoSeventy)
                {
                    rotationValue = 270;
                    xTranslation = 0;
                    yTranslation = yOffset;
                }
    
                tg.Children.Add(
                    new RotateTransform()
                    {
                        Angle = rotationValue
                    });
                tg.Children.Add(
                    new TranslateTransform()
                    {
                        X = xTranslation,
                        Y = yTranslation
                    });
                ele.RenderTransform = tg;
            }
    
            protected override Size ArrangeOverride(Size finalSize)
            {
                double currentY = 0;
                foreach (var child in Children)
                {
                    child.Arrange(
                        new Rect(
                            0,
                            currentY,
                            child.DesiredSize.Width,
                            finalSize.Width));
                    currentY += child.DesiredSize.Width;
    
                    RotateAndShift(
                        child,
                        finalSize.Width,
                        child.DesiredSize.Width);
                }
                return finalSize;
            }
        }
    
        public class VisibilityConverter
            : IValueConverter
        {
            public object Convert(
                object value, Type targetType, 
                object parameter, CultureInfo culture)
            {
                if (String.IsNullOrEmpty(value as string))
                {
                    return Visibility.Collapsed;
                }
                return Visibility.Visible;
            }
    
            public object ConvertBack(
                object value, Type targetType, 
                object parameter, CultureInfo culture)
            {
                throw new NotImplementedException();
            }
        }
    
        public class AxisTitleSettings
        {
            public static readonly DependencyProperty XAxisTitleProperty =
                DependencyProperty.RegisterAttached("XAxisTitle",
                                                    typeof(string),
                                                    typeof(AxisTitleSettings),
                                                    new PropertyMetadata(null));
    
            public static readonly DependencyProperty YAxisTitleProperty =
                DependencyProperty.RegisterAttached("YAxisTitle",
                                                    typeof(string),
                                                    typeof(AxisTitleSettings),
                                                    new PropertyMetadata(null));
    
            public static string GetXAxisTitle(DependencyObject target)
            {
                return (string)target.GetValue(XAxisTitleProperty);
            }
            public static void SetXAxisTitle(DependencyObject target, string title)
            {
                target.SetValue(XAxisTitleProperty, title);
            }
            public static string GetYAxisTitle(DependencyObject target)
            {
                return (string)target.GetValue(YAxisTitleProperty);
            }
            public static void SetYAxisTitle(DependencyObject target, string title)
            {
                target.SetValue(YAxisTitleProperty, title);
            }
    
        }