在 .NET 中管理演示文稿的图表数据标签

简介

数据标签显示有关图表系列和各个数据点的信息,帮助读者识别数值并理解图表。本文解释如何格式化数值、显示百分比、读取标签文本、控制超出坐标轴最大值的标签、调整类别坐标轴标签间距,以及定位饼图标签。

设置图表数据标签的数据精度

使用 NumberFormatOfValues 来格式化系列值。此示例创建了一个带有默认数据的折线图,显示其数据表,并为第一个系列启用数值标签。格式 #,##0.00 显示千位分隔符和两位小数,而不更改底层数值。

using Aspose.Slides;
using Aspose.Slides.Charts;
using Aspose.Slides.Export;

using var presentation = new Presentation();
var slide = presentation.Slides[0];

var chart = slide.Shapes.AddChart(ChartType.Line, 50, 50, 450, 300);
chart.HasDataTable = true;

var series = chart.ChartData.Series[0];
series.NumberFormatOfValues = "#,##0.00";
series.Labels.DefaultDataLabelFormat.ShowValue = true;

presentation.Save("PrecisionOfDatalabels_out.pptx", SaveFormat.Pptx);

要格式化图表嵌入工作簿中的数值单元格,请参阅 Set Custom Number Formats for Workbook Cells。该示例还展示了数值标签遵循其源单元格格式,同时保留存储的数值。

以标签显示百分比

对于堆积柱形图,计算每个值相对于其类别总计的百分比,并将文本分配给 TextFrameForOverriding。此示例使用默认图表数据,并以 8 磅字体显示两位小数的百分比。总计为零的类别将被跳过,以避免除以零。如果图表数据更改,需要重新计算自定义标签文本。

using System;
using Aspose.Slides;
using Aspose.Slides.Charts;
using Aspose.Slides.Export;

using var presentation = new Presentation();
var slide = presentation.Slides[0];

var chart = slide.Shapes.AddChart(ChartType.StackedColumn, 20, 20, 400, 400);

var categoryTotals = new double[chart.ChartData.Categories.Count];
for (int k = 0; k < chart.ChartData.Categories.Count; k++)
{
    for (int i = 0; i < chart.ChartData.Series.Count; i++)
    {
        var series = chart.ChartData.Series[i];
        var pointValue = Convert.ToDouble(series.DataPoints[k].Value.Data);
        categoryTotals[k] += pointValue;
    }
}

for (int x = 0; x < chart.ChartData.Series.Count; x++)
{
    var series = chart.ChartData.Series[x];
    series.Labels.DefaultDataLabelFormat.ShowLegendKey = false;

    for (int j = 0; j < series.DataPoints.Count; j++)
    {
        var label = series.DataPoints[j].Label;
        if (categoryTotals[j] == 0)
        {
            continue;
        }

        var pointValue = Convert.ToDouble(series.DataPoints[j].Value.Data);
        var dataPointPercent = (pointValue / categoryTotals[j]) * 100;

        var portion = new Portion();
        portion.Text = string.Format("{0:F2} %", dataPointPercent);
        portion.PortionFormat.FontHeight = 8f;

        label.TextFrameForOverriding.Text = "";

        var paragraph = label.TextFrameForOverriding.Paragraphs[0];
        paragraph.Portions.Add(portion);

        label.DataLabelFormat.ShowValue = true;
        label.DataLabelFormat.ShowSeriesName = false;
        label.DataLabelFormat.ShowPercentage = false;
        label.DataLabelFormat.ShowLegendKey = false;
        label.DataLabelFormat.ShowCategoryName = false;
        label.DataLabelFormat.ShowBubbleSize = false;
    }
}

presentation.Save("DisplayPercentageAsLabels_out.pptx", SaveFormat.Pptx);

在图表数据标签中设置百分号

当数值以分数形式存储时,使用 NumberFormat 来显示百分比。将 IsNumberFormatLinkedToSource 设置为 false,以使标签格式独立于源单元格。

此示例创建了一个 100% 堆积柱形图,其中红色和蓝色系列跨四个类别。每对数值相加为 1。标签格式 0.0% 将 0.30 显示为 30.0%,而垂直坐标轴使用两位小数。两个系列均使用白色、10 磅的标签文本。

using System.Drawing;
using Aspose.Slides;
using Aspose.Slides.Charts;
using Aspose.Slides.Export;

using var presentation = new Presentation();
var slide = presentation.Slides[0];

var chart = slide.Shapes.AddChart(ChartType.PercentsStackedColumn, 20, 20, 500, 400);

chart.Axes.VerticalAxis.IsNumberFormatLinkedToSource = false;
chart.Axes.VerticalAxis.NumberFormat = "0.00%";

chart.ChartData.Series.Clear();
chart.ChartData.Categories.Clear();

var workbook = chart.ChartData.ChartDataWorkbook;
int worksheetIndex = 0;
for (int i = 0; i < 4; i++)
{
    var categoryCell = workbook.GetCell(worksheetIndex, i + 1, 0, $"Category {i + 1}");
    chart.ChartData.Categories.Add(categoryCell);
}

string[] seriesNames = { "Reds", "Blues" };
Color[] seriesColors = { Color.Red, Color.Blue };
double[,] values = { { 0.30, 0.50, 0.80, 0.65 }, { 0.70, 0.50, 0.20, 0.35 } };

for (int i = 0; i < seriesNames.Length; i++)
{
    var seriesCell = workbook.GetCell(worksheetIndex, 0, i + 1, seriesNames[i]);
    var series = chart.ChartData.Series.Add(seriesCell, chart.Type);
    for (int j = 0; j < 4; j++)
    {
        var valueCell = workbook.GetCell(worksheetIndex, j + 1, i + 1, values[i, j]);
        series.DataPoints.AddDataPointForBarSeries(valueCell);
    }

    series.Format.Fill.FillType = FillType.Solid;
    series.Format.Fill.SolidFillColor.Color = seriesColors[i];

    var labelFormat = series.Labels.DefaultDataLabelFormat;
    labelFormat.ShowValue = true;
    labelFormat.IsNumberFormatLinkedToSource = false;
    labelFormat.NumberFormat = "0.0%";
    labelFormat.TextFormat.PortionFormat.FontHeight = 10;
    labelFormat.TextFormat.PortionFormat.FillFormat.FillType = FillType.Solid;
    labelFormat.TextFormat.PortionFormat.FillFormat.SolidFillColor.Color = Color.White;
}

presentation.Save("SetDataLabelsPercentageSign_out.pptx", SaveFormat.Pptx);

读取数据标签的实际文本

使用 GetActualLabelText 来检索数据标签设置生成的文本。这在提取报告标签、搜索演示文稿内容或验证生成的图表时非常有用。在下面的示例中,默认的 data label format 将每个类别名称、系列名称和数值组合在一起。一个数据点将其数值格式化为百分比,另一个使用来自 TextFrameForOverriding 的自定义文本。

using System;
using Aspose.Slides;
using Aspose.Slides.Charts;

using var presentation = new Presentation();
var slide = presentation.Slides[0];

var chart = slide.Shapes.AddChart(ChartType.ClusteredColumn, 20, 20, 500, 300);

chart.ChartData.Series.Clear();
chart.ChartData.Categories.Clear();

var workbook = chart.ChartData.ChartDataWorkbook;
chart.ChartData.Categories.Add(workbook.GetCell(0, 1, 0, "Q1"));
chart.ChartData.Categories.Add(workbook.GetCell(0, 2, 0, "Q2"));

var north = chart.ChartData.Series.Add(workbook.GetCell(0, 0, 1, "North"), chart.Type);
north.DataPoints.AddDataPointForBarSeries(workbook.GetCell(0, 1, 1, 0.25));
north.DataPoints.AddDataPointForBarSeries(workbook.GetCell(0, 2, 1, 0.75));

var south = chart.ChartData.Series.Add(workbook.GetCell(0, 0, 2, "South"), chart.Type);
south.DataPoints.AddDataPointForBarSeries(workbook.GetCell(0, 1, 2, 0.40));
south.DataPoints.AddDataPointForBarSeries(workbook.GetCell(0, 2, 2, 0.60));

foreach (var series in chart.ChartData.Series)
{
    var format = series.Labels.DefaultDataLabelFormat;
    format.ShowCategoryName = true;
    format.ShowSeriesName = true;
    format.ShowValue = true;
}

north.Labels[1].DataLabelFormat.IsNumberFormatLinkedToSource = false;
north.Labels[1].DataLabelFormat.NumberFormat = "0%";
south.Labels[0].TextFrameForOverriding.Text = "Reviewed";

foreach (var series in chart.ChartData.Series)
{
    foreach (var point in series.DataPoints)
    {
        var label = point.Label;
        if (!label.IsVisible)
        {
            continue;
        }

        Console.WriteLine($"Value: {point.Value.Data}; label: {label.GetActualLabelText()}");
    }
}

数据点中存储的数值仍为 0.75,即使其标签显示为 75% 并附带类别和系列名称。自定义文本会替代生成的标签文本。GetActualLabelText 在两种情况下均返回相应的标签字符串。如上所示,当只想提取可见标签时,需要单独检查 IsVisible。

控制超出坐标轴最大值的数据标签

当手动限制坐标轴范围时,某些数据点可能超出其最大值。使用 ShowDataLabelsOverMaximum 来控制是否显示这些数据标签。此设置仅改变标签可见性,不会更改坐标轴范围或底层数据值。

下面的示例创建了一个值为 60 和 120 的 2D 集群柱形图。它在垂直坐标轴上将 IsAutomaticMaxValue 设置为 false,并将 MaxValue 设置为 100。第一张幻灯片允许超出最大值的标签;该幻灯片的副本禁用这些标签。两张幻灯片均保存为 DataLabelsOverMaximum.pptx。

使用 ShowValue 启用数值标签。图表级别的设置本身不会启用数值显示,也不会覆盖单个标签的禁用数值显示。此示例为整个系列启用数值,并使用 Position 将标签放置在每根柱形的外侧端点。

using Aspose.Slides;
using Aspose.Slides.Charts;
using Aspose.Slides.Export;

using var presentation = new Presentation();
var slide = presentation.Slides[0];

var chart = slide.Shapes.AddChart(ChartType.ClusteredColumn, 50, 50, 600, 400);
chart.HasLegend = false;

chart.ChartData.Series.Clear();
chart.ChartData.Categories.Clear();

var workbook = chart.ChartData.ChartDataWorkbook;

var firstCategory = workbook.GetCell(0, 1, 0, "Within range");
var secondCategory = workbook.GetCell(0, 2, 0, "Above maximum");

chart.ChartData.Categories.Add(firstCategory);
chart.ChartData.Categories.Add(secondCategory);

var seriesName = workbook.GetCell(0, 0, 1, "Values");
var series = chart.ChartData.Series.Add(seriesName, chart.Type);

var firstValue = workbook.GetCell(0, 1, 1, 60);
var secondValue = workbook.GetCell(0, 2, 1, 120);

series.DataPoints.AddDataPointForBarSeries(firstValue);
series.DataPoints.AddDataPointForBarSeries(secondValue);

series.Labels.DefaultDataLabelFormat.ShowValue = true;
series.Labels.DefaultDataLabelFormat.Position = LegendDataLabelPosition.OutsideEnd;

chart.Axes.VerticalAxis.IsAutomaticMaxValue = false;
chart.Axes.VerticalAxis.MaxValue = 100;
chart.ShowDataLabelsOverMaximum = true;

var secondSlide = presentation.Slides.AddClone(slide);
var secondChart = (IChart)secondSlide.Shapes[0];
secondChart.ShowDataLabelsOverMaximum = false;

presentation.Save("DataLabelsOverMaximum.pptx", SaveFormat.Pptx);

以下图像显示了 Microsoft PowerPoint 渲染的已保存幻灯片。true 时,标签 120 显示在上边界;false 时,该标签被隐藏。标签 60 保持可见,坐标轴最大值保持在 100,第二个数据点在两种情况下均为 120。

ShowDataLabelsOverMaximum = true ShowDataLabelsOverMaximum = false
PowerPoint 图表显示值标签 120,坐标轴最大值为 100 PowerPoint 图表隐藏值标签 120,坐标轴最大值为 100

设置标签与坐标轴的距离

使用 LabelOffset 来控制类别坐标轴标签与坐标轴之间的距离。该数值是坐标轴标签最大字体大小的百分比。此示例创建了一个集群柱形图,并将水平坐标轴标签偏移设置为 500。此设置影响类别坐标轴标签,而不是附加到各个数据点的标签。

using Aspose.Slides;
using Aspose.Slides.Charts;
using Aspose.Slides.Export;

using var presentation = new Presentation();
var slide = presentation.Slides[0];

var chart = slide.Shapes.AddChart(ChartType.ClusteredColumn, 20, 20, 500, 300);
chart.Axes.HorizontalAxis.LabelOffset = 500;

presentation.Save("SetCategoryAxisLabelDistance_out.pptx", SaveFormat.Pptx);

调整标签位置

在饼图上,调整数据标签位置以改善间距并为引导线留出空间。

此示例显示第一个数据点的数值,将其标签放置在切片外部,并调整其 X 和 Y 偏移。这些偏移分别相对于图表的宽度和高度。

using Aspose.Slides;
using Aspose.Slides.Charts;
using Aspose.Slides.Export;

using var presentation = new Presentation();
var slide = presentation.Slides[0];

var chart = slide.Shapes.AddChart(ChartType.Pie, 50, 50, 200, 200);
var series = chart.ChartData.Series;

var label = series[0].Labels[0];
label.DataLabelFormat.ShowValue = true;
label.DataLabelFormat.Position = LegendDataLabelPosition.OutsideEnd;
label.X = 0.71f;
label.Y = 0.04f;

presentation.Save("presentation.pptx", SaveFormat.Pptx);

调整后数据标签位置的饼图

格式化引导线

当饼图标签位于切片外部时,引导线帮助读者将每个数值与相应切片对应。对比色和略微加粗的线条可以使这些连接更易于辨认,而透明度和虚线样式则保持线条的视觉不突兀。

使用 LeaderLinesFormat 在系列的数据标签集合层级格式化引导线。series.Labels.LeaderLinesFormat.Line 中的设置适用于整个集合,而不是单个标签。

此示例创建了一个默认数据的饼图,在切片外部显示数值,并启用 ShowLeaderLines。它使用 X 和 Y 偏移,将两个标签进一步远离其切片,偏移以图表宽度和高度的比例表示。无需输入演示文稿。

该线条的 FillFormat 设置为实心蓝色,透明度值为 191(在 255 中),约为 25% 透明度。Width 将粗细设为 2 磅,且 DashStyle 将线条设为虚线。

using System.Drawing;
using Aspose.Slides;
using Aspose.Slides.Charts;
using Aspose.Slides.Export;

using var presentation = new Presentation();
var slide = presentation.Slides[0];

var chart = slide.Shapes.AddChart(ChartType.Pie, 80, 50, 560, 400);
chart.HasLegend = false;

var series = chart.ChartData.Series[0];

var labelFormat = series.Labels.DefaultDataLabelFormat;
labelFormat.ShowValue = true;
labelFormat.Position = LegendDataLabelPosition.OutsideEnd;
labelFormat.ShowLeaderLines = true;

// 将两个标签从它们的切片中向外移动得更远。
series.Labels[0].X = 0.10f;
series.Labels[0].Y = -0.05f;
series.Labels[1].X = -0.10f;
series.Labels[1].Y = 0.05f;

var line = series.Labels.LeaderLinesFormat.Line;
line.FillFormat.FillType = FillType.Solid;
line.FillFormat.SolidFillColor.Color = Color.FromArgb(191, 30, 90, 160);
line.Width = 2;
line.DashStyle = LineDashStyle.Dash;

presentation.Save("FormattedLeaderLines.pptx", SaveFormat.Pptx);

启用引导线并不保证每个标签都会出现线条。可见性取决于标签位置和图表的布局行为。在此示例中,两个移位的标签具有可见的引导线;靠近切片的标签则不需要引导线。

以下图像显示了使用 Aspose.Slides 渲染的已保存 FormattedLeaderLines.pptx:

带有蓝色、半透明虚线引导线的饼图,连接两个外部标签与其切片

在柱形图上方添加多行数据标签

此示例创建了一个柱形图,在绘图区上方添加了两行数据标签。Series A 显示可见的柱形,而 Series B 和 Series C 提供额外的标签。通过移除填充和轮廓来隐藏它们的柱形。Overlap 属性使所有三个系列在相同的类别中心对齐。

PlotArea 设置为标签行预留空间。调用 ValidateChartLayout 计算默认位置后,使用 X 和 Y 保持水平对齐并应用垂直偏移,将标签排列成两行。数值仍然是链接到系列数值的数据标签;只有行标题是独立的文本形状。

using System.Drawing;
using Aspose.Slides;
using Aspose.Slides.Charts;
using Aspose.Slides.Export;

using var presentation = new Presentation();
var slide = presentation.Slides[0];

var chart = slide.Shapes.AddChart(ChartType.ClusteredColumn, 40, 40, 640, 200);
chart.HasTitle = false;
chart.HasLegend = false;
chart.TextFormat.PortionFormat.FontHeight = 12;

chart.ChartData.Series.Clear();
chart.ChartData.Categories.Clear();

var workbook = chart.ChartData.ChartDataWorkbook;
workbook.Clear(0);

string[] categories = { "North", "South", "East", "West" };
string[] seriesNames = { "Series A", "Series B", "Series C" };
double[,] seriesValues = {
    { 35, 42, 28, 47 },
    { 22, 31, 19, 26 },
    { 12, 16, 14, 18 }
};

for (int categoryIndex = 0; categoryIndex < categories.Length; categoryIndex++)
{
    var categoryCell = workbook.GetCell(0, categoryIndex + 1, 0, categories[categoryIndex]);
    chart.ChartData.Categories.Add(categoryCell);
}

for (int seriesIndex = 0; seriesIndex < seriesNames.Length; seriesIndex++)
{
    var seriesCell = workbook.GetCell(0, 0, seriesIndex + 1, seriesNames[seriesIndex]);
    var series = chart.ChartData.Series.Add(seriesCell, ChartType.ClusteredColumn);

    for (int categoryIndex = 0; categoryIndex < categories.Length; categoryIndex++)
    {
        var valueCell = workbook.GetCell(0, categoryIndex + 1, seriesIndex + 1, seriesValues[seriesIndex, categoryIndex]);
        series.DataPoints.AddDataPointForBarSeries(valueCell);
    }

    if (seriesIndex > 0)
    {
        // 隐藏 B 和 C 的柱形,但保留它们的数据标签。
        series.Format.Fill.FillType = FillType.NoFill;
        series.Format.Line.FillFormat.FillType = FillType.NoFill;
        var labelFormat = series.Labels.DefaultDataLabelFormat;
        labelFormat.ShowValue = true;
        labelFormat.TextFormat.PortionFormat.FontHeight = 12;
        var labelFill = labelFormat.TextFormat.PortionFormat.FillFormat;
        labelFill.FillType = FillType.Solid;
        labelFill.SolidFillColor.Color = Color.Black;
        labelFormat.Position = LegendDataLabelPosition.InsideBase;
    }
}

// 使所有三个系列在相同的类别中心对齐。
chart.ChartData.Series[0].ParentSeriesGroup.Overlap = 100;

// 在此紧凑示例中使用更少的网格线。
chart.Axes.VerticalAxis.IsAutomaticMajorUnit = false;
chart.Axes.VerticalAxis.MajorUnit = 10;

// 在绘图区上方预留空间,以容纳两行数据标签。
chart.PlotArea.LayoutTargetType = LayoutTargetType.Inner;
chart.PlotArea.X = 0.15f;
chart.PlotArea.Y = 0.32f;
chart.PlotArea.Width = 0.80f;
chart.PlotArea.Height = 0.48f;
chart.ValidateChartLayout();

for (int seriesIndex = 1; seriesIndex < seriesNames.Length; seriesIndex++)
{
    var series = chart.ChartData.Series[seriesIndex];
    float rowTop = seriesIndex == 1 ? 0.15f : 0.03f;

    for (int categoryIndex = 0; categoryIndex < categories.Length; categoryIndex++)
    {
        var dataLabel = series.DataPoints[categoryIndex].Label;
        // 保持默认的水平位置。Y 是相对于
        // 默认标签位置的偏移量,以图表高度的比例表示。
        dataLabel.X = 0;
        dataLabel.Y = rowTop - dataLabel.ActualY / chart.Height;
    }

    // 仅行标题是单独的文本形状。
    var rowHeading = slide.Shapes.AddAutoShape(ShapeType.Rectangle, chart.X, chart.Y + rowTop * chart.Height, 85, 18);
    rowHeading.FillFormat.FillType = FillType.NoFill;
    rowHeading.LineFormat.FillFormat.FillType = FillType.NoFill;
    rowHeading.AddTextFrame(seriesNames[seriesIndex]);
    rowHeading.TextFrame.TextFrameFormat.MarginTop = 0;
    rowHeading.TextFrame.TextFrameFormat.MarginBottom = 0;
    var headingFormat = rowHeading.TextFrame.Paragraphs[0].Portions[0].PortionFormat;
    headingFormat.FontHeight = 12;
    headingFormat.FillFormat.FillType = FillType.Solid;
    headingFormat.FillFormat.SolidFillColor.Color = Color.Black;
}

presentation.Save("multiple-rows-of-labels.pptx", SaveFormat.Pptx);

绘图区上方有两行数据标签的柱形图

常见问题

如何防止在密集图表上数据标签重叠?
结合自动标签布局、引导线和缩小字体大小;必要时,隐藏某些字段(例如类别),或仅对极值或关键点显示标签。

如何仅对零、负数或空值禁用标签?
在启用标签之前过滤数据点,并依据定义的规则关闭对值为 0、负数或缺失值的显示。

在导出为 PDF/图片时,如何确保标签样式一致?
显式设置字体族和大小,并确认渲染环境中存在该字体,以避免回退。