From d7c027b73225bdae0ff3030195564685305f78f0 Mon Sep 17 00:00:00 2001 From: QI Mingxuan Date: Thu, 21 May 2026 10:37:28 +0800 Subject: [PATCH] =?UTF-8?q?=E7=9B=B4=E6=96=B9=E5=9B=BE=E5=B0=86=E6=9F=B1?= =?UTF-8?q?=E7=8A=B6=E5=9B=BE=E6=9B=BF=E6=8D=A2=E4=B8=BA=E9=9D=A2=E7=A7=AF?= =?UTF-8?q?=E5=9B=BE=EF=BC=8C=E4=BC=98=E5=8C=96=E5=AF=86=E9=9B=86=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E6=98=BE=E7=A4=BA=E6=95=88=E6=9E=9C=EF=BC=8CY?= =?UTF-8?q?=E8=BD=B4=E5=88=BB=E5=BA=A6=E8=87=AA=E5=8A=A8=E5=8F=96=E6=95=B4?= =?UTF-8?q?=E6=94=AF=E6=8C=81=20K/M=20=E7=BC=A9=E5=86=99=EF=BC=8CX=20?= =?UTF-8?q?=E8=BD=B4=E6=A0=B9=E6=8D=AE=E6=95=B0=E6=8D=AE=E8=8C=83=E5=9B=B4?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E8=AE=BE=E7=BD=AE=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controls/ImageHistogram/ChartRenderer.cs | 61 ++++++++++++++++--- .../ImageHistogram/FrequencyLabelConverter.cs | 49 +++++++++++++++ .../ImageHistogram/ImageHistogramControl.xaml | 41 +++++++++---- 3 files changed, 131 insertions(+), 20 deletions(-) create mode 100644 XP.Common/Controls/ImageHistogram/FrequencyLabelConverter.cs diff --git a/XP.Common/Controls/ImageHistogram/ChartRenderer.cs b/XP.Common/Controls/ImageHistogram/ChartRenderer.cs index bf242a4..b02d5fc 100644 --- a/XP.Common/Controls/ImageHistogram/ChartRenderer.cs +++ b/XP.Common/Controls/ImageHistogram/ChartRenderer.cs @@ -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; /// @@ -25,12 +25,12 @@ namespace XP.Common.Controls.ImageHistogram /// 构造函数,接收 RadCartesianChart 实例 | Constructor, receives RadCartesianChart instance /// /// 图表控件实例 | Chart control instance - /// 柱状图系列 | Bar series + /// 面积图系列 | Area series /// X 轴 | X axis - 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(); @@ -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 /// 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 collection) + if (_areaSeries.ItemsSource is ICollection collection) return collection.Count; - if (_barSeries.ItemsSource is IEnumerable enumerable) + if (_areaSeries.ItemsSource is IEnumerable 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); + } + } + + /// + /// 将步长取整为"好看"的数值(1, 2, 5 的倍数 × 10^n)| Round step to "nice" value (multiples of 1, 2, 5 × 10^n) + /// 例如:123456 → 100000,350000 → 500000,780000 → 1000000 + /// + 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); } /// diff --git a/XP.Common/Controls/ImageHistogram/FrequencyLabelConverter.cs b/XP.Common/Controls/ImageHistogram/FrequencyLabelConverter.cs new file mode 100644 index 0000000..76a76c8 --- /dev/null +++ b/XP.Common/Controls/ImageHistogram/FrequencyLabelConverter.cs @@ -0,0 +1,49 @@ +using System; +using System.Globalization; +using System.Windows.Data; + +namespace XP.Common.Controls.ImageHistogram +{ + /// + /// 频次标签转换器:将大数值转为 K/M 缩写格式 | Frequency label converter: converts large values to K/M abbreviation format + /// 例如:500000 → "500K",1500000 → "1.5M",800 → "800" + /// + 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(); + } + } +} diff --git a/XP.Common/Controls/ImageHistogram/ImageHistogramControl.xaml b/XP.Common/Controls/ImageHistogram/ImageHistogramControl.xaml index 7d1f974..f274cf1 100644 --- a/XP.Common/Controls/ImageHistogram/ImageHistogramControl.xaml +++ b/XP.Common/Controls/ImageHistogram/ImageHistogramControl.xaml @@ -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"> + + + - + - + + MajorStep="64" + FontSize="9"> + + + + + + - + + FontSize="9"> + + + + + + - + - + @@ -44,7 +63,7 @@ Text="{loc:Localization Histogram_NoData}" HorizontalAlignment="Center" VerticalAlignment="Center" - FontSize="14" + FontSize="12" Foreground="#9E9E9E" Visibility="Visible"/>