ItemsControl Bar Chart Масштабирование баров
РЕДАКТИРОВАТЬ: добавлен код
Кроме того, поскольку DateTimes на самом деле не Datetime (sTrings в формате чч: мм: сс), я решил просто использовать Strings и использовать TimeSpan для получения totalMinutes.
<ObjectDataProvider x:Key="odpLbGrafiek" ObjectType="{x:Type myClasses:GrafiekBar}" MethodName="GetDataGrafiek"/>
<DataTemplate x:Key="GrafiekItemTemplate">
<Border Width="Auto" Height="Auto">
<Grid>
<Rectangle StrokeThickness="0" Height="30"
Margin="15"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Width="{Binding Value}"
Fill="{Binding Fill}">
<Rectangle.LayoutTransform>
<ScaleTransform ScaleX="20" />
</Rectangle.LayoutTransform>
</Rectangle>
</Grid>
</Border>
</DataTemplate>
Заполнение фактически дает размер на панели гистограммы iself.
ItemsControl:
<ItemsControl x:Name="icGrafiek"
Margin="20,3,0,0"
ItemsSource="{Binding Source={StaticResource odpLbGrafiek}}"
ItemTemplate="{DynamicResource GrafiekItemTemplate}"
RenderTransformOrigin="1,0.5" HorizontalAlignment="Left" VerticalAlignment="Top" Grid.RowSpan="6">
<ItemsControl.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleY="-1" ScaleX="1"/>
<SkewTransform AngleY="0" AngleX="0"/>
<RotateTransform Angle="180"/>
<TranslateTransform/>
</TransformGroup>
</ItemsControl.RenderTransform>
</ItemsControl>
Следующий метод вызывается в привязке данных. Там bar.Value дает значение для значения Width в табличке данных, которое дает размер бара.
public ObservableCollection<GrafiekBar> GetDataGrafiek()
{
var converter = new System.Windows.Media.BrushConverter();
Double maxValueStilstanden = GetLargestValueStilstanden();
TimeSpan tsMaxValue = TimeSpan.Parse(maxValueStilstanden.ToString());
totalMinutesMaxValue = tsMaxValue.TotalMinutes;
//calculate % of stilstanden Values
foreach(String t in stilStandenList)
{
TimeSpan ts = TimeSpan.Parse(t);
Double totalMin = ts.TotalMinutes;
totalMin = totalMin / totalMinutesMaxValue * 100;
valuesChartPercentage.Add(totalMin);
}
for (int j = 0; j < valuesChartPercentage.Count; j++)
{
GrafiekBar bar = new GrafiekBar();
bar.Value = valuesChartPercentage[j];
bar.Fill = converter.ConvertFromString(kleuren[j]) as Brush;
listGrafiek.Add(bar);
}
return listGrafiek;
}
Другая проблема на самом деле ширина (размер бара). Я на самом деле должен сделать * 10000, чтобы получить любую визуальную картину самого бара.
Я использую ItemsControl, который стилизован так, чтобы он выглядел как гистограмма.
Так, например, массив с 5 значениями от 1 до 5 создает 5 столбцов с различными размерами столбцов и с разными цветами. Очень похоже на следующий пример http://www.c-sharpcorner.com/uploadfile/mahesh/bar-chart-in-wpf/
Эта проблема:
Моя проблема связана с масштабированием размеров столбцов (в этом случае свойство width, поэтому необходимо использовать значение int/double).
- Моя программа записывает несколько дат в формате ЧЧ: мм: сс
- Бары должны быть масштабированы в эти даты
Например, у меня может быть бар с 01:22:11 или бар с 00:01:11 до максимального значения 6.
Как лучше всего масштабировать эти значения DateTime до определенного двойного значения? Это значение будет использоваться для определения размера бара на графике.
Думаю, я ищу какой-то расчет, который вычисляет все мои значения одинаково, поэтому я не получаю неожиданно большое значение, которое выходит за пределы моего пользовательского интерфейса.
Наиболее чистым решением было бы то, что все столбцы сравниваются друг с другом, и когда один меняется, другой растет / уменьшается, но пока это не требуется, если только это не так сложно, как кажется.
Сам столбчатый символ не должен быть слишком точным, он служит только для того, чтобы получить общую картину ситуации. Точные значения будут записаны в базу данных.
Любые предложения приветствуются!
Спасибо PeterP.
4 ответа
Я бы выбрал базовую дату и сделал бы график вашей коллекции количеством дней / часов / минут между базовой датой и датой данных.
Вы можете даже сделать это с помощью конвертера, в котором базовая дата передается в качестве параметра конвертера.
Ваши два примера даты (01:22:11
а также 00:01:11
) на самом деле время, так что в этом случае я бы просто графическое число минут с 0, так что ваши фактические значения данных на графике будет 82 и 1
В ответ на ваш запрос о масштабировании вы будете отображать все в процентах. В этом случае возьмите наибольшее число и наметьте любое другое число на основе процента от наибольшего числа.
Таким образом, используя ваши два примера, я бы преобразовал их в числа 82 и 1, взял бы большее число (82) как 100%, и возвратил бы список, который содержит процент от каждого числа до 82, так что список возврата будет содержать 100 % и 1,2% (1/82).
Вы все еще можете сделать это либо в ViewModel
или ItemsSource
Converter (конвертер будет принимать весь список в качестве параметра и возвращать весь список для возвращаемого значения)
редактировать
В ответ на ваши комментарии ниже, вот как я бы настроить его, используя Converter
на ItemsSource
, Преобразователь просто берет значение данных и преобразует его в другое значение, которое относится только к пользовательскому интерфейсу дисплея.
Исходный XAML будет выглядеть так:
<ItemsControl ItemsSource="{Binding MyCollection,
Converter="{StaticResource MyTimeConverter}}" />
где MyCollection
является ObservableCollection<DateTime>
, а также MyTimeConverter
делает следующее:
- В ролях прошло
value
какObservableCollection<DateTime>
, поскольку все параметры преобразователя передаются в видеobject
- Перебрать коллекцию и выяснить самое большое время
- Возьмите самое большое время в коллекции и выясните, сколько у нее минут. Это будет ваше 100% -ое значение, на котором вы будете основываться все остальные времена, поэтому сохраняйте количество минут, которое у вас самое большое время, в переменной
- Создать новый
List<decimal>
для возвращаемого значения - Цикл начала сбора. Для каждого времени в коллекции разделите его на "значение 100%", которое вы сохранили на шаге 3, что даст вам процент того, как долго этот столбец должен сравниваться с наибольшим значением. Добавьте этот процент к доходу
List<decimal>
- Вернуть
List<decimal>
кItemsControl
, этоList<decimal>
будет использоваться какItemsSource
вместо фактическогоObservableCollection<DateTime>
Это означает, что ваш ItemsControl
теперь привязан к коллекции десятичных знаков, где одно значение равно 100% и будет занимать всю ширину экрана, все остальные значения масштабируются до максимального значения.
Что касается вашего вопроса об использовании таймера для обновления коллекции, ваш таймер должен обновить ObservableCollection<DateTime>
называется MyCollection
что ItemsControl
связан с. Таймер не должен знать или заботиться о коде преобразователя вообще.
Например, если ваш таймер хочет воссоздать MyCollection
с совершенно новым набором времени, тогда это может, и пользовательский интерфейс автоматически повторно запустит код конвертера и обновит гистограмму, так как ObservableCollections
сообщит пользовательскому интерфейсу, когда коллекция изменилась, и пользовательский интерфейс необходимо обновить.
Что касается "базовой даты", о которой я говорил в моих комментариях ниже, если бы вы отображали график дат вместо времени, вы бы хотели установить базовую дату, чтобы ваш график не растянулся до 1/1/0001
, Вы не хотите использовать минимальную дату, так как это приведет к тому, что наименьшее значение будет отображаться как 0 на гистограмме, поэтому вы передадите Converter
конкретная дата для использования в качестве отправной точки на вашем графике. Если базовая дата была 1/1/12, а ваша самая большая дата была 3/1/12, тогда ваш график растянулся бы с 1/1/12 до 3/1/12.
Базовая дата будет использоваться в шагах 3 и 5 конвертера. Например, вместо того, чтобы получать количество минут во времени, вы можете получить количество дней между базовой датой и датой данных.
Вы также можете рассчитать базовую дату в конвертере, например, за 10 дней до самой низкой даты, хотя это может исказить график больше, чем вы предпочитаете, в зависимости от данных.
Нормализация.
- Конвертировать общее время в секунды.
- (Разделите общую доступную ширину на общее время) = factorA
- чтобы найти правильную ширину каждого бара, умножьте его время на фактор
РЕДАКТИРОВАТЬ: выберите подходящую дату / время, чтобы измерить ноль, или вы можете просто использовать свойство общее количество секунд ваших объектов DateTime
Похоже, вам просто нужно отслеживать максимум всех значений. Затем масштабируйте каждое значение в соответствии с этим максимумом:
var scale = value / maximum;
var height = scale * ActualHeight;
Конечно, способ, которым вы на самом деле устанавливаете высоту каждого элемента, скорее всего, будет через привязку.
Имхо, у вас вся гистограмма должна иметь ViewModel сзади, и эта виртуальная машина имеет логику для вычисления максимального значения, а затем определяет масштабирование.