目录
DateTimeAxisBug 时间轴
DescriptionChangeOnTheFly 动态更改线的粗细
HandledRepro 图中图
LineGraphUpdateOnDataSourceChange
SyncedWidthSample 同步两个图的x轴宽度
TwoAxisSample 两条x轴
VerticalRangeNotVisibleRepro 添加随机垂直range
HideAxisSample 隐藏轴
AxisColoringSample 轴更改样式
Animation 动态图
LineTestSample
<Button DockPanel.Dock="Top" Content="Add data source" Name="addDataSourceBtn" Click="addDataSourceBtn_Click"/>
<d3:ChartPlotter Name="plotter" Margin="0,5,0,0">
<d3:LineGraph Name="lineGraph" Stroke="OrangeRed" StrokeThickness="1"/>
<d3:VerticalLine Value="{Binding ElementName=dataFollowChart, Path=MarkerPosition.X}" StrokeThickness="2" Stroke="Violet" StrokeDashArray="4,5"/>
<d3:HorizontalLine Value="{Binding ElementName=dataFollowChart, Path=MarkerPosition.Y}" StrokeThickness="2" Stroke="Violet" StrokeDashArray="4,5"/>
<d3:ViewportPanel>
<Rectangle Width="35" Height="35" Fill="Yellow" Stroke="Orange"
d3:ViewportPanel.X="{Binding ElementName=dataFollowChart, Path=MarkerPosition.X}"
d3:ViewportPanel.Y="{Binding ElementName=dataFollowChart, Path=MarkerPosition.Y}"/>
</d3:ViewportPanel>
<d3:DataFollowChart Name="dataFollowChart" PointSource="{Binding ElementName=lineGraph}">
<DataTemplate>
<Grid d3:ViewportPanel.ScreenOffsetY="16">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Rectangle RadiusX="3" RadiusY="3" Stroke="LightGray" Fill="#99FFFFFF" Grid.Row="0" Grid.RowSpan="2"/>
<Ellipse Width="10" Height="10" Fill="LightGreen" Stroke="Green" Grid.Row="0"/>
<TextBlock Name="tb" Margin="2,15,2,0" Grid.Row="1">
<TextBlock Text="{Binding Position.X, StringFormat=G3}"/>
<TextBlock Text="{Binding Position.Y, StringFormat=G3}"/>
</TextBlock>
</Grid>
</DataTemplate>
</d3:DataFollowChart>
</d3:ChartPlotter>
DispatcherTimer timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(20) };
public Window1()
{
InitializeComponent();
plotter.Viewport.Domain = new Rect(-1, -1.2, 20, 2.4);
plotter.Children.Add(new HorizontalScrollBar());
plotter.AxisGrid.DrawHorizontalMinorTicks = false;
plotter.AxisGrid.DrawVerticalMinorTicks = false;
lineGraph.DataSource = CreateSineDataSource(1.0);
timer.Tick += new EventHandler(timer_Tick);
}
TimeSpan elapsed = new TimeSpan();
void timer_Tick(object sender, EventArgs e)
{
elapsed = elapsed.Add(timer.Interval);
double millis = elapsed.TotalMilliseconds;
double x = ((millis % 1000) - 500) / 500;
var visible = plotter.Viewport.Visible;
visible.XMin = x;
plotter.Visible = visible;
if (millis > 10000)
Close();
}
Random rnd = new Random();
private void addDataSourceBtn_Click(object sender, RoutedEventArgs e)
{
var line = plotter.AddLineGraph(CreateSineDataSource(rnd.NextDouble()));
DataFollowChart followChart = new DataFollowChart(line);
plotter.Children.Add(followChart);
}
private IPointDataSource CreateSineDataSource(double phase)
{
const int N = 100;
Point[] pts = new Point[N];
for (int i = 0; i < N; i++)
{
double x = i / (N / 10.0) + phase;
pts[i] = new Point(x, Math.Sin(x - phase));
}
var ds = new EnumerableDataSource<Point>(pts);
ds.SetXYMapping(pt => pt);
return ds;
}
private IPointDataSource CreateLineDataSource(double phase)
{
const int N = 100;
Point[] pts = new Point[N];
for (int i = 0; i < N; i++)
{
double x = i / (N / 10.0);
pts[i] = new Point(x, x * phase + phase);
}
var ds = new EnumerableDataSource<Point>(pts);
ds.SetXYMapping(pt => pt);
return ds;
}
IsolineSampleApp
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
Loaded += new RoutedEventHandler(Window1_Loaded);
}
private void Window1_Loaded(object sender, RoutedEventArgs e)
{
LoadField();
}
private static string[] ParseDataString(string str)
{
str = str.TrimEnd(' ');
return str.Split(' ');
}
private static string[] ParseGridString(string str)
{
return str.TrimEnd(' ').
Substring(0, str.Length - 3).
TrimStart('{').
Split(new string[] { " } {" }, StringSplitOptions.None);
}
private void LoadField()
{
var assembly = Assembly.GetExecutingAssembly();
List<string> strings = new List<string>();
using (Stream stream = assembly.GetManifestResourceStream("IsolineSampleApp.SampleData.txt"))
{
using (StreamReader reader = new StreamReader(stream))
{
while (!reader.EndOfStream)
{
string str = reader.ReadLine();
if (str == "Data:")
{
// do nothing
}
else if (str == "Grid:")
{
// do nothing too
}
else
{
strings.Add(str);
}
}
}
}
// data
string[] nums = ParseDataString(strings[0]);
int width = nums.Length;
int height = strings.Count / 2;
CultureInfo culture = new CultureInfo("ru-RU");
double[,] data = new double[width, height];
for (int row = 0; row < height; row++)
{
nums = ParseDataString(strings[row]);
for (int column = 0; column < width; column++)
{
double d = Double.Parse(nums[column], culture);
data[column, row] = d;
}
}
Point[,] gridData = new Point[width, height];
for (int row = 0; row < height; row++)
{
string str = strings[row + height];
nums = ParseGridString(str);
for (int column = 0; column < width; column++)
{
string[] vecStrs = nums[column].Split(new string[] { "; " }, StringSplitOptions.None);
gridData[column, row] = new Point(
Double.Parse(vecStrs[0], culture),
Double.Parse(vecStrs[1], culture));
}
}
WarpedDataSource2D<double> dataSource = new WarpedDataSource2D<double>(data, gridData);
isolineGraph.DataSource = dataSource;
trackingGraph.DataSource = dataSource;
DataRect visible = dataSource.GetGridBounds();
plotter.Viewport.Visible = visible;
}
}
<d3:ChartPlotter Name="plotter">
<d3:FastIsolineDisplay Name="isolineGraph" DrawLabels="True" WayBeforeTextMultiplier="20"/>
<d3:IsolineTrackingGraph Name="trackingGraph"/>
<d3:CursorCoordinateGraph/>
</d3:ChartPlotter>
Simulation
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
// Three observable data sources. Observable data source contains
// inside ObservableCollection. Modification of collection instantly modify
// visual representation of graph.
ObservableDataSource<Point> source1 = null;
ObservableDataSource<Point> source2 = null;
ObservableDataSource<Point> source3 = null;
public Window1()
{
InitializeComponent();
}
private void Simulation()
{
CultureInfo culture = CultureInfo.InvariantCulture;
Assembly executingAssembly = Assembly.GetExecutingAssembly();
// load spim-generated data from embedded resource file
const string spimDataName = "Simulation.Repressilator.txt";
using (Stream spimStream = executingAssembly.GetManifestResourceStream(spimDataName))
{
using (StreamReader r = new StreamReader(spimStream))
{
string line = r.ReadLine();
while (!r.EndOfStream)
{
line = r.ReadLine();
string[] values = line.Split(',');
double x = Double.Parse(values[0], culture);
double y1 = Double.Parse(values[1], culture);
double y2 = Double.Parse(values[2], culture);
double y3 = Double.Parse(values[3], culture);
Point p1 = new Point(x, y1);
Point p2 = new Point(x, y2);
Point p3 = new Point(x, y3);
source1.AppendAsync(Dispatcher, p1);
source2.AppendAsync(Dispatcher, p2);
source3.AppendAsync(Dispatcher, p3);
Thread.Sleep(10); // Long-long time for computations...
}
}
}
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
// Create first source
source1 = new ObservableDataSource<Point>();
// Set identity mapping of point in collection to point on plot
source1.SetXYMapping(p => p);
// Create second source
source2 = new ObservableDataSource<Point>();
// Set identity mapping of point in collection to point on plot
source2.SetXYMapping(p => p);
// Create third source
source3 = new ObservableDataSource<Point>();
// Set identity mapping of point in collection to point on plot
source3.SetXYMapping(p => p);
// Add all three graphs. Colors are not specified and chosen random
plotter.AddLineGraph(source1, 2, "Data row 1");
plotter.AddLineGraph(source2, 2, "Data row 2");
plotter.AddLineGraph(source3, 2, "Data row 3");
// Start computation process in second thread
Thread simThread = new Thread(new ThreadStart(Simulation));
simThread.IsBackground = true;
simThread.Start();
}
}
<Grid>
<d3:ChartPlotter Name="plotter"/>
</Grid>
PlotterLayoutPanels
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
private void ChartPlotter_Loaded(object sender, RoutedEventArgs e)
{
plotter.LeftPanel.Background = Brushes.DodgerBlue.MakeTransparent(0.4);
plotter.RightPanel.Background = Brushes.Salmon.MakeTransparent(0.4);
plotter.BottomPanel.Background = Brushes.PaleGreen;
plotter.TopPanel.Background = Brushes.Gold;
plotter.HeaderPanel.Background = Brushes.GreenYellow;
plotter.FooterPanel.Background = Brushes.DarkOrchid.MakeTransparent(0.4);
//NumericAxis horizAxis = plotter.MainHorizontalAxis as NumericAxis;
//horizAxis.AxisControl.IsStaticAxis = true;
//NumericAxis vertAxis = plotter.MainVerticalAxis as NumericAxis;
//vertAxis.AxisControl.IsStaticAxis = true;
}
}
<Grid>
<d3:ChartPlotter Name="plotter" Loaded="ChartPlotter_Loaded">
<d3:ChartPlotter.Template>
<ControlTemplate TargetType="{x:Type d3:Plotter}">
<Grid Name="PART_ContentsGrid" Background="{TemplateBinding Background}" DataContext="{TemplateBinding DataContext}">
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<StackPanel Name="PART_HeaderPanel" Orientation="Vertical" Grid.Row="0"/>
<Grid Name="PART_MainGrid" Grid.Row="1">
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<StackPanel Name="PART_LeftPanel" Grid.Column="0" Grid.Row="1" Orientation="Horizontal" Background="Transparent"/>
<StackPanel Name="PART_RightPanel" Grid.Column="2" Grid.Row="1" Orientation="Horizontal" Background="Transparent"/>
<StackPanel Name="PART_BottomPanel" Grid.Column="1" Grid.Row="2" Orientation="Vertical" Background="Transparent"/>
<StackPanel Name="PART_TopPanel" Grid.Column="1" Grid.Row="0" Orientation="Vertical" Background="Transparent"/>
<!-- Border of viewport -->
<Rectangle Name="Border" Grid.Column="1" Grid.Row="1" Stroke="{TemplateBinding BorderBrush}"
StrokeThickness="{TemplateBinding BorderThickness}"/>
<Grid Name="PART_CentralGrid" Grid.Column="1" Grid.Row="1" Background="Transparent">
<Rectangle Stroke="DarkGray" StrokeThickness="3" RadiusX="4" RadiusY="4" Panel.ZIndex="1" Fill="#70FFFFA0">
<Rectangle.RenderTransform>
<TranslateTransform X="10" Y="10"/>
</Rectangle.RenderTransform>
</Rectangle>
<Rectangle Stroke="Black" StrokeThickness="3" RadiusX="4" RadiusY="4" Panel.ZIndex="1" Fill="#50FFA0FF">
<Rectangle.RenderTransform>
<TranslateTransform X="30" Y="30"/>
</Rectangle.RenderTransform>
</Rectangle>
<Canvas Panel.ZIndex="2">
<TextBlock Text="CentralGrid" FontSize="12" Canvas.Top="12" Canvas.Left="14"/>
<TextBlock Text="MainCanvas" FontSize="12" Canvas.Top="32" Canvas.Left="34"/>
</Canvas>
</Grid>
<Canvas Name="PART_MainCanvas" Grid.Column="1" Grid.Row="1" ClipToBounds="True"/>
</Grid>
<Canvas Name="PART_ParallelCanvas" Grid.Column="1" Grid.Row="1"/>
<StackPanel Name="PART_FooterPanel" Orientation="Vertical" Grid.Row="2"/>
<Rectangle Stroke="DarkGray" StrokeThickness="3" Grid.RowSpan="3"/>
</Grid>
</ControlTemplate>
</d3:ChartPlotter.Template>
<d3:Header>HeaderPanel</d3:Header>
<d3:HorizontalAxis Placement="Top"/>
<d3:VerticalAxis Placement="Right"/>
<d3:HorizontalAxisTitle Placement="Bottom">BottomPanel</d3:HorizontalAxisTitle>
<d3:HorizontalAxisTitle Placement="Top">TopPanel</d3:HorizontalAxisTitle>
<d3:VerticalAxisTitle Placement="Left">LeftPanel</d3:VerticalAxisTitle>
<d3:VerticalAxisTitle Placement="Right">RightPanel</d3:VerticalAxisTitle>
<d3:Footer>FooterPanel</d3:Footer>
</d3:ChartPlotter>
</Grid>
5x1000Charts
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
private double[] xs;
private double[] y1;
private double[] y2;
private double[] y3;
private double[] y4;
private double[] y5;
private const int count = 1000;
bool useFilters = true;
private readonly Random rnd = new Random();
private void AddChartsBtn_Click(object sender, RoutedEventArgs e)
{
using (new DisposableTimer("adding charts"))
{
xs = Enumerable.Range(0, count).Select(i => (double)i).ToArray();
y1 = Enumerable.Range(0, count).Select(i => 1 + rnd.NextDouble()).ToArray();
y2 = Enumerable.Range(0, count).Select(i => 2 + rnd.NextDouble()).ToArray();
y3 = Enumerable.Range(0, count).Select(i => 3 + rnd.NextDouble()).ToArray();
y4 = Enumerable.Range(0, count).Select(i => 4 + rnd.NextDouble()).ToArray();
y5 = Enumerable.Range(0, count).Select(i => 5 + rnd.NextDouble()).ToArray();
var xds = xs.AsXDataSource();
var ds1 = xds.Join(y1.AsYDataSource());
var ds2 = xds.Join(y2.AsYDataSource());
var ds3 = xds.Join(y3.AsYDataSource());
var ds4 = xds.Join(y4.AsYDataSource());
var ds5 = xds.Join(y5.AsYDataSource());
plotter.Visible = new DataRect(-100, 0, 1200, 7);
if (!useFilters)
{
plotter.Children.Add(new LineGraph { DataSource = ds1, Stroke = ColorHelper.RandomBrush });
plotter.Children.Add(new LineGraph { DataSource = ds2, Stroke = ColorHelper.RandomBrush });
plotter.Children.Add(new LineGraph { DataSource = ds3, Stroke = ColorHelper.RandomBrush });
plotter.Children.Add(new LineGraph { DataSource = ds4, Stroke = ColorHelper.RandomBrush });
plotter.Children.Add(new LineGraph { DataSource = ds5, Stroke = ColorHelper.RandomBrush });
}
else
{
plotter.AddLineGraph(ds1);
plotter.AddLineGraph(ds2);
plotter.AddLineGraph(ds3);
plotter.AddLineGraph(ds4);
plotter.AddLineGraph(ds5);
}
}
}
}
<Grid>
<d3:ChartPlotter Name="plotter">
<Button Content="Add charts" Click="AddChartsBtn_Click"/>
</d3:ChartPlotter>
</Grid>
ArcSegmentPlotting
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
AddLineGraph(new Point(0, 0), new Point(0, 50));
AddCircularArcGraph(new Point(0, 0), new Point(50, -50), new Size(50, 50));
AddLineGraph(new Point(50, 50), new Point(0, 50));
}
private void AddLineGraph(Point startPoint, Point endPoint)
{
var ds = new EnumerableDataSource<Point>(new List<Point> {
new Point { X = startPoint.X, Y = startPoint.Y },
new Point { X = endPoint.X, Y = endPoint.Y }
});
ds.SetXYMapping(pt => pt);
plotter.AddLineGraph(ds);
}
private void AddCircularArcGraph(Point startPoint, Point endPoint, Size size)
{
PathFigure pf = new PathFigure();
pf.StartPoint = new Point(startPoint.X, startPoint.Y);
ArcSegment arcSegment = new ArcSegment();
arcSegment.Point = new Point(endPoint.X, endPoint.Y);
arcSegment.Size = size;
arcSegment.SweepDirection = SweepDirection.Counterclockwise;
PathSegmentCollection psc = new PathSegmentCollection();
psc.Add(arcSegment);
pf.Segments = psc;
PathFigureCollection pfc = new PathFigureCollection();
pfc.Add(pf);
PathGeometry pg = new PathGeometry();
pg.Figures = pfc;
var path = new Path();
path.Stroke = Brushes.Black;
path.StrokeThickness = 1;
path.Data = pg;
path.Fill = Brushes.Orange;
path.Stretch = Stretch.Fill;
var viewportPanel = new ViewportHostPanel();
ViewportPanel.SetViewportBounds(path, new DataRect(0, 0, 50, 50));
viewportPanel.Children.Add(path);
plotter.Children.Add(viewportPanel);
}
}
<Grid>
<d3:ChartPlotter Name="plotter"></d3:ChartPlotter>
</Grid>
DateTimeAxisBug
<Grid>
<d3:ChartPlotter Name="plotter">
<d3:ChartPlotter.MainHorizontalAxis>
<d3:HorizontalDateTimeAxis/>
</d3:ChartPlotter.MainHorizontalAxis>
<d3:ChartPlotter.MainVerticalAxis>
<d3:VerticalDateTimeAxis/>
</d3:ChartPlotter.MainVerticalAxis>
</d3:ChartPlotter>
</Grid>
DescriptionChangeOnTheFly
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
Loaded += new RoutedEventHandler(Window1_Loaded);
}
LineGraph chart;
void Window1_Loaded(object sender, RoutedEventArgs e)
{
double[] xs = new double[] { 1, 2, 3 };
double[] ys = new double[] { 0.1, 0.4, 0.2 };
var ds = xs.AsXDataSource().Join(ys.AsYDataSource());
chart = plotter.AddLineGraph(ds, "Chart1");
}
private void changeDescrBtn_Click(object sender, RoutedEventArgs e)
{
chart.Stroke = Brushes.Red;
chart.StrokeThickness = 3;
chart.Description = new PenDescription("Chart2");
}
}
<Grid>
<d3:ChartPlotter Name="plotter">
<Button Canvas.Bottom="10" Canvas.Right="10" Content="Change description" Name="changeDescrBtn" Click="changeDescrBtn_Click"/>
</d3:ChartPlotter>
</Grid>
HandledRepro
<Grid>
<d3:ChartPlotter Name="plotter">
<d3:VerticalLine Value="0.1"/>
<d3:ViewportHostPanel>
<Rectangle Width="100" Height="100" Fill="DarkOliveGreen" d3:ViewportPanel.X="0.2" d3:ViewportPanel.Y="0.2"/>
</d3:ViewportHostPanel>
<d3:ViewportHostPanel>
<Border d3:ViewportPanel.X="0.6" d3:ViewportPanel.Y="0.6" BorderThickness="1" BorderBrush="Black">
<d3:ChartPlotter Width="400" Height="400">
<d3:VerticalLine Value="0.5"/>
</d3:ChartPlotter>
</Border>
</d3:ViewportHostPanel>
</d3:ChartPlotter>
</Grid>
LineGraphUpdateOnDataSourceChange
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
var xs = Enumerable.Range(0, 100).Select(i => i * 0.1);
var xDS = xs.AsXDataSource();
var yDS1 = xs.Select(x => Math.Sin(x)).AsYDataSource();
var yDS2 = xs.Select(x => Math.Cos(x) + 2).AsYDataSource();
var ds1 = xDS.Join(yDS1);
var ds2 = xDS.Join(yDS2);
var list = new List<IPointDataSource>();
list.Add(ds1);
list.Add(ds2);
DataContext = list;
}
}
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<ListBox Name="listBox" Grid.Column="0" ItemsSource="{Binding}"/>
<ContentControl DataContext="{Binding SelectedItem, ElementName=listBox}" Grid.Column="1">
<ContentControl.Template>
<ControlTemplate>
<Grid>
<d3:ChartPlotter>
<d3:LineGraph Stroke="Red" StrokeThickness="2" DataSource="{Binding}"/>
</d3:ChartPlotter>
</Grid>
</ControlTemplate>
</ContentControl.Template>
</ContentControl>
</Grid>
SyncedWidthSample
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
private void StackPanel_Loaded(object sender, RoutedEventArgs e)
{
plotter2.LeftPanel.SetBinding(Panel.WidthProperty, new Binding("Width") { Source = plotter1.LeftPanel });
}
}
<StackPanel Loaded="StackPanel_Loaded">
<d3:ChartPlotter Visible="-25000,-14000,50000,28000" Name="plotter1"/>
<d3:ChartPlotter Visible="-25000,-2,50000,4" Name="plotter2">
<d3:WidthSpring SourcePanel="{Binding LeftPanel, ElementName=plotter1}"/>
</d3:ChartPlotter>
</StackPanel>
TwoAxisSample
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
VerticalAxis axis = new VerticalAxis();
axis.SetConversion(0, 100, 100, 0);
plotter.Children.Add(axis);
// this is only an example of visible rectange. Use here rect you actually need.
plotter.Viewport.Visible = new Rect(0, 0, 1, 100);
}
}
<Grid>
<d3:ChartPlotter Name="plotter">
</d3:ChartPlotter>
</Grid>
VerticalRangeNotVisibleRepro
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
private readonly Random rnd = new Random();
private void AddRandomRangeBtn_Click(object sender, RoutedEventArgs e)
{
double min = rnd.NextDouble();
double max = min + rnd.NextDouble();
VerticalRange range = new VerticalRange { Value1 = min, Value2 = max, Fill = Brushes.Green };
plotter.Children.Add(range);
}
}
<Grid>
<d3:ChartPlotter Name="plotter">
<Button Content="Add random range" Name="AddRandomRangeBtn" Click="AddRandomRangeBtn_Click"/>
</d3:ChartPlotter>
</Grid>
ViewportPanelBugRepro
<Grid>
<d3:ChartPlotter>
<d3:ViewportHostPanel>
<d3:ViewportPanel d3:ViewportPanel.ViewportBounds="0,0,1,1" Background="#90A0B0C0">
<Rectangle Fill="Green" Stroke="Black" StrokeThickness="4" Stretch="Fill"
d3:ViewportPanel.ViewportVerticalAlignment="Bottom"
d3:ViewportPanel.X="0.5"
d3:ViewportPanel.Y="0.5"
d3:ViewportPanel.ViewportHorizontalAlignment="Center"
d3:ViewportPanel.ViewportHeight="0.5"
d3:ViewportPanel.ViewportWidth="1">
</Rectangle>
<Path Name="crown" Stretch="Fill" Fill="Red" Stroke="Black" StrokeThickness="4"
d3:ViewportPanel.ViewportVerticalAlignment="Bottom"
d3:ViewportPanel.X="0.5"
d3:ViewportPanel.Y="0.5"
d3:ViewportPanel.ViewportHorizontalAlignment="Center"
d3:ViewportPanel.ViewportHeight="0.5"
d3:ViewportPanel.ViewportWidth="1">
<Path.Data>
<PathGeometry>
<PathFigure StartPoint="0,1" IsClosed="True" IsFilled="True">
<LineSegment Point="0.5,0"/>
<LineSegment Point="1,1"/>
<LineSegment Point="0.5,0.5"/>
<LineSegment Point="0,1"/>
</PathFigure>
</PathGeometry>
</Path.Data>
</Path>
</d3:ViewportPanel>
</d3:ViewportHostPanel>
</d3:ChartPlotter>
</Grid>
ZeroDifferenceRepro
#define old
using System;
using System.Linq;
using System.Windows;
using System.Windows.Media;
using Microsoft.Research.DynamicDataDisplay;
using Microsoft.Research.DynamicDataDisplay.Common.Auxiliary;
#if old
using Microsoft.Research.DynamicDataDisplay.DataSources;
using Microsoft.Research.DynamicDataDisplay.Charts;
#else
#if !old
using Microsoft.Research.DynamicDataDisplay.Charts.NewLine;
#endif
using Microsoft.Research.DynamicDataDisplay.Charts;
using Microsoft.Research.DynamicDataDisplay.Charts.Axes.Numeric;
#if !old
using Microsoft.Research.DynamicDataDisplay.Charts.NewLine.Functional;
using Microsoft.Research.DynamicDataDisplay.Charts.NewLine.Filters;
#endif
#endif
namespace ZeroDifferenceRepro
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
Loaded += new RoutedEventHandler(Window1_Loaded);
}
void Window1_Loaded(object sender, RoutedEventArgs e)
{
plotter.Children.Add(new HorizontalLine { Value = 0, Stroke = Brushes.DarkGreen.MakeTransparent(0.2) });
plotter.Children.Add(new VerticalLine { Value = 0, Stroke = Brushes.DarkGreen.MakeTransparent(0.2) });
#if old
var xs = Enumerable.Range(0, 500).Select(i => (i - 250) * 0.02);
var sineYDS = xs.Select(x => Math.Sin(x)).AsYDataSource();
var atanYDS = xs.Select(x => Math.Atan(x)).AsYDataSource();
var sineDS = new CompositeDataSource(xs.AsXDataSource(), sineYDS);
var atanDS = new CompositeDataSource(xs.AsXDataSource(), atanYDS);
var sineChart = plotter.AddLineGraph(sineDS);
var atanChart = plotter.AddLineGraph(atanDS);
//sineChart.Filters.Clear();
//atanChart.Filters.Clear();
#else
var xs = Enumerable.Range(0, 500).Select(i => (i - 250) * 0.02);
var sineDS = xs.Select(x => new Point(x, Math.Sin(x))).AsDataSource();
var atanDS = xs.Select(x => new Point(x, Math.Atan(x))).AsDataSource();
var sincDS = Enumerable.Range(-5000, 10001).Select(i =>
{
double x = Math.PI * i / 1000;
double y;
if (i == 0)
y = 100;
else
y = Math.Sin(x * 100);
return new Point(x, y);
}).AsDataSource();
LineChart sincChart = new LineChart { Stroke = ColorHelper.RandomBrush, DataSource = sincDS };
//plotter.Children.Add(sincChart);
LineChart sineChart = new LineChart { Stroke = ColorHelper.RandomBrush, DataSource = sineDS };
plotter.Children.Add(sineChart);
LineChart atanChart = new LineChart { Stroke = ColorHelper.RandomBrush, DataSource = atanDS };
plotter.Children.Add(atanChart);
#endif
}
}
}
<Grid>
<d3:ChartPlotter Name="plotter">
</d3:ChartPlotter>
</Grid>
PointsOnMapSampleApp
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
dataTypeCombobox.ItemsSource = new List<DataType>
{
DataType.Temp, DataType.Rainfall, DataType.SoilDepth
};
Loaded += Window1_Loaded;
}
void Window1_Loaded(object sender, RoutedEventArgs e)
{
LoadData();
dataTypeCombobox.SelectedIndex = 0;
bool res = dataTypeCombobox.Focus();
}
private EnumerableDataSource<DataPoint> CreateDataSource(IEnumerable<DataPoint> data)
{
EnumerableDataSource<DataPoint> ds = new EnumerableDataSource<DataPoint>(data);
MercatorTransform transform = new MercatorTransform();
ds.SetXMapping(p => p.X);
ds.SetYMapping(p => transform.DataToViewport(new Point(0, p.Y)).Y);
ds.AddMapping(CirclePointMarker.FillProperty, dp =>
{
double alpha = (dp.Data - currentRange.Min) / (currentRange.Max - currentRange.Min);
Debug.Assert(0 <= alpha && alpha <= 1);
const double hueWidth = 100;
double hue = hueWidth * (alpha - 0.5) + hueSlider.Value;
if (hue > 360) hue -= 360;
else if (hue < 0) hue += 360;
Debug.Assert(0 <= hue && hue <= 360);
Color mainColor = new HsbColor(hue, 1, 0 + 1 * alpha, 0.3 + 0.7 * alpha).ToArgbColor();
const int colorCount = 5;
GradientStopCollection colors = new GradientStopCollection(colorCount);
double step = 1.0 / (colorCount - 1);
for (int i = 0; i < colorCount; i++)
{
Color color = mainColor;
double x = attSlider.Value * step * i;
color.A = (byte)(255 * Math.Exp(-x * x));
colors.Add(new GradientStop(color, step * i));
}
return new RadialGradientBrush(colors);
});
return ds;
}
private List<SampleDataPoint> loadedData = new List<SampleDataPoint>();
private void LoadData()
{
string[] strings = File.ReadAllLines("example_for_visualization.csv");
// skipping 1st line, parsing all other lines
for (int i = 1; i < strings.Length; i++)
{
SampleDataPoint point = ParseDataPoint(strings[i]);
loadedData.Add(point);
}
tempRange.Min = loadedData.Min(p => p.Temp);
tempRange.Max = loadedData.Max(p => p.Temp);
rainfallRange.Min = loadedData.Min(p => p.RainFall);
rainfallRange.Max = loadedData.Max(p => p.RainFall);
soildepthRange.Min = loadedData.Min(p => p.SoilDepth);
soildepthRange.Max = loadedData.Max(p => p.SoilDepth);
}
MinMax tempRange = new MinMax();
MinMax rainfallRange = new MinMax();
MinMax soildepthRange = new MinMax();
MinMax currentRange;
private SampleDataPoint ParseDataPoint(string str)
{
var pieces = str.Split(',');
SampleDataPoint res = new SampleDataPoint();
res.Lat = Double.Parse(pieces[0], CultureInfo.InvariantCulture);
res.Lon = Double.Parse(pieces[1], CultureInfo.InvariantCulture);
res.Temp = Double.Parse(pieces[2], CultureInfo.InvariantCulture);
res.RainFall = Double.Parse(pieces[3], CultureInfo.InvariantCulture);
res.SoilDepth = Double.Parse(pieces[4], CultureInfo.InvariantCulture);
return res;
}
private IEnumerable<DataPoint> GetSampleData(DataType dataType)
{
switch (dataType)
{
case DataType.Temp:
currentRange = tempRange;
return loadedData.Select(dp => new DataPoint { X = dp.Lon, Y = dp.Lat, Data = dp.Temp }).ToList();
case DataType.Rainfall:
currentRange = rainfallRange;
return loadedData.Select(dp => new DataPoint { X = dp.Lon, Y = dp.Lat, Data = dp.RainFall }).ToList();
case DataType.SoilDepth:
currentRange = soildepthRange;
return loadedData.Select(dp => new DataPoint { X = dp.Lon, Y = dp.Lat, Data = dp.SoilDepth }).ToList();
default:
throw new InvalidOperationException();
}
}
private void Slider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
if (plotter == null)
return;
var graph = plotter.Children.OfType<MarkerPointsGraph>().FirstOrDefault();
if (graph != null)
graph.InvalidateVisual();
}
private void dataTypeCombobox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (graph == null || plotter == null)
return;
graph.DataSource = CreateDataSource(GetSampleData((DataType)dataTypeCombobox.SelectedValue));
}
private void Hyperlink_Click(object sender, RoutedEventArgs e)
{
Hyperlink link = (Hyperlink)sender;
Process.Start(link.NavigateUri.ToString());
}
}
class MinMax
{
public double Min { get; set; }
public double Max { get; set; }
}
class SampleDataPoint
{
public double Lat { get; set; }
public double Lon { get; set; }
public double Temp { get; set; }
public double RainFall { get; set; }
public double SoilDepth { get; set; }
}
class DataPoint
{
public double X { get; set; }
public double Y { get; set; }
public double Data { get; set; }
}
enum DataType
{
Temp,
Rainfall,
SoilDepth
}
<Grid>
<Grid.Resources>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Margin" Value="0,0,10,0"/>
</Style>
<Style TargetType="{x:Type Slider}">
<Setter Property="TickPlacement" Value="BottomRight"/>
<Setter Property="AutoToolTipPlacement" Value="BottomRight"/>
</Style>
<Style TargetType="{x:Type DockPanel}">
<Setter Property="Margin" Value="3,5,3,5"/>
</Style>
</Grid.Resources>
<d3:ChartPlotter Name="plotter">
<d3:MarkerPointsGraph Name="graph" IsHitTestVisible="False">
<d3:MarkerPointsGraph.Marker>
<d3:CirclePointMarker Size="20"/>
</d3:MarkerPointsGraph.Marker>
</d3:MarkerPointsGraph>
<d3:CursorCoordinateGraph/>
<Grid Width="300" Canvas.Bottom="10" Canvas.Right="10">
<Rectangle Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="0" Grid.RowSpan="3" RadiusX="5" RadiusY="5" Fill="#A9FFFFFF" Stroke="LightGray"/>
<Grid Margin="5">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" HorizontalAlignment="Right" VerticalAlignment="Center" Margin="2">Data Type:</TextBlock>
<ComboBox Grid.Row="0" Grid.Column="1" Name="dataTypeCombobox" SelectionChanged="dataTypeCombobox_SelectionChanged" Margin="1"/>
<TextBlock Grid.Row="1" Grid.Column="0" HorizontalAlignment="Right" VerticalAlignment="Center" Margin="2">Text Hue:</TextBlock>
<Slider Minimum="0" Maximum="360" Grid.Row="1" Grid.Column="1" Value="180"
ValueChanged="Slider_ValueChanged" Name="hueSlider"/>
<TextBlock Grid.Row="2" Grid.Column="0" HorizontalAlignment="Right" VerticalAlignment="Center" Margin="2">Attenuation:</TextBlock>
<Slider Minimum="0" Maximum="4" Value="2.3" Grid.Row="2" Grid.Column="1"
ValueChanged="Slider_ValueChanged" Name="attSlider"/>
</Grid>
</Grid>
<TextBlock Canvas.Bottom="10" Canvas.Left="10">
Data from <Hyperlink FontFamily="Consolas" FontSize="14" NavigateUri="http://www.nass.usda.gov" Click="Hyperlink_Click">http://www.nass.usda.gov</Hyperlink>
</TextBlock>
</d3:ChartPlotter>
</Grid>
HideAxisSample
<Grid>
<Grid.Resources>
<Style TargetType="{x:Type d3:Segment}">
<Setter Property="Stroke" Value="Orange"/>
<Style.Triggers>
<EventTrigger RoutedEvent="Shape.Loaded">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
From="1"
To="10"
Duration="0:0:3"
AutoReverse="True"
RepeatBehavior="Forever"
Storyboard.TargetProperty="StrokeThickness"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Style.Triggers>
</Style>
</Grid.Resources>
<d3:ChartPlotter Name="plotter" MainHorizontalAxis="{x:Null}" MainVerticalAxis="{x:Null}">
<d3:Segment StartPoint="{Binding ElementName=point0, Path=Position}"
EndPoint="{Binding ElementName=point1, Path=Position}"
Style="{x:Null}"/>
<d3:Segment StartPoint="{Binding ElementName=point0, Path=Position}"
EndPoint="{Binding ElementName=point2, Path=Position}"
Style="{x:Null}"/>
<d3:Segment StartPoint="{Binding ElementName=point0, Path=Position}"
EndPoint="{Binding ElementName=point3, Path=Position}"
Style="{x:Null}"/>
<d3:Segment StartPoint="{Binding ElementName=point0, Path=Position}"
EndPoint="{Binding ElementName=point4, Path=Position}"
Style="{x:Null}"/>
<d3:DraggablePoint Position="0.4, 0.75" Name="point0"/>
<d3:DraggablePoint Position="0.8, 0.8" Name="point1"/>
<d3:DraggablePoint Position="0.7, 0.6" Name="point2"/>
<d3:DraggablePoint Position="0.7, 0.9" Name="point3"/>
<d3:DraggablePoint Position="0.6, 0.55" Name="point4"/>
<d3:HorizontalLine Value="{Binding ElementName=point0, Path=Position.Y}" Stroke="Aquamarine"/>
<!-- H -->
<d3:Segment StartPoint="0.2, 0.2" EndPoint="0.2, 0.4"/>
<d3:Segment StartPoint="0.2, 0.3" EndPoint="0.25, 0.3"/>
<d3:Segment StartPoint="0.25, 0.2" EndPoint="0.25, 0.4"/>
<!-- E -->
<d3:Segment StartPoint="0.3, 0.3" EndPoint="0.35, 0.4"/>
<d3:Segment StartPoint="0.3, 0.3" EndPoint="0.35, 0.2"/>
<d3:Segment StartPoint="0.3, 0.3" EndPoint="0.4, 0.3"/>
<d3:Segment StartPoint="0.35, 0.4" EndPoint="0.4, 0.3"/>
<!-- L -->
<d3:Segment StartPoint="0.45, 0.2" EndPoint="0.45, 0.4"/>
<d3:Segment StartPoint="0.45, 0.2" EndPoint="0.5, 0.2"/>
<!-- L -->
<d3:Segment StartPoint="0.55, 0.2" EndPoint="0.55, 0.4"/>
<d3:Segment StartPoint="0.55, 0.2" EndPoint="0.6, 0.2"/>
<!-- O -->
<d3:Segment StartPoint="0.65, 0.3" EndPoint="0.7, 0.2"/>
<d3:Segment StartPoint="0.65, 0.3" EndPoint="0.7, 0.4"/>
<d3:Segment StartPoint="0.7, 0.2" EndPoint="0.75, 0.3"/>
<d3:Segment StartPoint="0.7, 0.4" EndPoint="0.75, 0.3"/>
<d3:ViewportPolyline Points="0.1,0.1 0.1,0.2 0.2,0.2 0.2,0.1"/>
<d3:ViewportPolygon Points="0.3,0.6 0.3,0.7 0.35,0.6 0.2,0.8 0.4,0.65, 0.4,0.7">
<d3:ViewportPolygon.Fill>
<VisualBrush Viewport="0.3,0.3,0.4,0.4">
<VisualBrush.Visual>
<MediaElement Source="C:\Users\Public\Videos\Sample Videos\Bear.wmv"/>
</VisualBrush.Visual>
</VisualBrush>
</d3:ViewportPolygon.Fill>
</d3:ViewportPolygon>
<d3:CursorCoordinateGraph/>
</d3:ChartPlotter>
</Grid>
AxisColoringSample
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
// setting custom colors of background, axis text labels and axis ticks:
{
// background brush is for axis's background
plotter.MainHorizontalAxis.Background = Brushes.Aqua.MakeTransparent(0.1);
// foreground bruhs is for axis's labels foreground
plotter.MainHorizontalAxis.Foreground = Brushes.DarkMagenta;
plotter.MainVerticalAxis.Background = new LinearGradientBrush(Colors.White, Colors.LightBlue, 90);
plotter.MainVerticalAxis.Foreground = Brushes.DarkGoldenrod;
// stroke brush is
// ------ /*to rule them all*/ ------
// for ticks' fill
((NumericAxis)plotter.MainHorizontalAxis).AxisControl.TicksPath.Stroke = Brushes.OrangeRed;
}
// this will make the most left axis to display ticks as percents
secondAxis.LabelProvider = new ToStringLabelProvider();
secondAxis.LabelProvider.LabelStringFormat = "{0}%";
secondAxis.LabelProvider.SetCustomFormatter(info => (info.Tick * 100).ToString());
// percent values that can be divided by 50 without a remainder will be red and with bigger font size
secondAxis.LabelProvider.SetCustomView((info, ui) =>
{
if (((int)Math.Round(info.Tick * 100)) % 50 == 0)
{
TextBlock text = (TextBlock)ui;
text.Foreground = Brushes.Red;
text.FontSize = 20;
}
});
// you can add new axes not only from XAML, but from C#, too:
HorizontalDateTimeAxis thirdAxis = new HorizontalDateTimeAxis();
thirdAxis.LabelProvider.SetCustomFormatter(info =>
{
DifferenceIn dateTimeDifference = (DifferenceIn)info.Info;
if (dateTimeDifference == DifferenceIn.Minute)
{
return info.Tick.ToString("%m:ss");
}
// null should be returned if you want to use default label text
return null;
});
// let's have major labels for hours in Spanish,
// for other time periods your Thread.CurrentThread.CurrentCulture will be used to format date.
// You can change this default thread culture and get desired look of labels.
CultureInfo culture = new CultureInfo("es-ES");
thirdAxis.MajorLabelProvider.SetCustomFormatter(info =>
{
MajorLabelsInfo mInfo = (MajorLabelsInfo)info.Info;
if ((DifferenceIn)mInfo.Info == DifferenceIn.Hour)
{
return info.Tick.ToString("MMMM/dd %m:ss", culture);
}
return null;
});
plotter.Children.Add(thirdAxis);
}
}
<Grid>
<d3:ChartPlotter Name="plotter">
<d3:NumericAxis Placement="Left" Name="secondAxis"/>
</d3:ChartPlotter>
</Grid>
Animation
public partial class AnimatedSampleWindow : Window
{
double phase = 0;
readonly double[] animatedX = new double[1000];
readonly double[] animatedY = new double[1000];
EnumerableDataSource<double> animatedDataSource = null;
/// <summary>Programmatically created header</summary>
Header chartHeader = new Header();
/// <summary>Text contents of header</summary>
TextBlock headerContents = new TextBlock();
/// <summary>Timer to animate data</summary>
readonly DispatcherTimer timer = new DispatcherTimer();
public AnimatedSampleWindow()
{
InitializeComponent();
headerContents.FontSize = 24;
headerContents.Text = "Phase = 0.00";
headerContents.HorizontalAlignment = HorizontalAlignment.Center;
chartHeader.Content = headerContents;
plotter.Children.Add(chartHeader);
}
private void AnimatedPlot_Timer(object sender, EventArgs e)
{
phase += 0.01;
if (phase > 2 * Math.PI)
phase -= 2 * Math.PI;
for (int i = 0; i < animatedX.Length; i++)
animatedY[i] = Math.Sin(animatedX[i] + phase);
// Here it is - signal that data is updated
animatedDataSource.RaiseDataChanged();
headerContents.Text = String.Format(CultureInfo.InvariantCulture, "Phase = {0:N2}", phase);
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
for (int i = 0; i < animatedX.Length; i++)
{
animatedX[i] = 2 * Math.PI * i / animatedX.Length;
animatedY[i] = Math.Sin(animatedX[i]);
}
EnumerableDataSource<double> xSrc = new EnumerableDataSource<double>(animatedX);
xSrc.SetXMapping(x => x);
animatedDataSource = new EnumerableDataSource<double>(animatedY);
animatedDataSource.SetYMapping(y => y);
// Adding graph to plotter
var line = plotter.AddLineGraph(new CompositeDataSource(xSrc, animatedDataSource),
new Pen(Brushes.Magenta, 3),
new PenDescription("Sin(x + phase)"));
timer.Interval = TimeSpan.FromMilliseconds(10);
timer.Tick += AnimatedPlot_Timer;
timer.Start();
}
}
<Grid>
<d3:ChartPlotter Name="plotter" Background="Transparent"/>
</Grid>
ImageHistogram
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
private void OnOpenImageClick(object sender, RoutedEventArgs e)
{
OpenFileDialog dlg = new OpenFileDialog();
dlg.Filter = "Image|*.bmp;*.jpg;*.png;*.gif";
if (dlg.ShowDialog(this).GetValueOrDefault(false))
{
OpenImage(dlg.FileName);
}
}
private void OpenImage(string fileName)
{
BitmapImage bmp = new BitmapImage(new Uri(fileName));
bmp.CacheOption = BitmapCacheOption.OnLoad;
image.Source = bmp;
ProcessImage(bmp);
}
EnumerableDataSource<int> red;
EnumerableDataSource<int> green;
EnumerableDataSource<int> blue;
int[] reds = new int[256];
int[] greens = new int[256];
int[] blues = new int[256];
byte[] pixels;
private void ProcessImage(BitmapImage bmp)
{
byte[] pixels = new byte[bmp.PixelWidth * bmp.PixelHeight * 4];
bmp.CopyPixels(pixels, bmp.PixelWidth * 4, 0);
for (int i = 0; i < pixels.Length; )
{
//BGRA
blues[pixels[i++]]++;
greens[pixels[i++]]++;
reds[pixels[i++]]++;
i++;
}
CreateHistograms();
}
private void CreateHistograms()
{
EnumerableDataSource<int> x = new EnumerableDataSource<int>(Enumerable.Range(0, 256).ToArray());
x.SetXMapping(_x => _x);
Func<int, double> mapping;
if (check.IsChecked.GetValueOrDefault())
mapping = logMapping;
else
mapping = linearMapping;
red = new EnumerableDataSource<int>(reds);
red.SetYMapping(mapping);
green = new EnumerableDataSource<int>(greens);
green.SetYMapping(mapping);
blue = new EnumerableDataSource<int>(blues);
blue.SetYMapping(mapping);
CompositeDataSource rDS = new CompositeDataSource(x, red);
CompositeDataSource gDS = new CompositeDataSource(x, green);
CompositeDataSource bDS = new CompositeDataSource(x, blue);
plotter.RemoveUserElements();
plotter.AddLineGraph(rDS, Colors.Red, 1, "Red").FilteringEnabled = false;
plotter.AddLineGraph(gDS, Colors.Green, 1, "Green").FilteringEnabled = false;
plotter.AddLineGraph(bDS, Colors.Blue, 1, "Blue").FilteringEnabled = false;
}
private Func<int, double> logMapping = i => i > 0 ? Math.Log10(i) : 0;
private Func<int, double> linearMapping = i => i;
private void CheckBox_Checked(object sender, RoutedEventArgs e)
{
Func<int, double> mapping;
if (check.IsChecked.GetValueOrDefault())
mapping = logMapping;
else
mapping = linearMapping;
red.SetYMapping(mapping);
green.SetYMapping(mapping);
blue.SetYMapping(mapping);
}
}
<DockPanel>
<Menu DockPanel.Dock="Top">
<MenuItem Header="Open image..." Click="OnOpenImageClick"/>
<Separator/>
<CheckBox Name="check" Content="Use log mapping" Checked="CheckBox_Checked"
Unchecked="CheckBox_Checked" IsChecked="False"/>
</Menu>
<Grid DockPanel.Dock="Bottom">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="3*"/>
</Grid.ColumnDefinitions>
<Image Name="image" Grid.Column="0"/>
<c:ChartPlotter Name="plotter" Grid.Column="1">
<c:Header Content="Histogram of image" FontSize="20" FontFamily="Georgia" c:Plotter.IsDefaultElement="True"/>
<c:HorizontalAxisTitle Content="Color [0..255]" FontFamily="Georgia" c:Plotter.IsDefaultElement="True"/>
<c:VerticalAxisTitle Content="Distribution of pixels' color" FontFamily="Georgia" c:Plotter.IsDefaultElement="True"/>
</c:ChartPlotter>
</Grid>
</DockPanel>