Full Example Report

Below you can find a full example report with plenty of comments detailing the inner workings and design philosophy of basic-report.

Code

from basic_report import Report

import datetime
import tempfile
import numpy as np
import pandas as pd
import matplotlib.pylab as plt

from lorem_text import lorem
from pathlib import Path

#-----------------------------------------------------------------------------------------------------------------------
# FAKE DATA PREPARATION
#-----------------------------------------------------------------------------------------------------------------------
errors = ['As most of the time you can use normal <strong>HTML</strong>',
          'code to manipulate your error strings',
          'You could probably even add tables and such',
          'But this is not yet supported on an API level']
warnings = ['This is just one single warning']
info     = ['This is just one info']

# Table data
# 100 rows - 5 cols of random variables
columns = ['COL 1', 'COL 2', 'COL 3', 'COL 4', 'COL 5']
table   = np.random.rand(100, 5)
table   = pd.DataFrame(table, columns=columns)
table.index.name = 'Index Name'

#-----------------------------------------------------------------------------------------------------------------------
# CREATING THE REPORT
#-----------------------------------------------------------------------------------------------------------------------
def make_report():
    # Calling Report() prepares everything for the main page index.html
    r = Report(
        report_dir='output_dir',
        report_title='Example Report',
        report_date=datetime.date.today(),
        color_mode='light',
        config_file=None,
        text_color='slate',
    )


    # First add the big report header. You don't have to do this if you just want to add plots, etc.
    r.add_report_header(color='forest')


    # Now we want to add the error/info/warning section which provides information about all the issues encountered
    r.add_header('Errors / warnings / info section')
    r.add_text('This is what the error/warning/info section looks like if there are no things to report on')
    r.add_error_warning_info_section(errors=[], warnings=[], info=[])

    r.add_text('And this is the section when there are things to report on')
    r.add_error_warning_info_section(errors=errors, warnings=warnings, info=info)


    # You can adjust the color of many elements, e.g., headers and subheaders. You can also use standard HTML syntax
    # in your text, e.g., to provide a link to an external page. You can also choose your text alignment when adding
    # a text element
    r.add_header('Columns <a href=www.google.com>www.google.com</a>', color='cherry')
    r.add_text(lorem.paragraph(), align='left', color='cherry')

    r.add_header('Columns <a href=www.google.com>www.google.com</a>', color='ocean')
    r.add_text(lorem.paragraph(), align='center', color='ocean')

    r.add_header('Columns <a href=www.google.com>www.google.com</a>', color='earth')
    r.add_text(lorem.paragraph(), align='right', color='earth')


    # The report class supports a variety of layout functions, which can also be nested. They all follow the same 
    # principle:
    #   1) Open the layouter
    #   2) Add layout item
    #   3) Close layouter
    # Here's a column layouter with three columns as an example:
    r.add_header('Columns I, II, III', color='cappuccino')
    r.open_columns()
    r.add_column()
    r.add_header('Column I')
    r.add_text(lorem.paragraph(), align='left')
    r.add_column()
    r.add_header('Column II')
    r.add_text(lorem.paragraph(), align='right')
    r.add_column()
    r.add_header('Column III')
    r.add_text(lorem.paragraph())
    r.close_columns()


    # You might want to add a sub-section with a slightly smaller header. The level defines the width of the section
    # header and ranges from 12 (widest header) to 1 (smallest header, which seems almost useless). The header will
    # adjust to its surroundings. I.e. if you are already in a column layouter with 3 columns sub_level 1 will only span
    # the column it is called in. You can further reduce the level of the content by putting it in a sublevel element
    r.add_sub_header('Sub header of level 10', sub_level=10)
    r.open_sublevel(size=10)
    r.add_text(lorem.paragraph())
    r.close_sublevel()

    r.add_sub_header('Sub header of level 6', sub_level=6)
    r.open_sublevel(size=6)
    r.add_text(lorem.paragraph())
    r.close_sublevel()

    
    # If you want to add a table you can do that easily by calling add_table. The data can either be a pandas DataFrame
    # or an array-like of array-likes. You can specify an array of formatters, one for every column. Or you can pass a
    # single formatter that is used on every column. If you pass a pandas dataframe the index will be the first column
    # and the formatter is automatically set to str. This default behavior might change in the future. If you pass a 
    # list of lists the first row represents the table headers, so the formatters won't be applied to them.
    # There are some additional options you can enable for the table
    #   1) search:  Adds a search box to the table
    #   2) page:    Only shows 10 entries at a time (can be extended to 25, 50 or 100)
    #   3) info:    Simply adds a text to the bottom of the table highlighting how many entries are currently shown and
    #               how many there are in total
    #   4) no_sort: Disables initial sorting
    #   5) color_negative_values: Colors values below zero in red
    #   6) color_positive_values: Colors values greater than zero in green
    #   7) full_width: Use the full page width to display the table
    # You can also add a caption to the table. And you can adjust the width of the table using `size` and the alignment 
    # of the cell content using `align`
    r.add_header('Large Table (color=cherry)', color='cherry')
    fmts = ['{:.1f}'.format, '{:2.0e}'.format, str, '{}'.format, '{:5.2e}'.format]
    r.add_table(
        table,
        options=['search', 'page', 'info'],
        formatters=fmts,
        caption='This is an example caption of a searchable table',
    )


    # If no options are selected all data will be shown. So if you really like long tables you can still show them on
    # the report page.
    r.add_header('Smaller Table w/ alignment (color=sunrise)', color='sunrise')
    r.add_table(
        table.iloc[:5],
        formatters='{:.2f}'.format,
        caption='Small Table',
        align='left',
        size=8,
    )

    
    # Just like the column layouter the tab layouter needs to be opened and closed to work. Just like every other
    # layouter it will automatically tell you to close the layout environment if you forget to do it. Once the tab
    # layouter is open you can add individual tabs again. Everything following an add_tab method will be displayed
    # within this particular tab
    r.add_header('How to use tabs')
    r.open_tabs()

    with tempfile.TemporaryDirectory() as tmp_dir:
        r.add_tab('Plot 1')
        plt.figure(figsize=(12,8))
        plt.plot(np.arange(100), np.random.rand(100))

        out_file = Path(tmp_dir) / 'plot1.png'
        plt.savefig(out_file)
        r.add_image(out_file)

        r.add_tab('Plot 2')
        plt.figure(figsize=(12,8))
        plt.plot(np.arange(100), np.random.rand(100))

        out_file = Path(tmp_dir) / 'plot2.png'
        plt.savefig(out_file)
        r.add_image(out_file)

    r.close_tabs()


    # As detailed above, the navbar layouter works just like the tabs one with one exception: When opening the layouter
    # you can actually select the location of the navbar pills, i.e., left, top, or right
    r.add_header('How to use tabs')
    r.add_header('Navbars')
    r.open_navbar(loc='top')
    r.add_navbar_item('Item 1')
    r.add_text(lorem.paragraph(), align='left')
    r.add_navbar_item('Item 2')
    r.add_text(lorem.paragraph())
    r.add_navbar_item('Item With A Long Name')
    r.add_text(lorem.paragraph(), align='right')
    r.close_navbar()

    r.open_navbar(loc='left', color='ocean')
    r.add_navbar_item('Item 1')
    r.add_text(lorem.paragraph(), align='left')
    r.add_navbar_item('Item 2')
    r.add_text(lorem.paragraph())
    r.add_navbar_item('Item With A Long Name')
    r.add_text(lorem.paragraph(), align='right')
    r.close_navbar()

    r.open_navbar(loc='right', color='cherry')
    r.add_navbar_item('Item 1')
    r.add_text(lorem.paragraph(), align='left')
    r.add_navbar_item('Item 2')
    r.add_text(lorem.paragraph())
    r.add_navbar_item('Item With A Long Name')
    r.add_text(lorem.paragraph(), align='right')
    r.close_navbar()


    # Last but not least there are the accordions. They are again following the default layouter syntax
    r.add_header('Accordions')
    r.open_accordion()
    r.add_accordion_item('Show Lorem')
    r.add_text(lorem.paragraph())
    r.add_accordion_item('Show Ipsum')
    r.add_text(lorem.paragraph())
    r.add_accordion_item('Show Dolor')
    r.add_text(lorem.paragraph())
    r.add_accordion_item('Show Table')
    r.add_table(table, size=12)
    r.close_accordion()


    # You can also add new pages to the report. This will not automatically switch the reporter to the newly created
    # page. So calling r.add_xyz still targets your old page. You can now either change the report to the new page or
    # reference it via key
    r.add_page('page2')
    r['page2'].add_header('This is page 2')

    r.set_current_page('page2')
    r.add_text(lorem.paragraph())
    r.add_text(lorem.paragraph(), align='left')
    r.add_text(lorem.paragraph(), align='right')


    # You can add links between pages either locally or globally. Local links will appear where you add them. They are
    # plain old vanilla html links. Because of the way the reporter is set up this only works calling the reporter
    # object and it does not work calling the keyed page as the page object does not know about the other pages.
    r.add_local_link_to_page('main', 'Local link to main')

    # Global links on the other hand are listed in a toolbar right on top of the page. They are visible for all
    # subpages. You also have to include main in your link list as we don't include it as a default. Global links can
    # be added from any page as they will be added to every page at the end.
    r.add_global_link_to_page('main', 'This is a global link to main')
    r.add_global_link_to_page('page2', 'This is a global link to subpage 2')

    # And let's add another page
    r.add_page('page3')
    r.add_global_link_to_page('page3', 'Page 3')
    r['page3'].add_table(table)


    # Actually write the whole report to file
    r.dump()

if __name__ == '__main__':
    make_report()