python-pptx icon indicating copy to clipboard operation
python-pptx copied to clipboard

How to set manually chart_title position?

Open rileym99 opened this issue 1 year ago • 2 comments

Hello,

I've been using python-pptx 1.0.2 for several weeks now and find it very helpful. I've been able to do or find workarounds to everything I need to do except set manually the position of a chart title. I would like to align it to the left of the chart object.

I was trying to use something like the layout attribute that I found here: https://stackoverflow.com/questions/48325832/custom-legend-position-with-python-pptx but the chart_title element doesn't appear to have this property as I get the error: AttributeError: 'CT_Title' object has no attribute 'get_or_add_layout'.

Does anyone have any suggestions on a workaround?

Thanks, Matthew

rileym99 avatar Dec 06 '24 17:12 rileym99

@rileym99 the general gist is:

  • figure out if PowerPoint can do it and then how how it does it. This involves using the PowerPoint UI to do it and inspecting before and after XML.
  • Manipulate the XML in your document to produce the necessary elements and attributes using the best tools available.

If there is an existing property or method like .get_or_add_layout() that's of course the first choice. Where those are not available you'll need to use lower-level lxml methods to achieve the XML changes.

If you have a search around on "python-pptx workaround function" as a start you should be able to find ways folks have done that. You may want to dig into the internals that python-pptx itself uses to make methods like get_or_add_layout() like OxmlElement() and so on, many of which are in oxml/xmlchemy.py and nearby.

scanny avatar Dec 06 '24 17:12 scanny

Thank you for your help @scanny - I've been able to implement this. I've put some sample code below in case anybody else has this request.

from pptx.oxml.xmlchemy import OxmlElement

# start with an existing python-pptx chart object
chart_title = chart.chart_title
__set_chart_title_position(chart_title, 0, 0.025) # 0.025 seems to match the default position

def __set_chart_title_position(title, x, y):
    """
    Manually sets the chart title position

    :param title: a chart_title object
    :param x: value between 0 and 1 as a proportion of the chart width, from top left corner
    :param y: value between 0 and 1 as a proportion of the chart height, from top left corner
    """
    # Remove the existing layout
    cte = title._element
    cte = __remove_xml_element(cte, 'c:layout')

    # Inside layout, add manualLayout
    L = __add_xml_element(cte, 'c:layout')
    mL = __add_xml_element(L, 'c:manualLayout')

    # Add xMode and yMode and set vals to edge and x, y co-ordinates from top left corner
    xM = __add_xml_element(mL, 'c:xMode', val="edge")
    xY = __add_xml_element(mL, 'c:yMode', val="edge")
    xE = __add_xml_element(mL, 'c:x', val=str(x))
    yE = __add_xml_element(mL, 'c:y', val=str(y))

def __remove_xml_element(parent, tag_name, **kwargs):
    """
    Removes an xml element from a parent xml element

    :param parent: the parent element from which to remove the element
    :tag_name: the xml tag names, e.g. c:layout]

    Output: a reference to the parent element
    """
    tag = tag_name.split(":")[-1]
    for child in parent.getchildren():
        if tag in str(child.tag):
            parent.remove(child)

    return parent

def __add_xml_element(parent, tag_name, **kwargs):
    """
    Adds a new xml element to the presentation

    :param parent: the parent element to which to add the new element
    :tag_name: the xml tag names, e.g. c:layout

    Output: a reference to the newly created element
    """
    element = OxmlElement(tag_name)
    element.attrib.update(kwargs)
    parent.append(element)
    return element

rileym99 avatar Dec 09 '24 13:12 rileym99