private Grid _series; // grid for series of points #endregion Fields #region Methods internal static IEnumerable<double> GetLabelValues(Range dataRange, double maxLabel) { double maxValue = Math.Max(-dataRange.Min, dataRange.Max); if (maxValue <= 0.0) // all values equal to 0 { yield return 0.0; } else { double nbDigit = Math.Ceiling(Math.Log10(maxValue / maxLabel)); double step = Math.Pow(10, nbDigit); // increment the label by a power of 10 if (step * 1.5 > maxValue) step /= 2; // label 5 by 5 else only one label that may be far and thus inelegant for (double current = 0.0; current <= dataRange.Max + step / 2.0; current += step) { yield return current; } for (double current = -step; current >= dataRange.Min - step / 2.0; current -= step) { yield return current; } } }
private Grid _series; // grid for series of points #endregion Fields #region Methods /// <summary> /// Generates the column chart. /// </summary> protected override void GenerateChart() { RootElement.Children.Clear(); var data = ItemsSource; if (data != null && data.Any()) { double max = Math.Max(0, data.Values.Max()); double min = Math.Min(0, data.Values.Min()); Range dataRange = new Range(min, max); // Get the label values var labels = BarChart.GetLabelValues(dataRange, MaxLabel).ToArray(); // The label might be out of the current range (e.g if max value = '99', there will be a label '100') // Extend the range to include the labels dataRange.Min = Math.Min(dataRange.Min, labels.Min()); dataRange.Max = Math.Max(dataRange.Max, labels.Max()); // Create chart main structure GenerateChartStructure(); // Generate the labels foreach (double val in labels) GenerateLabel(val, dataRange); // Generate data points foreach (var kvp in data) { GenerateDataPoint(kvp, dataRange); } } }
private void GenerateChartStructure(Range range) { // Main grid Grid root = new Grid { Margin = new Thickness(0, 10, 0, 10) }; // Vertical Margin for labels root.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(0, GridUnitType.Auto) }); // Column for labels root.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }); // Column for chart RootElement.Children.Add(root); // Grid for labels (column 0) _labels = new Grid(); root.Children.Add(_labels); // Grid for chart (column 1) Grid chart = new Grid(); Grid.SetColumn(chart, 1); root.Children.Add(chart); // Grid for Axis in coordinate 0 Grid gridAxis = new Grid(); double fraction = range.Fraction(0); gridAxis.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1 - fraction, GridUnitType.Star) }); gridAxis.RowDefinitions.Add(new RowDefinition { Height = new GridLength(fraction, GridUnitType.Star) }); chart.Children.Add(gridAxis); // Axis Border axisX = new Border { BorderThickness = new Thickness(0.75), BorderBrush = ForegroundColor, Opacity = 0.5, HorizontalAlignment = HorizontalAlignment.Stretch, VerticalAlignment = VerticalAlignment.Bottom, Margin = new Thickness(-5, 0, -5, 0) }; gridAxis.Children.Add(axisX); Border axisY = new Border { BorderThickness = new Thickness(0.75), BorderBrush = ForegroundColor, Opacity = 0.5, HorizontalAlignment = HorizontalAlignment.Left, VerticalAlignment = VerticalAlignment.Stretch, Margin = new Thickness(0, -5, 0, -5) }; chart.Children.Add(axisY); // Grid for series of points _series = new Grid(); chart.Children.Add(_series); }
// Generate one label private void GenerateLabel(double val, Range dataRange) { double fraction = dataRange.Fraction(val); Grid label = new Grid(); _labels.Children.Add(label); label.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1 - fraction, GridUnitType.Star) }); label.RowDefinitions.Add(new RowDefinition { Height = new GridLength(0, GridUnitType.Pixel) }); label.RowDefinitions.Add(new RowDefinition { Height = new GridLength(fraction, GridUnitType.Star) }); StackPanel stackPanel = new StackPanel { VerticalAlignment = VerticalAlignment.Center, HorizontalAlignment = HorizontalAlignment.Right, Orientation = Orientation.Horizontal, Margin = new Thickness(0, -20, 0, -20) }; Grid.SetRow(stackPanel, 1); label.Children.Add(stackPanel); TextBlock text = new TextBlock { Text = FormattedValue(val) }; stackPanel.Children.Add(text); Rectangle rect = new Rectangle { Stroke = ForegroundColor, Width = 5, Height = 1, Opacity = 0.5, Margin = new Thickness(2, 0, 0, 0) }; stackPanel.Children.Add(rect); }
private void GenerateDataPoint(KeyValuePair<string, double> kvp, Range range) { // Add a column and add a grid in this column _series.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }); Grid point = new Grid(); Grid.SetColumn(point, _series.ColumnDefinitions.Count - 1); _series.Children.Add(point); // Divide the grid in 3 rows double val = kvp.Value; double negativeFraction = range.Fraction(Math.Min(val, 0)); double positiveFraction = 1 - range.Fraction(Math.Max(val, 0)); point.RowDefinitions.Add(new RowDefinition { Height = new GridLength(positiveFraction, GridUnitType.Star) }); point.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1 - positiveFraction - negativeFraction, GridUnitType.Star), MinHeight = MinBarSize}); point.RowDefinitions.Add(new RowDefinition { Height = new GridLength(negativeFraction, GridUnitType.Star) }); Border axisX = new Border { BorderThickness = new Thickness(0.75), BorderBrush = ForegroundColor, Opacity = 0.5, HorizontalAlignment = HorizontalAlignment.Stretch, VerticalAlignment = val > 0 ? VerticalAlignment.Bottom : VerticalAlignment.Top, Margin = new Thickness(-5, 0, -5, 0) }; Grid.SetRow(axisX, 1); point.Children.Add(axisX); // Put a bar in the middle row Border bar = new Border { Margin = new Thickness(MarginBetweenBars, 0, MarginBetweenBars, 0), Background = GetColorByIndex(0) }; Grid.SetRow(bar, 1); SetTooltip(bar, kvp.Key, FormattedValue(kvp.Value)); point.Children.Add(bar); }
private void GenerateDataPoints(IDictionary<string, double> data, Range dataRange) { int count = data.Count; // Project the data in order to get the points in percent of the drawing space (rectangle 1*1) var points = data.Select((kvp, ind) => new { x = (2.0 * ind + 1.0) / (2.0 * count), y = 1.0 - dataRange.Fraction(kvp.Value)}); // Generate the lines from point to point var firstPoint = points.First(); PathFigure pathFigure = new PathFigure { IsClosed = false, StartPoint = new Point(firstPoint.x, firstPoint.y) }; foreach (var point in points.Skip(1)) { pathFigure.Segments.Add(new LineSegment {Point = new Point(point.x, point.y)}); } //Add these two empty line segments to force the drawing to stretch properly PathFigure pathFigure2 = new PathFigure { StartPoint = new Point(0, 0) }; pathFigure2.Segments.Add(new LineSegment { Point = new Point(0, 0) }); PathFigure pathFigure3 = new PathFigure { StartPoint = new Point(1, 1) }; pathFigure3.Segments.Add(new LineSegment { Point = new Point(1, 1) }); PathGeometry pathGeometry = new PathGeometry(); pathGeometry.Figures.Add(pathFigure2); pathGeometry.Figures.Add(pathFigure3); pathGeometry.Figures.Add(pathFigure); Path path = new Path { Stretch = Stretch.Fill, // stretch the polyline to fill the chart drawing space Stroke = GetColorByIndex(0), StrokeThickness = 2, StrokeLineJoin = PenLineJoin.Round, Data = pathGeometry, Margin = new Thickness(-1,-1,-1,-1) // half of strokeThickness }; _series.Children.Add(path); // Add a new grid for the points Grid pointsGrid = new Grid(); _series.Children.Add(pointsGrid); // Generate the points foreach (var kvp in data) { GenerateDataPoint(pointsGrid, kvp, dataRange); } }
private void GenerateDataPoint(Grid points, KeyValuePair<string, double> kvp, Range range) { // Add a column and add a grid in this column points.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }); Grid point = new Grid(); Grid.SetColumn(point, points.ColumnDefinitions.Count - 1); points.Children.Add(point); // Divide the grid in 3 rows double val = kvp.Value; double fraction = range.Fraction(val); point.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1 - fraction, GridUnitType.Star) }); point.RowDefinitions.Add(new RowDefinition { Height = new GridLength(0, GridUnitType.Pixel) }); point.RowDefinitions.Add(new RowDefinition { Height = new GridLength(fraction, GridUnitType.Star) }); // Put a point in the middle row Ellipse ellipse = new Ellipse { Height = 10, Width = 10, Fill = GetColorByIndex(0), HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Center, Margin = new Thickness(0, -20, 0, -20), Stroke = ForegroundColor, StrokeThickness = 1 }; Grid.SetRow(ellipse, 1); SetTooltip(ellipse, kvp.Key, FormattedValue(kvp.Value)); point.Children.Add(ellipse); }