Skip to main content

Pynarrative: Transform Data Visualizations into Stories

Logo Pynarrative

Installation

Pynarrative can be easily installed using pip, Python's package manager. To install the library, run the following command in your terminal:

pip install pynarrative

Core API Reference

Full technical documentation here

Story(
    data=None,
    width=None,
    height=None,
    font=None,
    base_font_size=None,
    template=None,
    geodata=False,
    geojson_url=None,
    geojson_property=None,
    geo_property=None,
    geo_value=None,
    **kwargs
)


add_labels_chart(
    values=None,
    color=None,
    orientation='horizontal',
    angle=0,
    dx=0,
    dy=0,
    font_size=11,
    font_weight='normal',
    geodata=False,
    geodata_label=None
)


add_title(
    title,
    subtitle=None,
    background_color=None,
    color=None, align='center',
    img_url=None,
    img_width=80,
    x_img_offset=-300,
    y_img_offset=0
)

add_context(
    text,
    text_align='left',
    position='left',
    align='middle',
    font_weight='normal',
    font_style='normal',
    title='CONTEXT',
    title_dy=0,
    img_url=None,
    img_width=60,
    x_img_offset=-40,
    y_img_offset=0
)

add_next_steps(
    steps,
    text_align='left',
    position='bottom',
    title='NEXT STEPS',
    title_dy=0,
    title_dx=0,
    mode='horizontal',
    img_url=None,
    img_width=45,
    x_img_offset=-50,
    y_img_offset=-22.5
)

add_source(
    text,
    position='top',
    vertical=False,
    align='center'
)

add_annotation(
    x=None,
    y=None,
    text=None,
    title=None,
    dx=0,
    dy=0,
    background_color=None,
    text_color=None,
    how_subject=False,
    line_point_color=None,
    img_url=None,
    img_width=60,
    x_img_offset=0,
    y_img_offset=0
)


add_line(
    value,
    orientation='horizontal',
    math=None,
    color='',
    label_text='',
    label_dx=0,
    label_dy=0,
    label_font_size='',
    label_font_weight='normal',
    label_font_style='normal'
)


render(
    save=False,
    filename=None,
    ppi=False
)

Overview

Pynarrative is a Python library designed to transform data visualizations into engaging narratives. Built as an extension of Altair, pynarrative provides a powerful set of tools for creating interactive, narrative-driven visualizations that engage and inform your audience. The library bridges the gap between raw data visualization and storytelling by introducing narrative elements such as contextual descriptions, annotations and nextsteps.

Architecture

Pynarrative is built around the Story class: it serves as the core component, handling the creation and management of narrative visualizations. It extends Altair's functionality while maintaining full compatibility with its declarative API.

The library requires Python 3.6 or later and depends on the following packages:

altair
pandas
geopandas

These dependencies will be automatically installed if they're not already present in your environment.

Getting Started

PyNarrative is designed to be intuitive while providing powerful narrative capabilities. Here are two comprehensive examples showcasing both basic usage and advanced features:

Basic Example

import pandas as pd
import pynarrative as pn
import altair as alt

colosseum = pd.DataFrame({
    "year" : [2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024],
    "visitors" : [5.201, 5.625, 6.182, 6.551, 6.409, 7.036, 7.650, 7.618, 1.086, 1.689, 9.812, 12.298, 14.733]
})

story = (
    pn.Story(
    #Builing the Story class object
        data = colosseum,
        #Not using any width and heighth, pynarrative vill apply the default values
        #Not using any template, pynarrative will apply its defaultTemplate, with default colors and dimensions
    )

    #Method invocation and chaining
    #Plotting a bar chart
    .mark_bar(
        cornerRadiusEnd = 10, #With the rounded ends of the bars
        size = 25 #Of a specified size
    )

    #Data encoding
    .encode(
        x = alt.X(
            "year:O", #'year' is an ordinal category (:O)
            title = "Year", 
            axis = alt.Axis(
                labelAngle = -45
            )
        ),
        y = alt.Y(
            "visitors:Q", #'visitors' is a quantity (:Q)
            title = "Number of visitors (in millions)",
        )
    )

    #Data source
    .add_source(
        text = "Source: Ministero della Cultura via statista.com",
        position = "top",
        align = "right"
    )

    #Title and subtitle
    .add_title(
        title = "Number of visitors to the Colosseum archaeological park in Rome",
        subtitle = "Colosseum, Roman Forum, and Palatine Hill (from 2012 to 2024)",
        align = "center",
    )

    #Context area (on top)
    .add_context(
        position = "top",
        title = "An introduction",
        text = "The Colosseum (Italian: Colosseo) is an elliptical amphitheatre in the centre of the city of Rome, Italy,just east of the Roman Forum. It is the largest ancient amphitheatre ever built, and is the largest standing amphitheatre in the world. Construction began under the Emperor Vespasian in 72 and was completed in \n AD 80 under his successor and heir, Titus. Further modifications were made during the reign of Domitian. The three emperors who were patrons of the work are known as the Flavian dynasty, and the amphitheatre was named the Flavian Amphitheatre (Latin: Amphitheatrum lavium; Italian: Anfiteatro Flavio) by later classicists and archaeologists for its association with their family name (Flavius).",
    )

    #Context area (on the left)
    .add_context(
        position = "left",
        title = "An analysis of tourism flows",
        text = "Between 2012 and 2019, the Colosseum \n Archaeological Park saw steady visitor growth, consistently surpassing the historical average of 7.07 million. Following a drastic plunge in 2020–2021 due to COVID-19 pandemic restrictions (reaching a low of 1.08 million), the reopening of international travel sparked a massive \"revenge tourism\" boom. By 2024, visitor numbers reached an all-time high of 14.73 million—more than doubling the period's overall average.",
    )

    #Highlghing 2020 and 2024 bars
    .add_highlight([2020, 2024])

    #Creating labels with values
    .add_labels_chart(
        values = "visitors:Q", #Setting the 'axis' for which to write the values
        font_size = 14,
        font_weight = "bold",
        dy = -10 #Moving the labels slightly upwards.
    )

    .render() #Rendering the data story
)

story #Visualizing the data story

Here is the result:

Example 1

Advanced Example with Customization

import pandas as pd
import pynarrative as pn
import altair as alt

story = (
    pn.Story(
    #Builing the Story class object
        data = colosseum,
        #Not using any width and heighth, pynarrative vill apply the default values
        #Not using any template, pynarrative will apply its defaultTemplate, with default colors and dimensions
    )

    #Method invocation and chaining
    #Plotting a bar chart
    .mark_bar(
        cornerRadiusEnd = 10, #With the rounded ends of the bars
        size = 25 #Of a specified size
    )

    #Data encoding
    .encode(
        x = alt.X(
            "year:Q", #'year' is an ordinal category (:O) but we need
                      #to indicate it as a quantity in order to be able
                      #to use the add_annotation() method (see later)
            title = "Year", 
            axis = alt.Axis(
                format = "d", #Using format = "d" (meaning 'decimal')
                              #to display years without thousands separators
                              #(e.g. 2020 instead of 2,020)
                labelAngle = -45,
                values = colosseum["year"].to_list() #Forcing values
                
            ),
        ),
        y = alt.Y(
            "visitors:Q", #'visitors' is a quantity (:Q)
            title = "Number of visitors (in millions)",
        )
    )

    .add_source(
        text = "Source: Ministero della Cultura via statista.com",
        position = "top",
        align = "right"
    )
    
    .add_title(
        title = "Number of visitors to the Colosseum archaeological park in Rome",
        subtitle = "Colosseum, Roman Forum, and Palatine Hill (from 2012 to 2024)",
        align = "center",
    )

    .add_context(
        position = "top",
        title = "An introduction",
        text = "The Colosseum (Italian: Colosseo) is an elliptical amphitheatre in the centre of the city of Rome, Italy,just east of the Roman Forum. It is the largest ancient amphitheatre ever built, and is the largest standing amphitheatre in the world. Construction began under the Emperor Vespasian in 72 and was completed in \n AD 80 under his successor and heir, Titus. Further modifications were made during the reign of Domitian. The three emperors who were patrons of the work are known as the Flavian dynasty, and the amphitheatre was named the Flavian Amphitheatre (Latin: Amphitheatrum lavium; Italian: Anfiteatro Flavio) by later classicists and archaeologists for its association with their family name (Flavius).",
    )

    .add_context(
        position = "left",
        title = "An analysis of tourism flows",
        text = "Between 2012 and 2019, the Colosseum \n Archaeological Park saw steady visitor growth, consistently surpassing the historical average of 7.07 million. Following a drastic plunge in 2020–2021 due to COVID-19 pandemic restrictions (reaching a low of 1.08 million), the reopening of international travel sparked a massive \"revenge tourism\" boom. By 2024, visitor numbers reached an all-time high of 14.73 million—more than doubling the period's overall average.",
    )

    .add_labels_chart(
        values = "visitors:Q",
        font_size = 14,
        font_weight = "bold",
        dy = -10
    )

    #Vertical line
    .add_line(
        orientation = "vertical",
        value = 2020,
        label_text = "Covid 19 pandemic beginning",
        label_dx = -200,
        label_dy = 50,
        label_font_size = 14
    )
    
    #Vertical line
    .add_line(
        orientation = "vertical",
        value = 2021,
        label_text = "End of Covid 19 pandemic restrictions",
        label_dx = 5,
        label_dy = 10,
        label_font_size = 14
    )

    #Horizontal line highlighing the average value in the 'value' argument 
    .add_line(
        orientation = "horizontal",
        value = colosseum["visitors"],
        math = "mean",
        label_text = "Avg per year = 7.07 Mln",
        label_font_size = 14
    )

    #Highlighing bar and adding an image
    .add_highlight(
        category = 2020,
        img_url = "https://cdn-icons-png.flaticon.com/512/2746/2746582.png",
        img_width = 60,
        y_img_offset = -35,
        x_img_offset = 20,
        highlight_color = "#E7473C"
    )

    #Highlighing bar
    .add_highlight(
        category = 2021,
        highlight_color = "#E7473C"
    )

    #Highlighing bar and adding an image
    .add_highlight(
        category = 2024,
        img_url = "https://cdn-icons-png.flaticon.com/512/190/190488.png",
        img_width = 60,
        y_img_offset = 130
    )

    #Writing annotation
    .add_annotation(
        x = 2012,
        y = 12,
        text = "Moderate but steady growth between 2012 and 2018"
    )


    .render() #Rendering the data story
)

story #Visualizing the data story

Here is the result:

Example 2

Example with template

You can easily apply a ready or a custom template to you data simply by importing it and by using the 'template' argument when initializing the 'Story' object, as in the following example:

import pandas as pd
import pynarrative as pn
import altair as alt

from pynarrative.templates.mytemplates.darkBlueTemplate import darkBlueTemplate
#Importing darkBlueTemplate

story = (
    pn.Story(
        data = colosseum,
        width = 600,
        height = 350,
        template = darkBlueTemplate #Using darkBlueTemplate
    )

[...]

)

Here is the result:

Example 3

These examples demonstrate how pynarrative allows you to create rich and interesting narrative visualizations with just a few lines of code.

Documentation

Comprehensive documentation is available for pynarrative, providing detailed information about all features and capabilities. The documentation includes in-depth tutorials, complete API reference, and numerous examples showing different ways to use pynarrative for creating engaging data narratives.

Full documentation is accessible on GitHub at: Pynarrative API Documentation

Contributing

Pynarrative is an open-source project, and we welcome contributions from the community. Whether you're fixing bugs, adding new features, or improving documentation, your help is valuable. Please refer to our contribution guidelines in the repository for more information about how to get involved.

License

Pynarrative is released under the MIT License, allowing both personal and commercial use with minimal restrictions. See the LICENSE file in the repository for the complete license text.

Authors

  • Pynarrative 1.0: Roberto Olinto Barsotti
  • Pynarrative 2.0: Lorenzo Ferrante
  • Supervisor: professor Angelica Lo Duca

Metadata

Release files for pynarrative 2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pynarrative 2.0
File Size Uploaded Provenance
pynarrative-2.0.tar.gz 35.9 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for pynarrative 2.0
File Interpreter ABI Platform
pynarrative-2.0-py3-none-any.whl Python 3 none any Details

Total release size: 68.6 kB

Release files / pynarrative-2.0.tar.gz

Download URL pynarrative-2.0.tar.gz
Size 35.9 kB
Tags Source
SHA-256 checksum
How to use checksums
474b63e978989e66900cc4d3f1289441636264ebcd33aeb73852a2f149778b93
BLAKE2b-256 checksum
How to use checksums
fabe987d9b378f920841c82fc8dba48120dc462f447a8b0acede7f64fab8f131
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.3

Release files / pynarrative-2.0-py3-none-any.whl

Download URL pynarrative-2.0-py3-none-any.whl
Size 32.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
64df1d381fc8c1adb42fe244a8e5d5e3d280300ab9a4f3c6e16f70a2010158db
BLAKE2b-256 checksum
How to use checksums
93170be12b1a6b1603f5afcd03a86d9a8c329a12e5def970a71bc6b4e6117e53
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.3

Security for pynarrative 2.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page