Search code examples
pythonpowerpointslidewin32compython-pptx

python ppt find and replace within a chart


I already referred these posts here here, here and here. Please don't mark it as a duplicate.

I have a chart embedded inside the ppt like below

enter image description here

I wish to replace the axis headers from FY2021 HC to FY1918 HC. Similarly, FY2122 HC should be replaced with FY1718 HC.

How can I do this using python pptx? This chart is coming from embedded Excel though. Is there anyway to change it in ppt?

When I tried the below, it doesn't get the axis headers

text_runs = []
for shape in slide.shapes:
    if not shape.has_text_frame:
        continue
    for paragraph in shape.text_frame.paragraphs:
        for run in paragraph.runs:
            text_runs.append(run.text)

when I did the below, I find the list of shape types from the specific slide. I wish to change only the chart headers. So, the screenshot shows only two charts that I have in my slide.

for slide in ip_ppt.slides:
    for shape in slide.shapes:
        print("id: %s, type: %s" % (shape.shape_id, shape.shape_type))

id: 24, type: TEXT_BOX (17)
id: 10242, type: TEXT_BOX (17)
id: 11306, type: TEXT_BOX (17)
id: 11, type: AUTO_SHAPE (1)
id: 5, type: TABLE (19)
id: 7, type: TABLE (19)
id: 19, type: AUTO_SHAPE (1)
id: 13, type: CHART (3)
id: 14, type: CHART (3)

When I try to access the shape using id, I am unable to as well

ip_ppt.slides[5].shapes[13].Chart

I also tried the code below

from pptx import chart
from pptx.chart.data import CategoryChartData
chart_data = CategoryChartData()
chart.datalabel = ['FY1918 HC', 'FY1718 HC']

Am new to python and pptx. Any solution on how to edit the embedded charts headers would really be useful. Help please


Solution

  • You can get to the category labels the following way:

    from pptx import Presentation
    from pptx.shapes.graphfrm import GraphicFrame
    
    prs = Presentation('chart-01.pptx')
    
    for slide in prs.slides:
        for shape in slide.shapes:
            print("slide: %s, id: %s, index: %s, type: %s" % (slide.slide_id, shape.shape_id, slide.shapes.index(shape), shape.shape_type))
            if isinstance(shape, GraphicFrame) and shape.has_chart:
                plotIndex = 0
                for plot in shape.chart.plots:
                    catIndex = 0
                    for cat in plot.categories:
                        print("  plot %s, category %s, category label: %s" % (plotIndex, catIndex, cat.label))
                        catIndex += 1
                    plotIndex += 1
    

    which will put out something like that:

    slide: 256, id: 2, index: 0, type: PLACEHOLDER (14)
    slide: 256, id: 3, index: 1, type: CHART (3)
      plot 0, category 0, category label: East
      plot 0, category 1, category label: West
      plot 0, category 2, category label: Midwest
    

    Unfortunately you can not change the category label, because it is stored in the embedded Excel. The only way to change those is to replace the chart data by using the chart.replace_data() method.

    Recreating the ChartData object you need for the call to replace_data based on the existing chart is a bit more involved, but here is my go at it based on a chart that I created with the following code:

    from pptx import Presentation
    from pptx.chart.data import CategoryChartData
    from pptx.enum.chart import XL_CHART_TYPE,XL_LABEL_POSITION
    from pptx.util import Inches, Pt
    from pptx.dml.color import RGBColor
    
    # create presentation with 1 slide ------
    prs = Presentation()
    slide = prs.slides.add_slide(prs.slide_layouts[5])
    
    # define chart data ---------------------
    chart_data = CategoryChartData()
    chart_data.categories = ['FY2021 HC', 'FY2122 HC']
    chart_data.add_series('blue', (34.5, 31.5))
    chart_data.add_series('orange', (74.1, 77.8))
    chart_data.add_series('grey', (56.3, 57.3))
    
    # add chart to slide --------------------
    x, y, cx, cy = Inches(2), Inches(2), Inches(6), Inches(4.5)
    gframe = slide.shapes.add_chart(
        XL_CHART_TYPE.COLUMN_STACKED, x, y, cx, cy, chart_data
    )
    chart = gframe.chart
    plot = chart.plots[0]
    plot.has_data_labels = True
    data_labels = plot.data_labels
    data_labels.font.size = Pt(13)
    data_labels.font.color.rgb = RGBColor(0x0A, 0x42, 0x80)
    data_labels.position = XL_LABEL_POSITION.INSIDE_END
    
    prs.save('chart-01.pptx')
    

    and that looks almost identical to your picture in the question:

    The view inside PowerPoint of the file chart-01.pptx

    The following code will change the category labels in that chart:

    from pptx import Presentation
    from pptx.chart.data import CategoryChartData
    from pptx.shapes.graphfrm import GraphicFrame
    from pptx.enum.chart import XL_CHART_TYPE
    from pptx.util import Inches
    
    # read presentation from file
    prs = Presentation('chart-01.pptx')
    
    # find the first chart object in the presentation
    slideIdx = 0
    for slide in prs.slides:
        for shape in slide.shapes:
            if shape.has_chart:
                chart = shape.chart
                print("Chart of type %s found in slide[%s, id=%s] shape[%s, id=%s, type=%s]"
                        % (chart.chart_type, slideIdx, slide.slide_id,
                           slide.shapes.index(shape), shape.shape_id, shape.shape_type ))
                break
        slideIdx += 1
    
    # create list with changed category names
    categorie_map = { 'FY2021 HC': 'FY1918 HC', 'FY2122 HC': 'FY1718 HC' }
    new_categories = list(categorie_map[c] for c in chart.plots[0].categories)
    
    # build new chart data with new category names and old data values
    new_chart_data = CategoryChartData()
    new_chart_data.categories = new_categories
    for series in chart.series:
        new_chart_data.add_series(series.name,series.values)
    
    # write the new chart data to the chart
    chart.replace_data(new_chart_data)
    
    # save everything in a new file
    prs.save('chart-02.pptx')
    

    The comments should explain what is going on and if you open chart-02.pptx with PowerPoint, this is what you will see:

    The view inside PowerPoint of the file chart-02.pptx

    Hope that solves your problem!