How can I ensure that text fits neatly within a cell in a table, preventing it from overflowing on a PowerPoint slide?

96 Views Asked by At

In need of help on optimizing text placement in PowerPoint table cells to prevent overflow. Instead of overflow, the goal is to position tickers in the second column, as shown in the expected output image. Shared relevant code and images for clarity. Grateful for any assistance!

Data:

enter image description here

Code:

import pandas as pd
from pptx import Presentation
from pptx.util import Inches, Pt
from pptx.enum.text import PP_ALIGN
from datetime import date

# Assuming you have the small_grid DataFrame and ranking_definitions
small_grid = pd.read_excel("E:/Praveen/Technical team/WHG/WHG_process_phase_1_09272023.xlsx", sheet_name="Small_grid")

ranking_definitions = {
    1: 'Bottom Fishing',
    2: 'Positive Developing',
    3: 'Positive Trending',
    4: 'Pullback Opportunity',
    5: 'Negative Developing',
    6: 'Negative Trending'
}

# Create a PowerPoint presentation
presentation = Presentation()
slide = presentation.slides.add_slide(presentation.slide_layouts[6])

# Get the dimensions of the slide
slide_width = presentation.slide_width
slide_height = presentation.slide_height

# Set the margins and padding
margin_left = Inches(0.3)
margin_right = Inches(0.3)
margin_top = Inches(1.2)
margin_bottom = Inches(0.7)

# Calculate the available width and height for the table
available_width = slide_width - margin_left - margin_right
available_height = slide_height - margin_top - margin_bottom

# Determine the number of columns
num_columns = len(ranking_definitions)

# Calculate column width
column_width = available_width / num_columns

# Add a table to the slide within the specified limits
table_left = margin_left
table_top = margin_top
table_width = available_width
table_height = available_height


shape = slide.shapes.add_table(rows=2, cols=num_columns, left=table_left, top=table_top, width=table_width, height=table_height)
tbl =  shape._element.graphic.graphicData.tbl
style_id = "{5940675A-B579-460E-94D1-54222C63F5DA}"
tbl[0][-1].text = style_id

table = shape.table

# Set column widths
for i in range(num_columns):
    table.columns[i].width = int(column_width)

# Set header row style
header_row = table.rows[0]
header_row.height = Inches(0.7)
for i, (ranking_code, ranking_name) in enumerate(ranking_definitions.items()):
    cell = header_row.cells[i]  # No need to shift by 1 as there is no additional row
    cell.text = ranking_name
    cell.text_frame.paragraphs[0].font.size = Pt(12)  # Adjust the font size for the header row
    cell.text_frame.paragraphs[0].font.bold = True
    cell.text_frame.paragraphs[0].alignment = PP_ALIGN.CENTER
    
# Populate table with data
data_row = table.rows[1]
for j, (ranking_code, ranking_name) in enumerate(ranking_definitions.items()):
    key = ranking_name  # Adjust this line if needed
    tickers = '\n'.join(small_grid[small_grid['Rankings'] == ranking_code]['Tickers'].tolist())
    
    # Adjust the font size for data rows
    cell = data_row.cells[j]
    cell.text = tickers
    
    # Set font size for each paragraph in the cell
    for paragraph in cell.text_frame.paragraphs:
        paragraph.font.size = Pt(10)

The output I'm getting Overflowing text

The output I want Expected Output

1

There are 1 best solutions below

1
On

When using a Table with Cells this is harder than you might think. First youve to make sure that youve enough columns avaible beforhand since splitting with the pptx python package is not an option.

In my case i made 3 columns per ranking:

num_columns = len(ranking_definitions)*3

Then you need to merge the headers:

#creating 3 columns per ranking and the merge them
for i, (ranking_code, ranking_name) in enumerate(ranking_definitions.items()):

    cell = header_row.cells[3*i]  # No need to shift by 1 as there is no additional row
    cell.text = ranking_name
    cell.text_frame.paragraphs[0].font.size = Pt(12)  # Adjust the font size for the header row
    cell.text_frame.paragraphs[0].font.bold = True
    cell.text_frame.paragraphs[0].alignment = PP_ALIGN.CENTER
    # merge header columns into 1
    cell.merge(header_row.cells[i*3+2])

When populating the data youve to limit the size of a column ( size_column = 35 ). Then Populate the column it with a few conditions. First checking how many tickers entries there are for a certain ranking. Later splitting does tickers into separated strings. See code below:

for j, (ranking_code, ranking_name) in enumerate(ranking_definitions.items()):
    key = ranking_name  # Adjust this line if needed
    tickers_list=small_grid[small_grid['Rankings'] == ranking_code]['Tickers'].tolist()
    #max entrys per column
    size_column = 35
    column_counter= len(tickers_list) // size_column
    
    for i in range(column_counter+1):
        tickers = '\n'.join(tickers_list[size_column * i:size_column * (i + 1)])
        cell = data_row.cells[3 * j+i]
        cell.text =tickers

        # Set font size for each paragraph in the cell
        for paragraph in cell.text_frame.paragraphs:
            paragraph.font.size = Pt(9)

The final touch is to remove the cell borders. The pptx python package does not have an implementation for this but there seems to be a workaround (https://github.com/scanny/python-pptx/issues/573 with Utkarsh Sinha)

Finally it looks like this: enter image description here

The downsides are if you have more then 3*35 entries you will run into problems. So it would be better to build in some more restrictions etc. Also working with table/cells in pptx python seems to be pretty heinous. Maybe working with textboxes instead would be better. Those boxes to also have an autofill option.

The entire code can be found here:

import pandas as pd
from pptx import Presentation
from pptx.util import Inches, Pt
from pptx.enum.text import PP_ALIGN
from pptx.oxml.xmlchemy import OxmlElement
from datetime import date

# Assuming you have the small_grid DataFrame and ranking_definitions
small_grid = pd.read_excel("book1.xlsx", sheet_name="Small_grid")

ranking_definitions = {
    1: 'Bottom Fishing',
    2: 'Positive Developing',
    3: 'Positive Trending',
    4: 'Pullback Opportunity',
    5: 'Negative Developing',
    6: 'Negative Trending'
}

# Create a PowerPoint presentation
presentation = Presentation()
slide = presentation.slides.add_slide(presentation.slide_layouts[6])

# Get the dimensions of the slide
slide_width = presentation.slide_width
slide_height = presentation.slide_height

# Set the margins and padding
margin_left = Inches(0.3)
margin_right = Inches(0.3)
margin_top = Inches(1.2)
margin_bottom = Inches(0.7)

# Calculate the available width and height for the table
available_width = slide_width - margin_left - margin_right
available_height = slide_height - margin_top - margin_bottom

# Determine the number of columns times 3 to create 3 availabe colums per ranking
num_columns = len(ranking_definitions)*3

# Calculate column width
column_width = available_width / num_columns

# Add a table to the slide within the specified limits
table_left = margin_left
table_top = margin_top
table_width = available_width
table_height = available_height

shape = slide.shapes.add_table(rows=2, cols=num_columns, left=table_left, top=table_top, width=table_width,
                               height=table_height)
tbl = shape._element.graphic.graphicData.tbl
style_id = "{5940675A-B579-460E-94D1-54222C63F5DA}"
tbl[0][-1].text = style_id

table = shape.table

# Set column widths
for i in range(num_columns):
    table.columns[i].width = int(column_width)


# Set header row style
header_row = table.rows[0]
header_row.height = Inches(0.7)

#creating 3 columns per ranking and the merge them
for i, (ranking_code, ranking_name) in enumerate(ranking_definitions.items()):

    cell = header_row.cells[3*i]  # No need to shift by 1 as there is no additional row
    cell.text = ranking_name
    cell.text_frame.paragraphs[0].font.size = Pt(12)  # Adjust the font size for the header row
    cell.text_frame.paragraphs[0].font.bold = True
    cell.text_frame.paragraphs[0].alignment = PP_ALIGN.CENTER
    # merge header columns into 1
    cell.merge(header_row.cells[i*3+2])


# Populate table with data
data_row = table.rows[1]



for j, (ranking_code, ranking_name) in enumerate(ranking_definitions.items()):
    key = ranking_name  # Adjust this line if needed
    tickers_list=small_grid[small_grid['Rankings'] == ranking_code]['Tickers'].tolist()
    #max entrys per column
    size_column = 35
    column_counter= len(tickers_list) // size_column
        
    for i in range(column_counter+1):
        tickers = '\n'.join(tickers_list[size_column * i:size_column * (i + 1)])
        cell = data_row.cells[3 * j+i]
        cell.text =tickers

        # Set font size for each paragraph in the cell
        for paragraph in cell.text_frame.paragraphs:
            paragraph.font.size = Pt(9)

#removing the cells border.
#src code from https://github.com/scanny/python-pptx/issues/573 with Utkarsh Sinha
def SubElement(parent, tagname, **kwargs):
    element = OxmlElement(tagname)
    element.attrib.update(kwargs)
    parent.append(element)
    return element

def _set_cell_border(cell, border_color="FFFFFF", border_width='12700'):
    """ Hack function to enable the setting of border width and border color
        - left border
        - right border
        - top border
        - bottom border
    """
    tc = cell._tc
    tcPr = tc.get_or_add_tcPr()

    # Right Cell Border
    lnR = SubElement(tcPr, 'a:lnR', w='3175', cap='flat', cmpd='sng', algn='ctr')
    lnR_solidFill = SubElement(lnR, 'a:solidFill')
    lnR_srgbClr = SubElement(lnR_solidFill, 'a:srgbClr', val=border_color)
    lnR_prstDash = SubElement(lnR, 'a:prstDash', val='solid')
    lnR_round_ = SubElement(lnR, 'a:round')
    lnR_headEnd = SubElement(lnR, 'a:headEnd', type='none', w='med', len='med')
    lnR_tailEnd = SubElement(lnR, 'a:tailEnd', type='none', w='med', len='med')

for c in range(num_columns):
    if c%3!=0:
        cell=data_row.cells[c-1]
        _set_cell_border(cell, border_color="FFFFFF", border_width="0")

presentation.save("test.pptx")