great-tables icon indicating copy to clipboard operation
great-tables copied to clipboard

Nanoplots compatible in an email using as_raw_html()

Open andre-sun opened this issue 9 months ago • 5 comments

Question

Is a nanoplot capable of appearing in an email? Before I open a bug I want to confirm whether or not when using as_raw_html() and sending via email the nanoplot should be displaying correctly?

Currently no interactive plot appears and instead the individual values are what's left when viewing in an email:

Product | Trend Plot
Shoes     0.9900.710.980.270.440.99

andre-sun avatar Feb 12 '25 20:02 andre-sun

@rich-iannone, I recall seeing a video where you shared your experience implementing a table that could be beautifully sent via email. Could you provide some insights on that? What else would be needed to send Great Tables via email without much hassle?

jrycw avatar Mar 12 '25 14:03 jrycw

Yes! That was with the R version of Great Tables ('gt'). I also develop an R package called blastula that makes it fairly easy to send an HTML email through SMTP (and Posit Connect). It does work well and is well tested across email providers/clients/OSes. What's uncertain is whether nanoplots in tables in HTML emails work as well as they do in a browser.

I tried testing this out yesterday (w/ gt + blastula, since it also has nanoplots) but came up against issues sending a message through SMTP. Using the SMTP2GO service (which used to be pretty painless) now has some hoops to jump through. What I'll try soon is sending an email (w/ nanoplots-containing table) through the combination of Posit Connect, Quarto (which has built-in emailing functionality), and Great Tables.

I looked around for a similar package to blastula in the Python ecosystem. Couldn't find anything, but maybe I'm missing something obvious. I've had Python users ask me to port over blastula to Python so maybe this is missing functionality in the Python space.

rich-iannone avatar Mar 13 '25 14:03 rich-iannone

@andre-sun I forgot to ask the more obvious thing: what are you using to send an email with a GT table? Could you share the code?

rich-iannone avatar Mar 13 '25 14:03 rich-iannone

@rich-iannone Thanks for the replies. I am using the smtplib library for the sending of the message and the email library for the message formatting. Here's concise version of my code which currently is using GT.save() to create a .png of the image as an alternative to as_raw_html():

    table = (
        GT(merged_df)
        .tab_header(title="Predictions")
        .fmt_percent("Probability", decimals=0)
        .fmt_nanoplot(
            columns="5-Day Trend",
            plot_type='bar',
            plot_height="4em",
            expand_y=[0, 1.2],
            options=nanoplot_options(
                interactive_data_values=False, 
                y_val_fmt_fn=lambda y: f"{y:.2f}" 
            )
        )
    )

    # Save the table as an image
    image_path = "table_image.png"
    table.save(image_path, selector='table', scale=1.0)
    
    # Create HTML body with embedded image
    html_body = """
    <html>
      <body>
        <p>Competitor Restore Predictions:</p>
        <img src="cid:table_image" alt="Predictions Table" style="max-width:100%; height:100%;"/>
      </body>
    </html>
    """
    
    # Send email using SMTP
    try:
        logging.info("Setting up email parameters")
        # Setup email parameters
        sender_email = email_config['sender']
        receiver_emails = email_config['recipient']
        email_subject = email_config['subject']

        if isinstance(receiver_emails, str):
            receiver_emails = [receiver_emails]
            logging.debug(f"Converted receiver_emails to list: {receiver_emails}")

        msg = MIMEMultipart('related')
        alt = MIMEMultipart('alternative')
        msg.attach(alt)
        msg["From"] = sender_email
        msg["To"] = ", ".join(receiver_emails)
        msg["Subject"] = email_subject
        
        # Attach HTML body to alt part
        alt.attach(MIMEText(html_body, "html"))
        
        # Attach image to related part
        with open(image_path, 'rb') as f:
            img = MIMEImage(f.read())
            img.add_header('Content-ID', '<table_image>')
            img.add_header('Content-Disposition', 'inline', filename='table_image.png')
            msg.attach(img)

        # Connect to SMTP server and send email
        logging.info("Connecting to SMTP server")
        with smtplib.SMTP(email_config['smtp_server'], email_config['smtp_port']) as server:
            server.sendmail(sender_email, receiver_emails, msg.as_string())
        logging.info(f"Email successfully sent!")
    except Exception as e:
        logging.error(f"Error sending email: {e}")

andre-sun avatar Mar 18 '25 17:03 andre-sun

GT table in Python does not display properly.

Image

JCfly3000 avatar Jul 11 '25 09:07 JCfly3000