Как изменить порядок категорий на гистограмме в PowerPoint с помощью Python-pptx? - PullRequest
2 голосов
/ 06 июня 2019

Я пытаюсь сгенерировать кластерную гистограмму, используя Python-pptx.Однако порядок появления категорий на графике противоположен порядку в таблице данных.

В PowerPoint отметьте «Категории в обратном порядке» в параметрах оси категорий, чтобы решить эту проблему.Я искал некоторое время, но не могу найти эквивалентное свойство в коде Python.Любая помощь или предложения высоко ценится.

Ответы [ 2 ]

2 голосов
/ 11 июня 2019

API пока не поддерживает эту функцию напрямую, как заметил @ Boosted_d16.Кажется, что это может быть выполнено довольно тривиально, используя функцию обхода.Во-первых, нам нужно выявить различия в базовом XML, а затем соответствующим образом манипулировать нашим выходным XML.

Вот соответствующая часть для диаграммы BAR_CLUSTERED по умолчанию из pptx, это относится к ееcategory_axis:

  <c:catAx>
    <c:axId val="-2068027336"/>
    <c:scaling>
      <c:orientation val="maxMin"/>
    </c:scaling>

Если мы изменим это вручную в приложении PowerPoint на Категории в обратном порядке , это будет выглядеть следующим образом:

  <c:catAx>
    <c:axId val="-2068027336"/>
    <c:scaling>
      <c:orientation val="minMax"/>
    </c:scaling>

Такединственное изменение относится к элементу /c:scaling/c:orientation[0], которому необходимо присвоить значение "minMax" вместо "maxMin".Мы можем сделать это, передав ссылку на ось вспомогательной функции, например:

def set_reverse_categories(axis):
    """
    workaround function that replicates the "Categories in Reverse Order" UI option in PPT
    """
    ele = axis._element.xpath(r'c:scaling/c:orientation')[0]
    ele.set("val", "maxMin")

Пример вывода

Диаграмма с обращенной осью категории слева, вывод по умолчаниюсправа.

enter image description here

Пример использования

Эта программа создаст презентацию с двумя слайдами на скриншоте выше.Обратите внимание, что вам может потребоваться изменить индекс компоновки.

from pptx import Presentation
from pptx.enum.chart import XL_CHART_TYPE
from pptx.chart.data import CategoryChartData
from pandas import DataFrame as DF
p = Presentation()
# Create some data to be used in the chart
series_names = ["A","B","C","D"]
cat_names = ["cat 1"]
data = {
        cat_names[0]: [.10, .20, .30, .40]
        }
df = DF(data, series_names, cat_names)
cd = CategoryChartData()
cd.categories = df.index
for name in df.columns:
    data = df[name]
    cd.add_series(name, data, '0%')

layout = p.slide_layouts[6] # MODIFY AS NEEDED, 6 is the index of my "Blank" slide template.

# Create two charts, one reversed and one not reversed on the Category Axis
for reverse in (True, False):
    slide = p.slides.add_slide( layout )
    shape = slide.shapes.add_chart(XL_CHART_TYPE.BAR_CLUSTERED, 0, 0, 9143301, 6158000, cd) 
    cht = shape.chart
    plot = cht.plots[0]
    plot.has_data_labels = False
    if reverse:
        set_reverse_categories(cht.category_axis)

p.save(r'c:\debug\ppt_chart.pptx')

ПРИМЕЧАНИЕ: Это также влияет на график визуально w / r / t "Crosses At" и горизонтальную ось / ось значенийтеперь появляется в верхней части графика.Вам нужно будет настроить это отдельно.API pptx не поддерживает это напрямую, но его также можно реализовать с помощью функции обхода:

def set_axis_crosses_at(cht, index, position_at):
    """
    cht: chart
    index: string 'value' or 'category' -- which axis to be adjusted
    position_at: 'max, 'autoZero', or int representing category index for Crosses At.
    """
    ns = "{http://schemas.openxmlformats.org/drawingml/2006/chart}"
    axes = {'value': cht.value_axis, 'category': cht.category_axis}
    axis = axes.get(index, None)
    if not axis: 
        return
        # probably should throw error here
    ax_ele = axis._element
    crosses = ax_ele.xpath(r'c:crosses')[0]
    scaling = ax_ele.xpath(r'c:scaling')[0]
    if position_at in ('max', 'autoZero'):
        crosses.set('val', f'{position_at}')
        return
    elif isinstance(position_at, int):
        ax_ele.remove(crosses)
        if len(ax_ele.xpath(r'c:auto')) > 0:
            ax_ele.remove(ax_ele.xpath(r'c:auto')[0])
        # crossesAt:
        if len(ax_ele.xpath(r'c:crossesAt')) == 0:
            crossesAt = etree.SubElement(ax_ele, f'{ns}crossesAt')
        else:
            crossesAt = ax_ele.xpath(r'c:crossesAt')[0]
        crossesAt.set('val', f'{position_at}')

Пример вывода:

enter image description here

1 голос
/ 11 июня 2019

Эта функция пока не поддерживается.

В репо на него есть билет: https://github.com/scanny/python-pptx/issues/517

...