XamDataChart の積層系列(StackedBarSeries/StackedColumnSeries)で、各フラグメント(セグメント)の矩形中央にデータ値を表示するには、StackedFragmentSeries の MarkerTemplate プロパティに、値に応じて Margin を計算するコンバーターをバインドした DataTemplate を設定します。

XamDataChart の設定

まず、積層系列(ここでは垂直方向の StackedColumnSeries を例に説明します)を構成します。各データ系列(Coal、Oil など)を StackedColumnSeries.Series 配下に StackedFragmentSeries として追加し、ValueMemberPath で対応するプロパティをバインドします。水平方向に積み上げる場合は同様の構成を StackedBarSeries で行います。

<ig:XamDataChart>
    <ig:XamDataChart.Axes>
        <ig:CategoryXAxis x:Name="xAxis" ItemsSource="{Binding}" Label="{}{Country}"/>
        <ig:NumericYAxis x:Name="yAxis" MinimumValue="0"/>
    </ig:XamDataChart.Axes>
    <ig:XamDataChart.Series>
        <ig:StackedColumnSeries XAxis="{Binding ElementName=xAxis}"
                        YAxis="{Binding ElementName=yAxis}"
                        ItemsSource="{Binding}">
            <ig:StackedColumnSeries.Series>
                <ig:StackedFragmentSeries x:Name="fragmentCoal" ValueMemberPath="Coal" Title="Coal" />
                <ig:StackedFragmentSeries x:Name="fragmentOil" ValueMemberPath="Oil" Title="Oil" />
                <!-- Gas, Nuclear, Hydro も同様に追加 -->
            </ig:StackedColumnSeries.Series>
        </ig:StackedColumnSeries>
    </ig:XamDataChart.Series>
</ig:XamDataChart>

MarkerTemplate の設定

StackedFragmentSeriesMarkerTemplate を設定し、内部の TextBlockText にデータ値(Item.Coal など)を、Margin に位置計算用のコンバーターをそれぞれバインドします。

<ig:StackedFragmentSeries x:Name="fragmentCoal" ValueMemberPath="Coal" Title="Coal">
    <ig:StackedFragmentSeries.MarkerTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding Item.Coal}" Margin="{Binding Converter={StaticResource markerConverter}, ConverterParameter=Coal}"/>
        </DataTemplate>
    </ig:StackedFragmentSeries.MarkerTemplate>
</ig:StackedFragmentSeries>

ラベル位置を計算するコンバーターの実装

IValueConverter を実装し、対象フラグメントの値を軸の ScaleValue でピクセル座標に変換した差分から MarginThickness)を算出します。水平方向の BarFragment では XAxis、垂直方向の ColumnFragment では YAxisScaleValue を使い分けることで、ラベルが各フラグメントの矩形内の適切な位置に表示されます。

public class SeriesMarkerConverter : IValueConverter
{
    private static readonly Dictionary<string, Func<EnergyProduction, double>> _accessors
        = new Dictionary<string, Func<EnergyProduction, double>>
        {
            { "Coal", e => e.Coal },
            // Oil, Gas, Nuclear, Hydro も同様に登録
        };

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value is DataContext context &&
            context.Item is EnergyProduction energyProduction &&
            parameter is string propName &&
            _accessors.TryGetValue(propName, out var accessor))
        {
            var val = accessor(energyProduction);

            // 水平方向(StackedBarSeries): XAxisのスケール値から差分を計算
            if (context.Series is BarFragment barFragment)
            {
                var xAxis = barFragment.XAxis;
                var scaledValue = xAxis.ScaleValue(val);
                var scaled0 = xAxis.ScaleValue(0);
                var diff = Math.Abs(scaledValue - scaled0);
                return new Thickness(0, 0, diff, 0);
            }

            // 垂直方向(StackedColumnSeries): YAxisのスケール値から差分を計算
            if (context.Series is ColumnFragment fragment)
            {
                var yAxis = fragment.YAxis as NumericYAxis;
                var scaledMin = yAxis?.ScaleValue(yAxis.ActualMinimumValue);
                var scaledValue = yAxis?.ScaleValue(val);

                if (scaledMin.HasValue && scaledValue.HasValue)
                {
                    return new Thickness(0, scaledMin.Value - scaledValue.Value, 0, 0);
                }
            }
        }
        return new Thickness(0, 0, 0, 0);
    }
}

RefreshCompleted イベントの処理

XamDataChart の RefreshCompleted イベントで、各 StackedFragmentSeriesMarkerTemplate を一度 null に戻してから元に戻すことで、バインディングを再評価させ、チャートの再描画後もラベルが正しく表示されるようにします。

private void XamDataChart_RefreshCompleted(object sender, EventArgs e)
{
    if (_isRefreshing) return;
    _isRefreshing = true;

    ResetMarkerTemplate(fragmentCoal);
    ResetMarkerTemplate(fragmentOil);
    // Gas, Nuclear, Hydro も同様にリセット

    Dispatcher.BeginInvoke(new Action(() => _isRefreshing = false),
        System.Windows.Threading.DispatcherPriority.DataBind);
}

private void ResetMarkerTemplate(StackedFragmentSeries fragment)
{
    var template = fragment.MarkerTemplate;
    fragment.MarkerTemplate = null;
    fragment.MarkerTemplate = template;
}

実行結果

上記の実装により、StackedBarSeries(水平)・StackedColumnSeries(垂直)のいずれの積み上げチャートでも、各フラグメントの矩形内にデータ値のラベルが表示され、ズームなどによるチャートの再描画後もラベルが正しく維持されます。

Tagged:

製品について

Ultimate UI for WPF