Нужно сделать Гистограмму в wpf, но не простую, внутри каждой колонки гистограммы, должны быть другие маленькие колонки, сумма значений которых, равна родительской. Каким образом это лучше реализовать, какие библиотеки лучше использовать? Так же прикрепляю изображение, с примером, как должна выглядеть одна колонка
2 Answers
Ну как вариант, это рассматривать каждый большой столбец за некий объект, который содержит в себе еще по несколько объектов, тогда в таком случае подойдет ItemsControl, который вложен в другой ItemsControl.
То есть делаем примерно такой стиль:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Text="{Binding Total, StringFormat={}{0}%}" TextAlignment="Center" FontSize="16" FontWeight="Medium"/>
<ItemsControl Grid.Row="1" ItemsSource="{Binding Items}" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<ProgressBar Background="Transparent" Foreground="#8AC976"
BorderThickness="0"
Margin="-.5 0 0 0"
Value="{Binding Total, Mode=OneWay}"
Orientation="Vertical"/>
<ItemsControl ItemsSource="{Binding Values}" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Margin=".5 0 0 0">
<ProgressBar Value="{Binding Path=.}"
Orientation="Vertical"
Width="15"
BorderBrush="Transparent"
Background="Transparent"
Foreground="#67AF7D"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<TextBlock Text="{Binding Total, StringFormat={}{0}%}" TextAlignment="Center" VerticalAlignment="Top"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
Здесь как я уже и говорил, создается ItemsControl, который размещает объекты в один ряд, переопределяем стиль элемента и вставляем туда еще один ItemsControl, который также размещает объекты в ряд, а в его шаблоне указываем вертикальный ProgressBar. Остальное, это "финтифлюшки в виде текста и заднего фона "большого" столбца.
Все остальное уже зависит от ваших данных, какие они, что хотите вывести и так далее. Лично в моем примере идет класс ItemModel, который принимает значения и подсчитывает общее значение:
class ItemModel
{
public List<double> Values { get; set; }
public double Total => Values?.Any() == true ? Values.Sum() : 0;
public ItemModel(params double[] values)
{
Values = new List<double>(values);
}
}
И в основной VM два свойства: коллекция с числами, а также общее число:
class MainViewModel
{
public ObservableCollection<ItemModel> Items { get; set; }
public double Total => Items?.Any() == true ? Items.SelectMany(x=>x.Values).Sum() : 0;
public MainViewModel()
{
Items = new ObservableCollection<ItemModel>
{
new ItemModel(.5,1,2.22),
new ItemModel(15,50.01,7),
new ItemModel(.2,20,6.2),
new ItemModel(3,1,.03),
};
}
Тут опять же, я все сделал на скорую руку, что бы протестировать сам дизайн, вам же стоит все это сделать под себя.
В итоге результат получается примерно следующий:
Имея это уже дальше можно играться. Добавить автоматическое обновление данных, может анимации навесить, стиль подстроить под себя и так далее.
- 15,694
Для различного рода инфографики я бы обратил внимание на https://github.com/Live-Charts/Live-Charts и https://github.com/oxyplot/oxyplot
Пример для Live-Charts:
Возьмем такую же модель
public class ItemModel
{
public List<double> Values { get; set; }
public double Total => Values?.Any() == true ? Values.Sum() : 0;
public ItemModel(params double[] values)
{
Values = new List<double>(values);
}
}
Наша ViewModel
public class HistogramViewModel
{
public HistogramViewModel()
{
Items = new ObservableCollection<ItemModel>
{
new ItemModel(.5,1,2.22),
new ItemModel(15,50.01,7),
new ItemModel(.2,20,6.2),
new ItemModel(3,1,.03),
};
InitializeComponent();
Formatter = value => value.ToString("P");
}
public ObservableCollection<ItemModel> Items { get; set; }
public ChartValues<double> Values1 => new ChartValues<double>
(Items.SelectMany(x => x.Values));
public ChartValues<double> Values2 => new ChartValues<double>
(Items.Select(x => x.Total));
public Func<double, string> Formatter { get; set; }
}
Наша View
<UserControl x:Class="WPF.Ext.Views.HistogramExampleView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:lvc="clr-namespace:LiveCharts.Wpf;assembly=LiveCharts.Wpf">
<Grid Height="400">
<lvc:CartesianChart Width="200">
<lvc:CartesianChart.Series>
<lvc:ColumnSeries Fill="#8AC976"
ColumnPadding="1"
MaxColumnWidth="50"
DataLabels="True"
Values="{Binding Values2}"/>
</lvc:CartesianChart.Series>
<lvc:CartesianChart.AxisX>
<lvc:Axis ShowLabels="False"/>
</lvc:CartesianChart.AxisX>
<lvc:CartesianChart.AxisY>
<lvc:Axis ShowLabels="False" MaxValue="100"/>
</lvc:CartesianChart.AxisY>
</lvc:CartesianChart>
<lvc:CartesianChart Width="200">
<lvc:CartesianChart.Series>
<lvc:ColumnSeries Fill="#67AF7D"
ColumnPadding="1"
MaxColumnWidth="15"
Values="{Binding Values1}"/>
</lvc:CartesianChart.Series>
<lvc:CartesianChart.AxisX>
<lvc:Axis ShowLabels="False"/>
</lvc:CartesianChart.AxisX>
<lvc:CartesianChart.AxisY>
<lvc:Axis ShowLabels="False" MaxValue="100" />
</lvc:CartesianChart.AxisY>
</lvc:CartesianChart>
</Grid>
Минус тут в том, что на одном чарте не расположишь колонки разной ширины, один фиг максимальная ширина колонки равна ширине грида / количество колонок. Приходится лепить два чарта и выравнивать максимальное значение по Y
Еще проблема возникнет, если надо в главных колонках выводить разное количество подчиненных, думаю, тут надо переопределять шаблон колонки для чарта.
- 143


