You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Openpyxl单系列折线图自动分配分类颜色的问题及解决方法

单系列折线图颜色异常问题排查与修复

我用openpyxl编写了一个基于工作表数据生成折线图的函数,其中secondary参数用于控制是否启用次坐标轴。当图表包含两个及以上数据系列时显示正常,但仅绘制单个系列时,折线的每个分段会被自动分配不同颜色,效果如下:

单系列折线图颜色异常
正常多系列折线图

问题代码

import openpyxl
from openpyxl import Workbook, load_workbook
from openpyxl.styles import Alignment, NamedStyle
from openpyxl.utils.dataframe import dataframe_to_rows
from openpyxl.utils import get_column_letter
from openpyxl.chart import LineChart, Reference
from openpyxl.chart.axis import ChartLines, TextAxis
from openpyxl.drawing.line import LineProperties
from openpyxl.drawing.text import ParagraphProperties, CharacterProperties
from openpyxl.chart.layout import Layout, ManualLayout
from openpyxl.chart.shapes import GraphicalProperties


def produce_line_chart(ws_data, charts_sheet, series_list, secondary=False):
    """
    Creates a customized line chart based on data from a worksheet and prepares it for placement on a charts sheet.

    Args:
        ws_data (Worksheet): The worksheet containing the data to be charted.
        charts_sheet (Worksheet): The worksheet where the chart will be placed.
        series_list (list): A list of strings where the first item is the chart title and subsequent items are the series names to be charted.
        secondary (bool): If True, disables major gridlines on both axes. Defaults to False.

    Returns:
        tuple: A tuple containing the created chart object and the suggested position for placing the chart on the charts sheet.
    """

    # Create a line chart
    chart = LineChart()

    # Set the title of the chart to the first item in series_list
    chart.title = series_list[0]

    # Increase the chart title's font size to 20pt
    chart.title.tx.rich.paragraphs[0].pPr = ParagraphProperties(
        defRPr=CharacterProperties(sz=2000)  # Font size 20pt
    )

    # Configure the appearance of the axes
    chart.x_axis = TextAxis(tickMarkSkip=4)
    chart.x_axis.spPr = GraphicalProperties(ln=LineProperties(solidFill="D9D9D9"))
    chart.y_axis.spPr = GraphicalProperties(ln=LineProperties(solidFill="D9D9D9"))

    # Add or remove major gridlines based on the 'secondary' flag
    if secondary:
        chart.x_axis.majorGridlines = None
        chart.y_axis.majorGridlines = None
    else:
        chart.x_axis.majorGridlines = ChartLines()
        chart.x_axis.majorGridlines.spPr = GraphicalProperties(
            ln=LineProperties(solidFill="D9D9D9")
        )
        chart.y_axis.majorGridlines = ChartLines()
        chart.y_axis.majorGridlines.spPr = GraphicalProperties(
            ln=LineProperties(solidFill="D9D9D9")
        )

    # Set tick marks on the axes
    chart.x_axis.majorTickMark = "out"
    chart.y_axis.majorTickMark = "cross"

    # Ensure axes are displayed
    chart.x_axis.delete = False
    chart.y_axis.delete = False

    # Set the legend position at the bottom of the chart
    chart.legend.position = "b"

    # Adjust the layout to ensure enough space for the legend
    chart.layout = Layout(
        manualLayout=ManualLayout(
            x=0,  # x position of the plot area
            y=0,  # y position of the plot area
            h=0.85,  # height of the plot area
            w=0.9,  # width of the plot area
        )
    )

    # Scale the chart by adjusting its height and width
    chart.height = chart.height * 1.71
    chart.width = chart.width * 2.03

    # Get the first row of the worksheet data to identify the series columns
    first_row = next(ws_data.iter_rows(min_row=1, max_row=1, values_only=True), [])

    # Loop through each series name in series_list (excluding the title)
    num_rows = ws_data.max_row
    for series in series_list[1:]:
        for idx, cell_value in enumerate(first_row, start=1):
            if cell_value == series:
                col = idx
                break
        # Create a reference to the data for the series and add it to the chart
        series_to_chart = Reference(
            ws_data, min_col=col, min_row=1, max_col=col, max_row=num_rows
        )
        chart.add_data(series_to_chart, titles_from_data=True)
        # Ensure the series line is not smoothed
        chart.series[-1].smooth = False

    # Set the categories (x-axis labels) based on the first column of data (excluding the header)
    categories = Reference(ws_data, min_col=1, min_row=2, max_row=ws_data.max_row)
    chart.set_categories(categories)

    # Determine the next available position for the chart on the charts sheet
    chart_row = 1  # Start in the first row
    for existing_chart in charts_sheet._charts:
        existing_chart_row = int("".join(filter(str.isdigit, existing_chart.anchor)))
        # Convert the chart height from cm to Excel cell height and add some space
        chart_row = max(chart_row, existing_chart_row + int(chart.height * 1.86) + 4)

    # Determine the position to add the chart on the charts sheet
    chart_position = f"A{chart_row}"

    return chart, chart_position

原因分析

问题出在openpyxl的LineChart默认启用了varyColors属性(值为True)。这个属性的逻辑是:

  • 当图表包含多个系列时,为每个系列分配不同颜色,符合正常需求
  • 当只有单个系列时,会自动给系列内的每个数据点/线段分配不同颜色,导致单系列折线出现“彩虹色”效果

修复方案

方案1:禁用逐点变色

在创建图表后,手动将varyColors设置为False,强制单系列使用统一颜色:

# 创建折线图
chart = LineChart()
# 添加这一行,禁用逐点变色
chart.varyColors = False

方案2:自定义系列颜色(可选)

如果需要指定单系列的具体颜色,可以在添加系列后进一步设置:

# 在循环添加系列的代码之后
if len(chart.series) == 1:
    series = chart.series[0]
    # 设置线条为红色,可替换为其他十六进制颜色码
    series.graphicalProperties.line.solidFill = "FF0000"

内容的提问来源于stack exchange,提问作者ensbana

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 19:24:53