假设有一份数据,需要用柱图或条图表示,但是,有些类型的值很小,譬如不过1000,有些类型的值很大,譬如2000万了,这个时候普通的柱图显示已经不合理,所以自己开发一个坐标刻度是以指数增长的图表,在设计这个图表的时候,我选择了一个简单的UserControl来实现。这个图表主要有三个部分组成:
纵轴刻度
横轴刻度
中间区域,显示条图和网格线
- 前端代码
<Grid Background="{Binding Path=Background,ElementName=root}">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="30" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="30" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<!--网格面板-->
<Canvas x:Name="GrdLine"
Grid.Row="0"
Grid.Column="2" SizeChanged="GrdLine_OnSizeChanged"></Canvas>
<!--纵轴刻度面板-->
<Canvas x:Name="GrdContext"
Grid.Row="0"
Grid.Column="1"
HorizontalAlignment="Right"
VerticalAlignment="Stretch"
MinWidth="{Binding Path=MinAxisYWidth,ElementName=root}"
Margin="0,0,0,0"
SizeChanged="GrdContext_OnSizeChanged"
>
</Canvas>
<!--横轴刻度面板-->
<Canvas x:Name="GrdScale"
Grid.Row="1"
Grid.Column="2"
HorizontalAlignment="Stretch"
VerticalAlignment="Top"
MinHeight="{Binding Path=MinAxisXHeight,ElementName=root}"
SizeChanged="GrdScale_OnSizeChanged">
</Canvas>
<!--柱子面板-->
<Canvas x:Name="GrdBar"
Grid.Row="0"
Grid.Column="2"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
SizeChanged="GrdBar_OnSizeChanged">
</Canvas>
<!--纵轴单位面板-->
<Canvas x:Name="GrdYUnit"
Grid.Row="0"
Grid.Column="0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Margin="0,0,20,0"
RenderTransformOrigin="0.5,0.5">
<Canvas.RenderTransform>
<RotateTransform Angle="-90" />
</Canvas.RenderTransform>
<TextBlock Text="{Binding Path=YUnit,ElementName=root}"
FontFamily="{Binding Path=AxisFontFamily,ElementName=root}"
FontSize="{Binding Path=AxisFontSize,ElementName=root}"
Foreground="{Binding Path=AxisFontground,ElementName=root}"></TextBlock>
</Canvas>
<!--横轴单位面板-->
<Canvas x:Name="GrdXUnit"
Grid.Row="2"
Grid.Column="2"
HorizontalAlignment="Center"
VerticalAlignment="Center"
RenderTransformOrigin="0.5,0.5">
<Canvas.RenderTransform>
<RotateTransform Angle="0" />
</Canvas.RenderTransform>
<TextBlock Text="{Binding Path=XUnit,ElementName=root}"
FontFamily="{Binding Path=AxisFontFamily,ElementName=root}"
FontSize="{Binding Path=AxisFontSize,ElementName=root}"
Foreground="{Binding Path=AxisFontground,ElementName=root}"></TextBlock>
</Canvas>
</Grid>
- 纵轴
- 根据你纵轴面板的高度自动计算每个刻度的位置
/// <summary>
/// 纵轴
/// </summary>
/// <param name="sources">数据源</param>
private void DrawContextAxis(IEnumerable sources)
{
ObservableCollection<LogarithmicModel> itemsSource = sources as ObservableCollection<LogarithmicModel>;
if (itemsSource == null)
return;
int count = itemsSource.Count;
GrdContext.Children.Clear();
Border bd = new Border();
bd.HorizontalAlignment = HorizontalAlignment.Right;
bd.VerticalAlignment = VerticalAlignment.Stretch;
bd.Width = LineThickness;
bd.Background = LineBrush;
GrdContext.Children.Add(bd);
everyheight = GrdContext.ActualHeight / count;
for (int i = 0; i < itemsSource.Count; i++)
{
TextBlock tx = new TextBlock();
tx.FontFamily = AxisFontFamily;
tx.FontSize = AxisFontSize;
tx.Foreground = AxisFontground;
tx.Text = itemsSource[i].Name;
tx.HorizontalAlignment = HorizontalAlignment.Right;
tx.VerticalAlignment = VerticalAlignment.Bottom;
double right = 3;
double bottom = everyheight * i;
Canvas.SetRight(tx, right);
Canvas.SetBottom(tx, bottom);
tx.Loaded += ContextLoaded;
GrdContext.Children.Add(tx);
}
}
- 横轴
- 根据你横轴面板宽度自动计算么个刻度的位置和显示的值
/// <summary>
/// 画出值刻度轴(横坐标)
/// </summary>
/// <param name="baseValue">底数</param>
/// <param name="scaleCount">刻度个数</param>
private void DrawValueScale(double baseValue, int scaleCount)
{
double grdScaleWidth = GrdScale.ActualWidth;
GrdScale.Children.Clear();
Border bd = new Border();
bd.HorizontalAlignment = HorizontalAlignment.Stretch;
bd.VerticalAlignment = VerticalAlignment.Top;
bd.Height = LineThickness;
bd.Background = LineBrush;
bd.Width = GrdScale.ActualWidth;
GrdScale.Children.Add(bd);
everyWidth = grdScaleWidth / (scaleCount - 1);
for (int i = 0; i < scaleCount; i++)
{
TextBlock tx = new TextBlock();
tx.FontFamily = AxisFontFamily;
tx.FontSize = AxisFontSize;
tx.Foreground = AxisFontground;
tx.Text = Math.Pow(baseValue, i).ToString(CultureInfo.InvariantCulture);
tx.HorizontalAlignment = HorizontalAlignment.Left;
tx.VerticalAlignment = VerticalAlignment.Top;
double left = everyWidth * i;
if (double.IsNaN(left))
{
left = 0;
}
double top = 3;
Canvas.SetLeft(tx, left);
Canvas.SetTop(tx, top);
tx.Loaded += Tx_Loaded;
GrdScale.Children.Add(tx);
}
}
- 绘制柱子
- a) 理解对数函数,计算每根柱子的长度
/// <summary>
/// 绘制柱子
/// </summary>
/// <param name="sources">数据源</param>
private void DrawValueBar(IEnumerable sources)
{
ObservableCollection<LogarithmicModel> itemsSource = sources as ObservableCollection<LogarithmicModel>;
if (itemsSource == null)
return;
int count = itemsSource.Count;
GrdBar.Children.Clear();
for (int i = 0; i < count; i++)
{
double value = itemsSource[i].Value;
double barCount = Math.Log(value, BaseValue);
double barWidth = barCount * everyWidth;
if (Math.Abs(value) <= 0)
{
barWidth = 0;
}
Border bd = new Border();
bd.Height = GetBarHeight(everyheight);
bd.Width = barWidth;
if (barBrushes.Count <= i || barBrushes[i] != null)
{
Brush b = GenerateRandomBrush();
barBrushes.Add(b);
}
bd.Background = barBrushes[i];
bd.HorizontalAlignment = HorizontalAlignment.Left;
bd.VerticalAlignment = VerticalAlignment.Bottom;
//ToolTip
StackPanel stp = new StackPanel();
TextBlock tooltip = new TextBlock();
tooltip.Text = itemsSource[i].Name.ToString();
TextBlock tooltip1 = new TextBlock();
tooltip1.Text = value.ToString();
stp.Children.Add(tooltip);
stp.Children.Add(tooltip1);
bd.ToolTip = stp;
double left = 0;
double bottom = everyheight * i;
Canvas.SetLeft(bd, left);
Canvas.SetBottom(bd, bottom);
bd.Loaded += Bd_Loaded;
GrdBar.Children.Add(bd);
}
}
其他的譬如数据更新处理,柱子颜色加载、文字样式,网格线等,就不贴出来了。这个demo大家做个参考,希望对需要的人有用。