Display matplotlib image on HTML page using Django

A lot of times we use matplotlib to plot images and we would like to display them on a HTML page simply for rendering as PDF for download or just for display.

Fortunately this is possible for django backend, here is an example for plotting a horizontal barchart in HTML.

from io import BytesIO
import base64

from matplotlib.figure import Figure
from matplotlib.ticker import MaxNLocator
import seaborn as sns


class HorizontalBarChart:
    """Plots a horizontal barchat for displaying in html"""

    @staticmethod
    def _set_annotation(fig, rect, max_bar_width):
        """Annotate the value of each bar in the barchart"""

        # Rectangle widths are already integer-valued but are floating
        # type, so it helps to remove the trailing decimal point and 0 by
        # converting width to int type
        total = int(rect.get_width())

        annotation_inside_bar_params = dict(
            xytext=(-5, 0),
            color='white',
            ha='right',  # Horizontal align
        )

        annotation_to_right_of_bar_params = dict(
            xytext=(5, 0),
            color='black',
            ha='left',  # Horizontal align
        )

        if max_bar_width == 0:
            # Empty plot, default annotation to the right of bar
            annotation_params = annotation_to_right_of_bar_params
        else:
            # We default all annotations inside the bar
            annotation_params = annotation_inside_bar_params

            current_bar_width_percentage = int(total / max_bar_width * 100)
            if current_bar_width_percentage < 10:
                # current bar size is smaller than 10% of the longest bar, means that
                # it may not be wide enough to print its value inside
                annotation_params = annotation_to_right_of_bar_params

        yloc = rect.get_y() + rect.get_height() / 2
        fig.annotate(
            total,
            xy=(total, yloc),
            textcoords="offset points",
            va='center',
            weight='bold',
            clip_on=True,
            **annotation_params,
        )

    @staticmethod
    def _to_data_url(fig):
        """
        Create a data url for matplotlib Figure for embed in html

        How to use it:
        In python:
            chart = HorizontalBarChart()._to_data_url(data)
        In html:
            <img src="{{ chart }}">
        """

        buf = BytesIO()  # Save it to a temporary buffer.
        fig.savefig(buf, format='png')
        data = base64.b64encode(buf.getbuffer()).decode("ascii")
        return f"data:image/png;base64,{data}"

    @classmethod
    def get_image_url(cls, data, title=None, with_annotation=False):
        """ Generate a barplot for given data

        :param dict[int] data: input data to plot
        :param str title: title for the chart
        :param bool with_annotation:
            if set, annotate the value of each bar in the chart so its easier to see
            defaults to False

        :return str: data url of the generated barchart, easy to render in html
        """

        x = list(data.values())
        y = list(data.keys())

        # Create a Figure constructor so it saves to in-memory buffers
        fig = Figure(figsize=(10, 5))

        # Set temporary plot themes for each plot
        with sns.plotting_context("talk"):  # Bigger fonts
            with sns.axes_style("whitegrid"):  # White grid background
                ax = fig.subplots()
                ax = sns.barplot(x=x, y=y, color='#2b7bba', ax=ax) # Mono color for bars

                if title:
                    ax.set_title(title)

        # Make sure we use integer for axis
        ax.xaxis.set_major_locator(MaxNLocator(integer=True))

        if with_annotation:
            max_bar_width = max([rect.get_width() for rect in ax.patches])

            # Annotation the value of each bar for a better presentation
            for rect in ax.patches:
                cls._set_annotation(ax, rect, max_bar_width)

        fig.set_tight_layout(tight=True)  # less white margines

        return cls._to_data_url(fig)

Here is the html template code:

  <picture>
    <img src="{{ your_generated_chart }}" style="width: 100%;">
  </picture>

The key implementations here:

Reference: