-1

Let's say we have 25 products A and 14 products B. I want to create chart that is representing them using rectangles and grid. I wrote this code below and it works, but the chart generated with it is very inaccurate. Any ideas how to fix it?

<StackPanel Orientation="Horizontal">
  <!--Products A-->
  <Grid>
     <Grid.RowDefinitions>
         <RowDefinition Height="0.2*"/>
         <RowDefinition Height="percentage1*"/>
     </Grid.RowDefinitions>
     <Rectangle Fill="Red" HorizontalAlignment="Stretch" Grid.Row="1"/>
  </Grid>
  <!--Products B-->
  <Grid>
     <Grid.RowDefinitions>
         <RowDefinition Height="0.2*"/>
         <RowDefinition Height="percentage2*"/>
     </Grid.RowDefinitions>
     <Rectangle Fill="Red" HorizontalAlignment="Stretch" Grid.Row="1"/>
  </Grid>
</StackPanel>

percentage1 = 25 products / (25 products + 14 products)
percentage2 = 14 products / (25 products + 14 products)

bbz
  • 25
  • 4
  • Does [this](https://stackoverflow.com/questions/147908) answer your question ? Demo based on it available [here](https://github.com/Orace/SO/tree/main/SO_72633207). – Orace Jun 15 '22 at 15:01

1 Answers1

0

You should not control the size of the Rectangle with the width of a Grid row. Instead host the Rectangle elements in a ItemsControl and calculate the final rendered width based on the available space:

rectangle_width = ratio * available_width  
                = value / max_value_in_chart * available_width

It's best to move the logic and layout to a custom Control or UserControl. And because the Rectangle bars are hosted in an ItemsControl, you can as many bars as you need to without any hassle (opposed to modifying a Grid to add more rows):

Usage

<local:SimpleBarChart x:Name="BarChart" 
                      BarThickness="64"
                      Orientation="Horizontal"/>
public MainWindow()
{
  InitializeComponent();

  // Better use data binding
  this.BarChart.BarValues = new List<double> { 12, 24, 36 };
}

SimpleBarChart.xaml.cs

public partial class SimpleBarChart : UserControl
{
  public IList<double> BarValues
  {
    get => (IList<double>)GetValue(BarValuesProperty);
    set => SetValue(BarValuesProperty, value);
  }

  public static readonly DependencyProperty BarValuesProperty = DependencyProperty.Register(
    "BarValues",
    typeof(IList<double>),
    typeof(SimpleBarChart),
    new PropertyMetadata(default));

  public double BarThickness
  {
    get => (double)GetValue(BarThicknessProperty);
    set => SetValue(BarThicknessProperty, value);
  }

  public static readonly DependencyProperty BarThicknessProperty = DependencyProperty.Register(
    "BarThickness",
    typeof(double),
    typeof(SimpleBarChart),
    new PropertyMetadata(default));

  public SimpleBarChart()
  {
    InitializeComponent();
  }
}

SimpleBarChart.xaml

<UserControl>
  <UserControl.Resources>
    <local:BarValueToLengthConverter x:Key="BarValueToLengthConverter" />
  </UserControl.Resources>
 
  <ItemsControl ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=BarValues}"
                HorizontalAlignment="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=HorizontalContentAlignment}"
                HorizontalContentAlignment="Stretch"
                VerticalAlignment="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=VerticalalContentAlignment}"
                VerticalContentAlignment="Stretch">
  <ItemsControl.ItemContainerStyle>
    <Style TargetType="ContentPresenter">
      <Setter Property="Margin"
              Value="0,0,0,12" />
    </Style>
  </ItemsControl.ItemContainerStyle>

    <ItemsControl.ItemTemplate>
      <DataTemplate>
        <Rectangle Fill="Orange" 
                   Height="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=BarThickness}"
                   HorizontalAlignment="Left">
          <Rectangle.Width>
            <MultiBinding Converter="{StaticResource BarValueToLengthConverter}">
              <Binding RelativeSource="{RelativeSource AncestorType=UserControl}"
                       Path="BarValues" />
              <Binding />
              <Binding RelativeSource="{RelativeSource AncestorType=UserControl}"
                       Path="BarThickness" />
              <Binding RelativeSource="{RelativeSource AncestorType=ItemsControl}"
                       Path="ActualWidth" />
            </MultiBinding>
          </Rectangle.Width>
        </Rectangle>
      </DataTemplate>
    </ItemsControl.ItemTemplate>
  </ItemsControl>
</UserControl>

BarValueToLengthConverter.cs

public class BarValueToLengthConverter : IMultiValueConverter
{
  public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
  {
    IList<double>? barValues = values.OfType<IList<double>>().FirstOrDefault();
    IList<double>? doubleValues = values.OfType<double>().ToList();
    double barValue = doubleValues[0];
    double barThickness = doubleValues[1];
    double barHostWidth = doubleValues[2];

    return barValue / barValues.Max() * barHostWidth;
  }

  public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) 
    => throw new NotSupportedException();
}
BionicCode
  • 1
  • 4
  • 28
  • 44
  • I'm using a grid because I want to automatically change size of bars when I resize the window. – bbz Jun 16 '22 at 16:06
  • The above example control resizes to fit the available space. The bars will resize accordingly. – BionicCode Jun 16 '22 at 16:55