Skip to content

Commit

Permalink
Merge pull request #1230 from Quantipy/i1225_import_fails
Browse files Browse the repository at this point in the history
I1225 import fails
  • Loading branch information
AndersFreund authored Feb 13, 2019
2 parents 796da38 + 80aa678 commit a19c73e
Show file tree
Hide file tree
Showing 2 changed files with 180 additions and 86 deletions.
8 changes: 7 additions & 1 deletion quantipy/sandbox/pptx/PptxChainClass.py
Original file line number Diff line number Diff line change
Expand Up @@ -653,7 +653,13 @@ def sig_test(self):
# Get the sig testing
sig_df = self.prepare_dataframe()
sig_df = sig_df.get_propstest()
self._sig_test = sig_df.df.values.tolist()
_sig_test = sig_df.df.values.tolist()

# Assume that all items in the list of sig tests has same length
check_list = map(lambda x: len(x), _sig_test)
assert check_list.count(check_list[0]) == len(check_list), 'List of sig test results is not uniform'

self._sig_test = [zip(*_sig_test)[i] for i in range(len(_sig_test[0]))]
return self._sig_test

@property
Expand Down
258 changes: 173 additions & 85 deletions quantipy/sandbox/pptx/PptxPainterClass.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import re
from lxml import etree
import warnings

# Imports from Python-PPTX
from pptx import Presentation
Expand Down Expand Up @@ -30,11 +31,11 @@
)

from PptxDefaultsClass import PptxDefaults
from PptxChainClass import float2String
import pandas as pd
import copy


# chartdata_from_dataframe taken from topy.core.pandas_pptx.py
def chartdata_from_dataframe(df, number_format="0%", xl_number_format='0.00%'):
"""
Return a CategoryChartData instance from the given Pandas DataFrame.
Expand Down Expand Up @@ -97,7 +98,6 @@ def get_parent(sub_categories, line, pos):
return cd


# return_slide_layout_by_name is taken from quantipy.core.builds.powerpoint.visual_editor.py
def return_slide_layout_by_name(pptx, slide_layout_name):
"""
Loop over the slide layout object and find slide layout by name, return slide layout
Expand All @@ -116,6 +116,18 @@ def return_slide_layout_by_name(pptx, slide_layout_name):
sld_layout=slide_layout_name))


def convertable(obj, func):
"""
Returns True if obj can be converted by func without an error.
"""

try:
func(obj)
return True
except ValueError:
return False


class PptxPainter(object):
"""
A convenience wrapper around the python-pptx library
Expand Down Expand Up @@ -169,6 +181,121 @@ def __init__(self, path_to_presentation, slide_layout=None, shape_properties=Non
'side_tables': {},
}

@staticmethod
def get_plot_values(plot):
"""
Return a list of dicts with serie name as dict-key and serie values as dict-value
Parameters
----------
plot: pptx.chart.plot._BasePlot
Returns
-------
list
"""
series = [
{series.name: [str(s) for s in series.values]}
for series in plot.series
]

return series

def show_data_labels(self, plot, decimals=0):
"""
Explicitly sets datalabels to allow for datalabel editing.
Parameters
----------
plot: pptx.chart.plot._BasePlot
The plot object for which datalabels need should be shown.
decimals: the number of decimals to show
Returns
-------
None
"""

# Get number format and font from data labels
data_labels = plot.data_labels
number_format = data_labels.number_format # '0%'
font = data_labels.font

plot_values = self.get_plot_values(plot)
for s, series in enumerate(plot_values):
values = [
value
for value in series.values()[0]
if convertable(value, float)
]
for v, value in enumerate(values):
if value is not None:
if number_format == '0%':
value = round(float(value) * 100, decimals)

str_value = float2String(value) + '%'
else:
str_value = str(value)
else:
str_value = ""
point = plot.series[s].points[v]
data_label = point.data_label
frame = data_label.text_frame
frame.text = str_value
pgraph = frame.paragraphs[0]
for run in pgraph.runs:
run.font.bold = font.bold
# run.font.color.rgb = font.color.rgb
# run.font.fill.fore_color.rgb = font.fill.fore_color.rgb

run.font.italic = font.italic
run.font.name = font.name
run.font.size = font.size
run.font.underline = font.underline

def edit_datalabel(self, plot, series, point, text, prepend=False, append=False, rgb=None):
"""
Add/append data label text.
Parameters
----------
plot: pptx.chart.plot._BasePlot
An instance of a Chart object.
serie: int
The serie where the data label should be edited
chart.series[serie]
point: int
The point where the data label should be edited
chart.series[serie].points[point]
text: basestring
The text to add/append to data label
prepend: bool
Set to True to prepend text to existing data label
append: bool
Set to True to append text to existing data label
rgb: tuple
Tuple with three ints defining each RGB color
Returns
-------
None
"""
data_label = plot.series[series].points[point].data_label
frame = data_label.text_frame

run = frame.paragraphs[0].runs[0]
original_text = frame.text
if prepend:
run.text = u'{}{}'.format(text, original_text)
elif append:
run.text = u'{}{}'.format(original_text, text)
else:
run.text = text
if rgb is not None:
run.font.color.rgb = RGBColor(*rgb)

def queue_slide_items(self, pptx_chain, slide_items):
"""
Helper function to queue a full automated slide.
Expand Down Expand Up @@ -241,10 +368,28 @@ def queue_slide_items(self, pptx_chain, slide_items):
side_table_draft['values_suffix_columns'] = pct_index
self.queue_side_table(settings=side_table_draft)
if slide_item.startswith('chart'):
sig_test = False
cell_items = slide_item.split(':')[1]

'''
Makes no sense to actually have 'test' as a cell_item.
Will remove it from cell_items and set flag sig_test as True
'''
cell_items = cell_items.split(',')
if 'test' in cell_items:
sig_test = True
pptx_chain.add_test_letter_to_column_labels()
pptx_chain.chart_df = pptx_chain.prepare_dataframe()
cell_items.remove('test')
cell_items = ','.join(cell_items)

pptx_frame = pptx_chain.chart_df.get(cell_items)
if not pptx_frame().empty:
chart_draft = self.draft_autochart(pptx_frame(), pptx_chain.chart_type)
if sig_test:
chart_draft['sig_test_visible'] = True
chart_draft['sig_test_results'] = pptx_chain.sig_test

self.queue_chart(settings=chart_draft)

self._check_shapes()
Expand Down Expand Up @@ -879,7 +1024,11 @@ def add_chart(self, slide,

# Number format
number_format='0.00%',
xl_number_format='0.00%'
xl_number_format='0.00%',

# Sig test
sig_test_visible = False,
sig_test_results = None,
):
"""
Adds a chart to the given slide and sets all properties for the chart
Expand Down Expand Up @@ -1044,6 +1193,27 @@ def add_chart(self, slide,
data_labels.number_format = data_labels_num_format
data_labels.number_format_is_linked = data_labels_num_format_is_linked

if not sig_test_results: sig_test_visible = False
if len(dataframe.columns) == 1: sig_test_visible = False
if sig_test_visible:
self.show_data_labels(plot, decimals=0)
for serie, column in enumerate(sig_test_results[::-1]):
for point, test_result in enumerate(column[::-1]):
if not isinstance(test_result, basestring): continue
for text in ['*.',
'*',
'**.',
'**',
'\'@L\'.',
'\'@L\'',
'\'@H\'.',
'\'@H\'',
]:
test_result = test_result.replace(text,'')
if test_result == '': continue
text = u' ({})'.format(test_result)
self.edit_datalabel(plot, serie, point, text, prepend=False, append=True)

# # ================================ series
# for i, ser in enumerate(dataframe.columns):
# ser = plot.series[i]
Expand Down Expand Up @@ -1416,85 +1586,3 @@ def add_net(slide,
#paragraph.line_spacing = Pt(6)
cell.text = str(subval)

@staticmethod
def edit_datalabel(chart, series, point, text, prepend=False, append=False,
position=None, rgb=None):
"""
Add/append data label text.
"""
data_label = chart.series[series].points[point].data_label
frame = data_label.text_frame
if prepend:
original_text = frame.text
frame.text = text
pgraph = frame.add_paragraph()
pgraph.text = original_text
run = frame.paragraphs[0].runs[0]
elif append:
pgraph = frame.add_paragraph()
pgraph.text = text
run = frame.paragraphs[-1].runs[0]
else:
frame.text = text
run = frame.paragraphs[0].runs[0]
if rgb is not None:
run.font.color.rgb = RGBColor(*rgb)
if position is not None:
data_label.position = data_label_pos_dct(position)

def show_data_labels(self, chart, position=None):
"""
Explicitly sets datalabels to allow for datalabel editing.
Parameters
----------
chart : pptx.chart.chart.Chart
The chart object for which datalabels need should be shown.
position : str, default=None
The position, relative to the data point, that the datalabel
should be appear in. Must be one of the following, or None:
'above', 'below', 'best', 'center', 'inside_base',
'inside_end', 'left', 'mixed', 'outside_end', 'right'
If None then the position already set for the chart will
be used.
Returns
-------
None
"""

chart_values = self.get_chart_values(chart)
for s, series in enumerate(chart_values):
values = [
value
for value in series.values()[0]
if self.convertable(value, float)
]
for v, value in enumerate(values):
point = chart.series[s].points[v]
frame = point.data_label.text_frame
frame.text = "" if value is None else str(value)
if position is not None:
point.data_label.position = data_label_pos_dct(position)

@staticmethod
def get_chart_values(chart):

series = [
{series.name: [str(s) for s in series.values]}
for series in chart.series
]

return series

@staticmethod
def convertable(obj, func):
"""
Returns True if obj can be convertedby func without an error.
"""

try:
func(obj)
return True
except ValueError:
return False

0 comments on commit a19c73e

Please sign in to comment.