直方图将柱状图替换为面积图,优化密集数据显示效果,Y轴刻度自动取整支持 K/M 缩写,X 轴根据数据范围自动设置。
This commit is contained in:
@@ -13,7 +13,7 @@ namespace XP.Common.Controls.ImageHistogram
|
||||
internal sealed class ChartRenderer
|
||||
{
|
||||
private readonly RadCartesianChart _chart;
|
||||
private readonly BarSeries _barSeries;
|
||||
private readonly ScatterAreaSeries _areaSeries;
|
||||
private readonly LinearAxis _xAxis;
|
||||
|
||||
/// <summary>
|
||||
@@ -25,12 +25,12 @@ namespace XP.Common.Controls.ImageHistogram
|
||||
/// 构造函数,接收 RadCartesianChart 实例 | Constructor, receives RadCartesianChart instance
|
||||
/// </summary>
|
||||
/// <param name="chart">图表控件实例 | Chart control instance</param>
|
||||
/// <param name="barSeries">柱状图系列 | Bar series</param>
|
||||
/// <param name="areaSeries">面积图系列 | Area series</param>
|
||||
/// <param name="xAxis">X 轴 | X axis</param>
|
||||
public ChartRenderer(RadCartesianChart chart, BarSeries barSeries, LinearAxis xAxis)
|
||||
public ChartRenderer(RadCartesianChart chart, ScatterAreaSeries areaSeries, LinearAxis xAxis)
|
||||
{
|
||||
_chart = chart ?? throw new ArgumentNullException(nameof(chart));
|
||||
_barSeries = barSeries ?? throw new ArgumentNullException(nameof(barSeries));
|
||||
_areaSeries = areaSeries ?? throw new ArgumentNullException(nameof(areaSeries));
|
||||
_xAxis = xAxis ?? throw new ArgumentNullException(nameof(xAxis));
|
||||
}
|
||||
|
||||
@@ -64,6 +64,8 @@ namespace XP.Common.Controls.ImageHistogram
|
||||
// 设置 X 轴范围 | Set X axis range
|
||||
_xAxis.Minimum = 0;
|
||||
_xAxis.Maximum = xAxisMax;
|
||||
// 根据范围自动设置刻度间隔(保持 4-5 个刻度)| Auto set major step based on range (keep 4-5 ticks)
|
||||
_xAxis.MajorStep = xAxisMax <= 255 ? 64 : 16384;
|
||||
|
||||
// 构建数据点 | Build data points
|
||||
var dataPoints = new List<HistogramDataPoint>();
|
||||
@@ -103,7 +105,7 @@ namespace XP.Common.Controls.ImageHistogram
|
||||
}
|
||||
|
||||
// 更新图表数据 | Update chart data
|
||||
_barSeries.ItemsSource = dataPoints;
|
||||
_areaSeries.ItemsSource = dataPoints;
|
||||
|
||||
// 设置 Y 轴范围 | Set Y axis range
|
||||
UpdateYAxis(displayData, isLogarithmic);
|
||||
@@ -114,7 +116,7 @@ namespace XP.Common.Controls.ImageHistogram
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
_barSeries.ItemsSource = null;
|
||||
_areaSeries.ItemsSource = null;
|
||||
|
||||
// X 轴范围重置为 0-255 | Reset X axis range to 0-255
|
||||
_xAxis.Minimum = 0;
|
||||
@@ -131,9 +133,9 @@ namespace XP.Common.Controls.ImageHistogram
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_barSeries.ItemsSource is ICollection<HistogramDataPoint> collection)
|
||||
if (_areaSeries.ItemsSource is ICollection<HistogramDataPoint> collection)
|
||||
return collection.Count;
|
||||
if (_barSeries.ItemsSource is IEnumerable<HistogramDataPoint> enumerable)
|
||||
if (_areaSeries.ItemsSource is IEnumerable<HistogramDataPoint> enumerable)
|
||||
return enumerable.Count();
|
||||
return 0;
|
||||
}
|
||||
@@ -173,7 +175,48 @@ namespace XP.Common.Controls.ImageHistogram
|
||||
if (maxValue == 0)
|
||||
maxValue = 1;
|
||||
|
||||
SetYAxisRange(0, maxValue, isLogarithmic);
|
||||
// 计算取整的 MajorStep(约 4 个刻度,对齐到 K/M 整数倍)| Calculate rounded MajorStep (~4 ticks, aligned to K/M multiples)
|
||||
if (_chart.VerticalAxis is LinearAxis linearAxis)
|
||||
{
|
||||
long rawStep = maxValue / 4;
|
||||
long step = RoundStepToNice(rawStep);
|
||||
if (step < 1) step = 1;
|
||||
linearAxis.MajorStep = step;
|
||||
|
||||
// 将最大值向上取整到 step 的整数倍 | Round max up to multiple of step
|
||||
long roundedMax = ((maxValue / step) + 1) * step;
|
||||
SetYAxisRange(0, roundedMax, isLogarithmic);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetYAxisRange(0, maxValue, isLogarithmic);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将步长取整为"好看"的数值(1, 2, 5 的倍数 × 10^n)| Round step to "nice" value (multiples of 1, 2, 5 × 10^n)
|
||||
/// 例如:123456 → 100000,350000 → 500000,780000 → 1000000
|
||||
/// </summary>
|
||||
private static long RoundStepToNice(long rawStep)
|
||||
{
|
||||
if (rawStep <= 0) return 1;
|
||||
|
||||
// 找到数量级 | Find order of magnitude
|
||||
double magnitude = Math.Pow(10, Math.Floor(Math.Log10(rawStep)));
|
||||
double normalized = rawStep / magnitude;
|
||||
|
||||
// 取整到 1, 2, 5 中最近的 | Round to nearest of 1, 2, 5
|
||||
double niceNormalized;
|
||||
if (normalized <= 1.5)
|
||||
niceNormalized = 1;
|
||||
else if (normalized <= 3.5)
|
||||
niceNormalized = 2;
|
||||
else if (normalized <= 7.5)
|
||||
niceNormalized = 5;
|
||||
else
|
||||
niceNormalized = 10;
|
||||
|
||||
return (long)(niceNormalized * magnitude);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace XP.Common.Controls.ImageHistogram
|
||||
{
|
||||
/// <summary>
|
||||
/// 频次标签转换器:将大数值转为 K/M 缩写格式 | Frequency label converter: converts large values to K/M abbreviation format
|
||||
/// 例如:500000 → "500K",1500000 → "1.5M",800 → "800"
|
||||
/// </summary>
|
||||
internal sealed class FrequencyLabelConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value == null) return "0";
|
||||
|
||||
double num;
|
||||
if (value is double d)
|
||||
num = d;
|
||||
else if (value is decimal dec)
|
||||
num = (double)dec;
|
||||
else if (!double.TryParse(value.ToString(), out num))
|
||||
return value.ToString() ?? "0";
|
||||
|
||||
if (num >= 1_000_000)
|
||||
{
|
||||
double mValue = num / 1_000_000.0;
|
||||
return mValue == Math.Floor(mValue)
|
||||
? $"{(int)mValue}M"
|
||||
: $"{mValue:0.#}M";
|
||||
}
|
||||
|
||||
if (num >= 1_000)
|
||||
{
|
||||
double kValue = num / 1_000.0;
|
||||
return kValue == Math.Floor(kValue)
|
||||
? $"{(int)kValue}K"
|
||||
: $"{kValue:0.#}K";
|
||||
}
|
||||
|
||||
return $"{(int)num}";
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,39 +3,58 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation"
|
||||
xmlns:loc="clr-namespace:XP.Common.Localization.Extensions"
|
||||
xmlns:local="clr-namespace:XP.Common.Controls.ImageHistogram"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="200" d:DesignWidth="400">
|
||||
<UserControl.Resources>
|
||||
<local:FrequencyLabelConverter x:Key="FreqConverter"/>
|
||||
</UserControl.Resources>
|
||||
<Grid>
|
||||
<!-- 图表控件 | Chart control -->
|
||||
<telerik:RadCartesianChart x:Name="HistogramChart">
|
||||
<telerik:RadCartesianChart x:Name="HistogramChart" Padding="0">
|
||||
<!-- 禁用 Telerik 自带的无数据提示 | Disable Telerik built-in empty content -->
|
||||
<telerik:RadCartesianChart.EmptyContent>
|
||||
<TextBlock/>
|
||||
</telerik:RadCartesianChart.EmptyContent>
|
||||
<!-- X 轴:灰度级别 | X Axis: Gray Level -->
|
||||
<!-- X 轴:灰度级别(缩小字体,控制刻度数量,K/M 缩写)| X Axis: Gray Level -->
|
||||
<telerik:RadCartesianChart.HorizontalAxis>
|
||||
<telerik:LinearAxis x:Name="XAxis"
|
||||
Minimum="0"
|
||||
Maximum="255"
|
||||
Title="灰度级别"/>
|
||||
MajorStep="64"
|
||||
FontSize="9">
|
||||
<telerik:LinearAxis.LabelTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Converter={StaticResource FreqConverter}}"/>
|
||||
</DataTemplate>
|
||||
</telerik:LinearAxis.LabelTemplate>
|
||||
</telerik:LinearAxis>
|
||||
</telerik:RadCartesianChart.HorizontalAxis>
|
||||
|
||||
<!-- Y 轴:像素频次(默认线性)| Y Axis: Pixel Frequency (default linear) -->
|
||||
<!-- Y 轴:像素频次(K/M 缩写标签)| Y Axis: Pixel Frequency (K/M abbreviation labels) -->
|
||||
<telerik:RadCartesianChart.VerticalAxis>
|
||||
<telerik:LinearAxis x:Name="YAxis"
|
||||
Minimum="0"
|
||||
Maximum="1"
|
||||
Title="频次"/>
|
||||
FontSize="9">
|
||||
<telerik:LinearAxis.LabelTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Converter={StaticResource FreqConverter}}"/>
|
||||
</DataTemplate>
|
||||
</telerik:LinearAxis.LabelTemplate>
|
||||
</telerik:LinearAxis>
|
||||
</telerik:RadCartesianChart.VerticalAxis>
|
||||
|
||||
<!-- 柱状图系列 | Bar Series -->
|
||||
<!-- 面积图系列(适合密集直方图数据)| Area Series (suitable for dense histogram data) -->
|
||||
<telerik:RadCartesianChart.Series>
|
||||
<telerik:BarSeries x:Name="HistogramBarSeries"
|
||||
ValueBinding="Frequency"
|
||||
CategoryBinding="GrayLevel"
|
||||
ShowLabels="False"/>
|
||||
<telerik:ScatterAreaSeries x:Name="HistogramBarSeries"
|
||||
XValueBinding="GrayLevel"
|
||||
YValueBinding="Frequency"
|
||||
Fill="#7F2196F3"
|
||||
Stroke="#FF2196F3"
|
||||
StrokeThickness="1"/>
|
||||
</telerik:RadCartesianChart.Series>
|
||||
</telerik:RadCartesianChart>
|
||||
|
||||
@@ -44,7 +63,7 @@
|
||||
Text="{loc:Localization Histogram_NoData}"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="14"
|
||||
FontSize="12"
|
||||
Foreground="#9E9E9E"
|
||||
Visibility="Visible"/>
|
||||
</Grid>
|
||||
|
||||
Reference in New Issue
Block a user