WPF Charts (Part 6)
Posted by Bastian M.K. Ohta
Scrolling day
Sometimes charts are too big, axes look like ugly clusters and details vanish in the limitedness of limits. You can flip labels by 90 degrees, but this won’t always solve the problem.
There is a simple way for beginners to zoom a graph. Use the ScrollViewer control and set the size of the inner context to something larger than the control. This works fine. It is just looks slightly irritating. The axes are disappearing from the visible area as soon as you start scrolling. This is demonstrated in the left graph.
A more advanced approach can be found in the right graph. There are 4 ScrollBars. The inner ScrollBars are moving the visible graph area. The outer ScrollBars are determining the zoom level. This can be achieved by setting the minimum and maximum properties of the corresponding axis. Take care to never cross these values. The minimum must be less than the maximum. Therefore, when you for instance increase the X-axis position, you should first set the new maximum and then the new minimum. Don’t do it the other way around.
My first try to scroll charts involved value converters. You would bind the minimum and then automatically set the maximum. This approach does not work. You quickly get exceptions where the minimum is larger than the maximum. But events can deal with this easily.
<Window x:Class="Demo.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:Demo" xmlns:datavisualization="clr-namespace:System.Windows.Controls.DataVisualization;assembly=System.Windows.Controls.DataVisualization.Toolkit" xmlns:chart="clr-namespace:System.Windows.Controls.DataVisualization.Charting;assembly=System.Windows.Controls.DataVisualization.Toolkit" Title="Demo Window" Loaded ="Window_Loaded"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="1*"/> <ColumnDefinition Width="1*"/> </Grid.ColumnDefinitions> <ScrollViewer HorizontalScrollBarVisibility="Visible" VerticalScrollBarVisibility="Visible" Grid.Column="0"> <chart:Chart Name="myChart1" Width="1000" Height="600"> <chart:Chart.LegendStyle> <Style TargetType="datavisualization:Legend"> <Setter Property="Width" Value="0" /> </Style> </chart:Chart.LegendStyle> <chart:Chart.Axes> <chart:LinearAxis Name="YAxis1" Orientation="Y" ShowGridLines="False" /> <chart:LinearAxis Name="XAxis1" Orientation="X" ShowGridLines="False"/> </chart:Chart.Axes> <chart:LineSeries Name="MyLineSeries1" ItemsSource="{Binding}" IndependentValueBinding="{Binding X}" DependentValueBinding="{Binding Y}"> <chart:LineSeries.DataPointStyle> <Style TargetType="{x:Type chart:LineDataPoint}"> <Setter Property="Background" Value="Black" /> <Setter Property="Height" Value="0"/> <Setter Property="Width" Value="0"/> </Style> </chart:LineSeries.DataPointStyle> </chart:LineSeries> </chart:Chart> </ScrollViewer> <Grid Grid.Column="1"> <DockPanel LastChildFill="True" > <ScrollBar Name="HBarZoom" Height="20" DockPanel.Dock="Bottom" Orientation="Horizontal" BorderBrush="Black" Value="50" Minimum="1" Maximum="100" Margin="0,0,40,0" ValueChanged="HBar_ValueChanged"/> <ScrollBar Name="HBar" Height="20" DockPanel.Dock="Bottom" Orientation="Horizontal" BorderBrush="Black" Value="250" Minimum="0" Maximum="100" Margin="0,0,40,0" ValueChanged="HBar_ValueChanged"/> <ScrollBar Name="VBarZoom" Width="20" DockPanel.Dock="Right" Orientation="Vertical" BorderBrush="Black" Value="50" Minimum="1" Maximum="100" ValueChanged="VBar_ValueChanged"> <ScrollBar.LayoutTransform> <RotateTransform Angle="180"/> </ScrollBar.LayoutTransform> </ScrollBar> <ScrollBar Name="VBar" Width="20" DockPanel.Dock="Right" Orientation="Vertical" Value="1" Minimum="0" Maximum="1000000" BorderBrush="Black" ValueChanged="VBar_ValueChanged"> <ScrollBar.LayoutTransform> <RotateTransform Angle="180"/> </ScrollBar.LayoutTransform> </ScrollBar> <chart:Chart Name="myChart2" DockPanel.Dock="Bottom"> <chart:Chart.LegendStyle> <Style TargetType="datavisualization:Legend"> <Setter Property="Width" Value="0" /> </Style> </chart:Chart.LegendStyle> <chart:Chart.Axes> <chart:LinearAxis Name="YAxis2" Orientation="Y" ShowGridLines="False" /> <chart:LinearAxis Name="XAxis2" Orientation="X" ShowGridLines="False" /> </chart:Chart.Axes> <chart:LineSeries Name="MyLineSeries2" ItemsSource="{Binding}" IndependentValueBinding="{Binding X}" DependentValueBinding="{Binding Y}"> <chart:LineSeries.DataPointStyle> <Style TargetType="{x:Type chart:LineDataPoint}"> <Setter Property="Background" Value="Black" /> <Setter Property="Height" Value="0"/> <Setter Property="Width" Value="0"/> </Style> </chart:LineSeries.DataPointStyle> </chart:LineSeries> </chart:Chart> </DockPanel> </Grid> </Grid> </Window>
using System; using System.Collections.Generic; using System.Windows; using System.Linq; namespace Demo { public partial class MainWindow : Window { private double _YMin, _YMax; private int _XMin = 0, _XMax = 2000; public MainWindow() { InitializeComponent(); } // constructor private void Window_Loaded(object xSender, RoutedEventArgs e) { // add arbitrary LineSeries points List<Point> lPoints = new List<Point>(); Random lRandom = new Random(); double y = 1.0; double lYMin = double.MaxValue; double lYMax = double.MinValue; for (int i = _XMin; i < _XMax; i++) { double lChange = lRandom.NextDouble() - 0.5; y += lChange / 100.0; if (y > lYMax) lYMax = y; if (y < lYMin) lYMin = y; Point lPoint = new Point((double)i, y); lPoints.Add(lPoint); } _YMax = lYMax; _YMin = lYMin; VBar.Maximum = lYMax; VBar.Minimum = lYMin; HBar.Minimum = _XMin; HBar.Maximum = _XMax; // we clone the list to avoid trouble (deep copy) List<Point> lPoints2 = (from p in lPoints select new Point(p.X, p.Y)).ToList(); MyLineSeries1.ItemsSource = lPoints; MyLineSeries2.ItemsSource = lPoints2; } // private void HBar_ValueChanged(object xSender, RoutedPropertyChangedEventArgs<double> e) { if (XAxis2 == null) return; double? lMax = XAxis2.Maximum; if (lMax == null) lMax = XAxis2.ActualMaximum; //double? lMin = XAxis2.Minimum; if (lMin == null) lMin = XAxis2.ActualMinimum; double lZoom = (_XMax - _XMin) * HBarZoom.Value / 100.0 / 2.0; double lValue = HBar.Value; // We do not use e.NewValue, because this event is called from many sources. if (lValue > lMax) { XAxis2.Maximum = Math.Max(lValue + lZoom, _XMax); // widen the range first! XAxis2.Minimum = Math.Min(XAxis2.Maximum.Value - lZoom, _XMin); // now we can tighten the range return; } XAxis2.Minimum = Math.Max(lValue - lZoom, _XMin); // widen XAxis2.Maximum = Math.Min(XAxis2.Minimum.Value + lZoom, _XMax); // tighten e.Handled = true; } // private void VBar_ValueChanged(object xSender, RoutedPropertyChangedEventArgs<double> e) { if (XAxis2 == null) return; double? lMax = YAxis2.Maximum; if (lMax == null) lMax = YAxis2.ActualMaximum; //double? lMin = YAxis2.Minimum; if (lMin == null) lMin = YAxis2.ActualMinimum; double lZoom = (_YMax - _YMin) * VBarZoom.Value / 100.0 / 2.0; double lValue = VBar.Value; // We do not use e.NewValue, because this event is called from many sources. if (lValue > lMax) { YAxis2.Maximum = Math.Min(lValue + lZoom, _YMax); YAxis2.Minimum = Math.Max(YAxis2.Maximum.Value - lZoom, _YMin); return; } YAxis2.Minimum = Math.Max(lValue - lZoom, _YMin); YAxis2.Maximum = Math.Min(YAxis2.Minimum.Value + lZoom, _YMax); e.Handled = true; } // } // class } // namespace
Posted on November 25, 2014, in Advanced, Basic, C#, Charts, DataBinding, WPF and tagged advanced, button, C#, C-sharp, chart, DataBinding, datavisualization, draw, OnMouse, programming, Source code, Windows, WPF. Bookmark the permalink. Leave a comment.
Leave a comment
Comments 0