[ratings] Недавно возникла необходимость построить пару графиков. Это были результаты расчета некой физической модели. И я решил, что было бы удобно построить их прямо в рассчитывающей программе с помощью WPF (.Net 3.0). Хотя задача довольно проста, можно долго копаться в хелпе, ища как сделать ту или иную вещь. Поэтому приведу тут исчерпывающий пример, который ответит на многие вопросы.
При построении графика можно пользоваться разными подходами. Я пользуюсь здесь геометрическими классами, которые позволяют создать векторное изображение. То есть можно будет изменять масштабы картинки с графиком, а качество будет идеальным. Также можно будет качественно распечатать изображение.
Для начала подготовим данные. Пусть это будут просто массивы синусов и косинусов:
Теперь собственно построение.
Вначале создадим группу геометрических изображений DrawingGroup, каждое из которых будет нарисовано своими кистями.
Я разбил процесс построения графика на стадии. Этому соответствует цикл по DrawingStage. На каждой стадии создается объект класса GeometryDrawing, который впоследствии добавляется в DrawingGroup. Внутри GeometryDrawing создается группа геометрических примитивов GeometryGroup. Наконец GeometryGroup может содержать в себе объекты классов LineGeometry, RectangleGeometry, etc.
for (int DrawingStage = 0; DrawingStage < 10; DrawingStage++)
{
GeometryDrawing drw = new GeometryDrawing();
GeometryGroup gg = new GeometryGroup();
//Задный фон
if (DrawingStage == 1)
{
drw.Brush = Brushes.Beige;
drw.Pen = new Pen(Brushes.LightGray, 0.01);
RectangleGeometry myRectGeometry = new RectangleGeometry();
myRectGeometry.Rect = new Rect(0, 0, 1, 1);
gg.Children.Add(myRectGeometry);
}
//тут остальные стадии.....
drw.Geometry = gg;
aDrawingGroup.Children.Add(drw);
}
image1.Source = new DrawingImage(aDrawingGroup);
На первой стадии нарисуем задний фон, который представляет из себя квадрат бежевого цвета с серыми краями. Задаются цвета и стили кистей (Brush, Pen) у класса GeometryDrawing. Обратите внимание на координаты. Фактически их можно задавать любыми. Здесь я использую зону чуть шире чем квадрат 0..1×0..1. На второй стадии нарисуем мелкую сетку на фоне:
if (DrawingStage == 2)
{
drw.Brush = Brushes.Beige;
drw.Pen = new Pen(Brushes.Gray, 0.003);
DoubleCollection dashes = new DoubleCollection();
for (int i = 1; i < 10; i++)
dashes.Add(0.1);
drw.Pen.DashStyle = new DashStyle(dashes, 0);
drw.Pen.EndLineCap = PenLineCap.Round;
drw.Pen.StartLineCap = PenLineCap.Round;
drw.Pen.DashCap = PenLineCap.Round;
for (int i = 1; i < 10; i++)
{
LineGeometry myRectGeometry = new LineGeometry(new Point(1.1, i * 0.1), new Point(-0.1, i * 0.1));
gg.Children.Add(myRectGeometry);
}
}
Чтобы нарисовать линии пунктиром используется свойство Pen.DashStyle.
Свойства Pen.EndLineCap, Pen.StartLineCap, Pen.DashCap отвечают за то, как будут нарисованы концы линий. В данном случае выставлены закругленные концы.
Собственно построение двух кривых (одну – линией, другую – точками):
if (DrawingStage == 3)
{
drw.Brush = Brushes.White;
drw.Pen = new Pen(Brushes.Black, 0.005);
gg = new GeometryGroup();
for (int i = 0; i < Np; i++)
{
LineGeometry l = new LineGeometry(new Point((double)i / (double)Np, 1.0 - (Data1[i] / 2.0)),
new Point((double)(i + 1) / (double)Np, 1.0 - (Data1[i + 1] / 2.0)));
gg.Children.Add(l);
}
}
//график #2 - точки
if (DrawingStage == 4)
{
drw.Brush = Brushes.White;
drw.Pen = new Pen(Brushes.Black, 0.005);
gg = new GeometryGroup();
for (int i = 0; i < Np; i++)
{
EllipseGeometry el = new EllipseGeometry(new Point((double)i / (double)Np, 1.0 - (Data2[i] / 2.0)), 0.01, 0.01);
gg.Children.Add(el);
}
}
Наконец можно нарисовать сверху рамочку и сделать подписи:
if (DrawingStage == 5)
{
drw.Brush = Brushes.Transparent;
drw.Pen = new Pen(Brushes.White, 0.2);
RectangleGeometry myRectGeometry = new RectangleGeometry();
myRectGeometry.Rect = new Rect(-0.1, -0.1, 1.2, 1.2);
gg.Children.Add(myRectGeometry);
}
//Рамка
if (DrawingStage == 6)
{
drw.Brush = Brushes.Transparent;
drw.Pen = new Pen(Brushes.LightGray, 0.01);
RectangleGeometry myRectGeometry = new RectangleGeometry();
myRectGeometry.Rect = new Rect(0, 0, 1, 1);
gg.Children.Add(myRectGeometry);
}
//Надписи
if (DrawingStage == 7)
{
drw.Brush = Brushes.LightGray;
drw.Pen = new Pen(Brushes.Gray, 0.003);
for (int i = 1; i < 10; i++)
{
// Create a formatted text string.
FormattedText formattedText = new FormattedText(
((double)(1-i*0.1)).ToString(),
CultureInfo.GetCultureInfo("en-us"),
FlowDirection.LeftToRight,
new Typeface("Verdana"),
0.05,
Brushes.Black);
// Set the font weight to Bold for the formatted text.
formattedText.SetFontWeight(FontWeights.Bold);
// Build a geometry out of the formatted text.
Geometry geometry = formattedText.BuildGeometry(new Point(-0.1, i * 0.1 - 0.03));
gg.Children.Add(geometry);
}
}
Обратите внимание, что текст также переделывается в геометрию (formattedText.BuildGeometry).
Я сохранил проект с исходным кодом:
Рабочий пример для Visual Studio