C# и chart для финансовых рынков

1,00
р.
Хочу реализовать отображение графиков используя wpf.
Приблизительно как на картинке. Интересует только View(разметка xaml).
Массив на вход: list. Price содержит: open price, close price.

Как это можно отобразить во View? Не используя сторонние библиотеки?
P. S. Смотрел на stackoverflow — данная тема поднималась уже много раз и много лет. Но нигде не было ни одного рабочего примера. Только ответы в духе: «это сложно», «за вас никто не сделает».
Поэтому критерием закрытия темы объявляю реальный рабочий пример! Давайте добьем эту задачу! Кто то ленится, а кто-то просто не может сделать — отнеситесь с пониманием.

Ответ
Не понимаю, что развели за разборки: сложно, невозможно, много кода... Фыр. Это же WPF, тут всё просто.
Для начала сгенерируем случайные данные:
using System using System.Collections.Generic using System.Linq using static System.Math
namespace CandlestickChartApp { public partial class MainWindow { public MainWindow() { DataContext = new Candlestick() InitializeComponent() } }
public class Candlestick { private const int PriceCount = 500 private const int PricesPerCandle = 10
public List Prices { get } = new List(PriceCount + 1) public List Candles { get } = new List(PriceCount / PricesPerCandle) public List Labels { get } = new List() public double PriceCurrent { get } public double PriceMin { get } public double PriceMax { get } public double PriceHeight { get }
public Candlestick() { var rnd = new Random(1) var today = DateTime.Today var date = DateTime.Today var value = 300 for (var i = 0 i < Prices.Capacity i++) Prices.Add(new Price { Date = date = date.AddMinutes(5), Value = value += rnd.Next(-9, 10) }) for (var i = 0 i < Candles.Capacity i++) { var prices = Prices.Select(p => p.Value).Skip(i * PricesPerCandle).Take(PricesPerCandle + 1) Candles.Add(new Candle { Date = (Prices[i * PricesPerCandle].Date - today).TotalMinutes / 5, Min = prices.Min(), Max = prices.Max(), Height = prices.Max() - prices.Min(), DeltaMin = prices.First(), DeltaMax = prices.Last(), DeltaHeight = Abs(prices.Last() - prices.First()), IsPositive = prices.First() < prices.Last(), }) } Candles.ForEach(c => c.Fix()) PriceCurrent = Prices.Last().Value PriceMin = Prices.Min(p => p.Value) - 20 PriceMax = Prices.Max(p => p.Value) + 20 PriceHeight = PriceMax - PriceMin - 40 for (double price = Round(PriceMin / 10) * 10 price < PriceMax price += 50) Labels.Add(price) } }
public class Price { public DateTime Date { get set } public double Value { get set } }
public class Candle { public double Date { get set } public double Min { get set } public double Max { get set } public double Height { get set } public double DeltaMin { get set } public double DeltaMax { get set } public double DeltaHeight { get set } public bool IsPositive { get set }
public void Fix() { if (!IsPositive) { var min = DeltaMin DeltaMin = DeltaMax DeltaMax = min } } } }
А потом остаётся только описать несколько шаблонов и стилей:

И пожалуйста:

Даты преобразуются к double, чтобы использовать как координату X. Цены генерируются в виде double и используются как Y (в доменной области они должны быть decimal).
Списки свечек и надписей служат источником элементов для ItemsControl. В качестве панели для размещения элементов используется Canvas вместо стандартной VirtualizingStackPanel, что позволяет размещать элементы не стопкой, а по координатам. Координаты прикрепляются через стилизацию ContentPresenter, которые оборачивают каждый элемент.
Возникает загвоздка с координатой Y, так как в WPF она считается от левого верхнего угла, а в графике — от левого нижнего. Это решается при помощи преобразования вертикального отражения относительно центра: сначала отражается всё содержимое контейнера, потом текстовые надписи отражаются ещё раз, чтобы их можно было прочитать.
Маргины вроде 0 8 0 -8 — это сдвиги элементов относительно исходной позиции без изменения "внешнего" размера (в данном случае сдвиг вниз на 8 пунктов). Этот приём используется для центрирования надписей по вертикали и сдвига самого графика.
Так как WPF не умеет рисовать прямоугольники с отрицательной высотой, то надо удостовериться, что "минимальное" значение DeltaMin меньше "максимального" DeltaMax.
Запросы LINQ в коде не очень оптимальные, совершается много избыточных проходов по коллекции. Это сделано исключительно для простоты кода, в реальном приложении придётся написать менее кратко.
Также к имени класса Candle можно дописать суффикс ViewModel, так как этот класс — не часть домена, а используется исключительно как удобный источник данных для XAML. По вкусу можно сделать свойства свечки более доменными, но тогда понадобится писать конвертеры IValueConverter для преобразования DateTime и decimal к double. Это уже вопрос вкуса.

Всего-то 80 строк XAML.
Разумеется, это пример, и многое зашито в коде. Доведение до ума, в том числе дорисовывание вертикальных линий сетки — домашнее задание.
Реальный контрол должен учитывать, что диапазоны значений бывают разные по масштабу, давать возможность перематывать и масштабировать график и т.п. Но вот отобразить свечки — ну вообще никаких проблем. Развели разборки на пустом месте.
Человеку хочется потратить 500 репы на непонятно зачем нужную задачку — его воля. Может, он изучает WPF и хочет красивый простой пример. Может, у него неадекватный заказчик, который запрещает пользоваться библиотеками. Разные бывают ситуации. Если человек готов пожертвовать потом и кровью добытую репу, то, наверное, очень надо.