Commit 7f3596af authored by Sanjay Yadav's avatar Sanjay Yadav

ENH: Table cell gradients.

parent ab41801f
Pipeline #31153 failed with stage
in 52 seconds
......@@ -221,9 +221,9 @@ def add_text_to_shape(shape, text, font_size, txt_fill):
run.font.color.rgb = RGBColor.from_string(txt_fill)
def scale_data(data, lo, hi, factor):
def scale_data(data, lo, hi, factor=None):
"""Function to scale data."""
return ((data - lo) / (hi - lo)) * factor
return ((data - lo) / (hi - lo)) * factor if factor else (data - lo) / (hi - lo)
def rect(shape, x, y, width, height):
......@@ -329,12 +329,11 @@ def table(shape, spec, data):
if not len(data):
return
table_properties = utils.TableProperties()
data_cols = list(data[0].keys())
data_cols = list(set(spec.get('columns', data_cols)).intersection(data_cols))
table_properties = utils.TableProperties()
color_grad = table_properties.table_cell_gradient(spec.get('gradient', {}), data_cols, data)
# Extending table if required.
color_grad = table_properties.table_cell_gradient(spec.get('cell', {}), data_cols, data)
table_properties.extend_table(shape, data, len(data) + 1, len(data_cols))
# Fetching Table Style for All Cells and texts.
tbl_style = table_properties.get_table_css(shape)
......@@ -358,10 +357,14 @@ def table(shape, spec, data):
if 'text' not in cell_css:
cell_css['text'] = {}
txt_css = copy.deepcopy(tbl_style.get('header' if row_num == 0 else 'row', {}))
if color_grad and isinstance(txt, (int, float)):
cell_css['fill'] = _color.gradient(txt, color_grad)
if row_num > 0 and colname in color_grad:
grad_txt = scale_data(txt, color_grad[colname]['min'],
color_grad[colname]['max'])
gradient = matplotlib.cm.get_cmap(color_grad[colname]['gradient'])
cell_css['fill'] = matplotlib.colors.to_hex(gradient(grad_txt))
if cell_css.get('fill'):
cell_css['fill'] = _color.contrast(cell_css.get('fill'))
cell_css['text']['color'] = _color.contrast(cell_css.get('fill'))
txt_css.update(cell_css.get('text', {}))
cell_css.update({'text': txt_css})
table_properties.apply_table_css(cell, paragraph, run, cell_css)
......@@ -474,12 +477,11 @@ def chart(shape, spec, data):
chart_css(fill, info, fill_graph)
chart_css(line_fill, info, fill_graph)
# Custom Charts Functions below(Sankey, Treemap, Calendarmap).
def sankey(shape, spec, data):
"""Draw sankey in Treemap."""
"""Draw sankey in PPT."""
# Shape must be a rectangle.
if shape.auto_shape_type != MSO_SHAPE.RECTANGLE:
raise NotImplementedError()
......@@ -793,7 +795,7 @@ def bullet(shape, spec, data):
percentage = {'good': 0.125, 'average': 0.25, 'poor': 0.50}
for index, metric in enumerate(['good', 'average', 'poor']):
if not np.isnan(spec[metric]):
scaled = scale_data(spec.get(metric, np.nan), lo, hi, width)
scaled = scale_data(spec.get(metric, np.nan), lo, hi, factor=width)
_width = scaled if orient == 'horizontal' else height
_hight = height if orient == 'horizontal' else scaled
yaxis = y if orient == 'horizontal' else y + (width - scaled)
......@@ -802,7 +804,7 @@ def bullet(shape, spec, data):
'stroke': matplotlib.colors.to_hex(gradient(percentage[metric]))}
rect_css(_rect, **rectstyle)
scaled = scale_data(spec['data'], lo, hi, width)
scaled = scale_data(spec['data'], lo, hi, factor=width)
_width = scaled if orient == 'horizontal' else height / 2.0
yaxis = y + height / 4.0 if orient == 'horizontal' else y + (width - scaled)
xaxis = x if orient == 'horizontal' else x + height / 4.0
......@@ -825,7 +827,7 @@ def bullet(shape, spec, data):
if not np.isnan(spec['target']):
line_hight = 10000
scaled = scale_data(spec['target'], lo, hi, width)
scaled = scale_data(spec['target'], lo, hi, factor=width)
_width = line_hight if orient == 'horizontal' else height
_hight = height if orient == 'horizontal' else line_hight
yaxis = y if orient == 'horizontal' else (width - scaled) + y
......@@ -996,7 +998,7 @@ def bar_circle(shape, spec, data):
# width = width - (width * 0.20)
ticks = width / float(len(bar_data) or 1)
height = scale_data(0, ymax, shape.height, bar_data[bary])
height = scale_data(0, ymax, shape.height, factor=bar_data[bary])
xpos = pd.np.arange(0, width, ticks)
pos = pd.DataFrame(OrderedDict([
('x', xpos),
......@@ -1034,8 +1036,9 @@ def bar_circle(shape, spec, data):
d = circle_data[circle_data['Category'] == row['Category']]
for i, circle in d.iterrows():
y = top + scale_data(0, ymax, row['width'], circle[circley]) - (row['width'] / 2.0)
sz = scale_data(0, d['Size'].max(), row['width'] / 2.0, circle['Size'])
y = top + scale_data(0, ymax, row['width'],
factor=circle[circley]) - (row['width'] / 2.0)
sz = scale_data(0, d['Size'].max(), row['width'] / 2.0, factor=circle['Size'])
xaxis = left + row['x'] + (row['width'] / 2.0) - (sz / 2.0)
cir = parent.add_shape(
MSO_SHAPE.OVAL, xaxis, y, sz, sz)
......
......@@ -8,7 +8,6 @@ import pandas as pd
from six import iteritems
from lxml import objectify
from pptx.util import Inches
from . import color as _color
from pptx.dml.color import RGBColor
from pptx.enum.text import PP_ALIGN
from lxml.builder import ElementMaker
......@@ -532,7 +531,7 @@ class SubTreemap(object):
class TableProperties():
"""Change Table's properties."""
"""Get/Set Table's properties."""
def extend_table(self, shape, data, total_rows, total_columns):
"""Function to extend table rows and columns if required."""
......@@ -552,26 +551,31 @@ class TableProperties():
def table_cell_gradient(self, info, data_cols, data):
"""Function to calculate color gradient."""
color_grad = None
if info.get('gradient'):
color_grad = _color.RdYlGn
data = pd.DataFrame(data) if not isinstance(data, (pd.DataFrame,)) else data
numeric = data[data_cols]._get_numeric_data()
numeric = pd.DataFrame(data)[data_cols]
if 'gradient' in info:
config = {'gradient': info.get('gradient', 'RdYlGn')}
numeric = numeric._get_numeric_data()
min_data = min([numeric[x].min() for x in numeric.columns])
max_data = max([numeric[x].max() for x in numeric.columns])
mean_data = (min_data + max_data) / 2.0
grad_map = {0: min_data, 1: mean_data, 2: max_data}
if isinstance(info['gradient'], str):
color_grad = getattr(_color, info['gradient'])
color_grad[0][0] = min_data
color_grad[1][0] = mean_data
color_grad[2][0] = max_data
elif isinstance(info['gradient'], list) and isinstance(info['gradient'][0], tuple):
color_grad = info['gradient']
elif isinstance(info['gradient'], list) and isinstance(info['gradient'][0], str):
color_grad = [(grad_map[index], clr) for index, clr in enumerate(info['gradient'])]
return color_grad
config['min'] = info.get('min', min_data)
config['max'] = info.get('max', max_data)
config['mid'] = info.get('mid', (min_data + max_data) / 2.0)
info['gradient'] = {}
for column in numeric.columns:
info['gradient'][column] = {column: config}
else:
for column, config in info.items():
if column in numeric.columns:
min_data = numeric[column].min()
max_data = numeric[column].max()
config['gradient'] = config.get('gradient', 'RdYlGn')
config['min'] = config.get('min', min_data)
config['max'] = config.get('max', max_data)
config['mid'] = config.get('mid', (min_data + max_data) / 2.0)
else:
config.pop(column)
return info
def get_table_css(self, shape):
"""Function to get Table style for rows and columns."""
......@@ -615,10 +619,10 @@ class TableProperties():
if info.get('text'):
text_style = info.get('text')
if text_style.get('fill'):
if text_style.get('color'):
rows_text = run.font.fill
rows_text.solid()
run.font.color.rgb = RGBColor.from_string(conver_color_code(text_style['fill']))
run.font.color.rgb = RGBColor.from_string(conver_color_code(text_style['color']))
if text_style.get('font-family'):
run.font.name = text_style['font-family']
if text_style.get('font-size'):
......
......@@ -7,8 +7,9 @@ edit-table:
Table:
table:
data: data['table_data']
gradient:
Sales: {gradient: Reds}
GrossProfit: {gradient: PuBuGn}
# cell:
# text: {'font-size': 14, 'fill': '#ffffff', 'text-align': 'center'}
# fill: '#D73027'
# gradient: Oranges
columns: ['Sales', 'GrossProfit']
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment