How to Create a Beautiful Polar Histogram With Python and Matplotlib

Hi, and welcome to this Python + Matplotlib tutorial, where I will show you how to create the beautiful polar histogram you see above.

Polar histograms are great when you have too many values for a standard bar chart. The circular shape where each bar gets thinner towards the middle allows us to cram more information into the same area.

I’m using data from the World Happiness Report and information about income levels from the World Bank.

You can find the code and data I’m using in this GitHub repository.

Let’s get started.


Step 1: Preparations

Let’s start with a few preperations.

Importing libraries

We only need standard Python libraries familiar to everyone. PIL is not mandatory, but it’s my preferred choice for handling images which we do when adding flags.

<span>import</span> <span>math</span>
<span>import</span> <span>numpy</span> <span>as</span> <span>np</span>
<span>import</span> <span>pandas</span> <span>as</span> <span>pd</span>
<span>import</span> <span>seaborn</span> <span>as</span> <span>sns</span>
<span>import</span> <span>matplotlib.pyplot</span> <span>as</span> <span>plt</span>
<span>from</span> <span>PIL</span> <span>import</span> <span>Image</span>
<span>from</span> <span>matplotlib.lines</span> <span>import</span> <span>Line2D</span>
<span>from</span> <span>matplotlib.patches</span> <span>import</span> <span>Wedge</span>
<span>from</span> <span>matplotlib.offsetbox</span> <span>import</span> <span>OffsetImage</span><span>,</span> <span>AnnotationBbox</span>
<span>import</span> <span>math</span>
<span>import</span> <span>numpy</span> <span>as</span> <span>np</span>
<span>import</span> <span>pandas</span> <span>as</span> <span>pd</span>

<span>import</span> <span>seaborn</span> <span>as</span> <span>sns</span>
<span>import</span> <span>matplotlib.pyplot</span> <span>as</span> <span>plt</span>

<span>from</span> <span>PIL</span> <span>import</span> <span>Image</span>
<span>from</span> <span>matplotlib.lines</span> <span>import</span> <span>Line2D</span>
<span>from</span> <span>matplotlib.patches</span> <span>import</span> <span>Wedge</span>
<span>from</span> <span>matplotlib.offsetbox</span> <span>import</span> <span>OffsetImage</span><span>,</span> <span>AnnotationBbox</span>
import math import numpy as np import pandas as pd import seaborn as sns import matplotlib.pyplot as plt from PIL import Image from matplotlib.lines import Line2D from matplotlib.patches import Wedge from matplotlib.offsetbox import OffsetImage, AnnotationBbox

Enter fullscreen mode Exit fullscreen mode

The only thing that stands out is a few specific Matplotlib imports at the end. I’ll cover those components later in the tutorial.

I use pandas to load the data.

<span>df</span> <span>=</span> <span>pd</span><span>.</span><span>read_csv</span><span>(</span><span>"</span><span>./hapiness_report_2022.csv</span><span>"</span><span>,</span> <span>index_col</span><span>=</span><span>None</span><span>)</span>
<span>df</span> <span>=</span> <span>df</span><span>.</span><span>sort_values</span><span>(</span><span>"</span><span>score</span><span>"</span><span>).</span><span>reset_index</span><span>(</span><span>drop</span><span>=</span><span>True</span><span>)</span>
<span>df</span> <span>=</span> <span>pd</span><span>.</span><span>read_csv</span><span>(</span><span>"</span><span>./hapiness_report_2022.csv</span><span>"</span><span>,</span> <span>index_col</span><span>=</span><span>None</span><span>)</span>
<span>df</span> <span>=</span> <span>df</span><span>.</span><span>sort_values</span><span>(</span><span>"</span><span>score</span><span>"</span><span>).</span><span>reset_index</span><span>(</span><span>drop</span><span>=</span><span>True</span><span>)</span>
df = pd.read_csv("./hapiness_report_2022.csv", index_col=None) df = df.sort_values("score").reset_index(drop=True)

Enter fullscreen mode Exit fullscreen mode

Seaborn style settings

Next, I use Seaborn to create a base style by defining the background, text color, and font.

<span>font_family</span> <span>=</span> <span>"</span><span>PT Mono</span><span>"</span>
<span>background_color</span> <span>=</span> <span>"</span><span>#F8F1F1</span><span>"</span>
<span>text_color</span> <span>=</span> <span>"</span><span>#040303</span><span>"</span>
<span>sns</span><span>.</span><span>set_style</span><span>({</span>
<span>"</span><span>axes.facecolor</span><span>"</span><span>:</span> <span>background_color</span><span>,</span>
<span>"</span><span>figure.facecolor</span><span>"</span><span>:</span> <span>background_color</span><span>,</span>
<span>"</span><span>font.family</span><span>"</span><span>:</span> <span>font_family</span><span>,</span>
<span>"</span><span>text.color</span><span>"</span><span>:</span> <span>text_color</span><span>,</span>
<span>})</span>
<span>font_family</span> <span>=</span> <span>"</span><span>PT Mono</span><span>"</span>
<span>background_color</span> <span>=</span> <span>"</span><span>#F8F1F1</span><span>"</span>
<span>text_color</span> <span>=</span> <span>"</span><span>#040303</span><span>"</span>

<span>sns</span><span>.</span><span>set_style</span><span>({</span>
    <span>"</span><span>axes.facecolor</span><span>"</span><span>:</span> <span>background_color</span><span>,</span>
    <span>"</span><span>figure.facecolor</span><span>"</span><span>:</span> <span>background_color</span><span>,</span>
    <span>"</span><span>font.family</span><span>"</span><span>:</span> <span>font_family</span><span>,</span>
    <span>"</span><span>text.color</span><span>"</span><span>:</span> <span>text_color</span><span>,</span>
<span>})</span>
font_family = "PT Mono" background_color = "#F8F1F1" text_color = "#040303" sns.set_style({ "axes.facecolor": background_color, "figure.facecolor": background_color, "font.family": font_family, "text.color": text_color, })

Enter fullscreen mode Exit fullscreen mode

There are several more parameters for set_style, but these four are the only ones I need in this tutorial.

I use websites such as Colorhunt and Coolors to create beautiful color palettes.

Global settings

I’m also adding a few global settings to control the general look. The first four define the range, size, and width of the wedges in the histogram.

<span>START_ANGLE</span> <span>=</span> <span>100</span> <span># At what angle to start drawing the first wedge </span><span>END_ANGLE</span> <span>=</span> <span>450</span> <span># At what angle to finish drawing the last wedge </span><span>SIZE</span> <span>=</span> <span>(</span><span>END_ANGLE</span> <span>-</span> <span>START_ANGLE</span><span>)</span> <span>/</span> <span>len</span><span>(</span><span>df</span><span>)</span> <span># The size of each wedge </span><span>PAD</span> <span>=</span> <span>0.2</span> <span>*</span> <span>SIZE</span> <span># The padding between wedges </span>
<span>INNER_PADDING</span> <span>=</span> <span>2</span> <span>*</span> <span>df</span><span>.</span><span>score</span><span>.</span><span>min</span><span>()</span>
<span>LIMIT</span> <span>=</span> <span>(</span><span>INNER_PADDING</span> <span>+</span> <span>df</span><span>.</span><span>score</span><span>.</span><span>max</span><span>())</span> <span>*</span> <span>1.3</span> <span># Limit of the axes </span>
<span>START_ANGLE</span> <span>=</span> <span>100</span> <span># At what angle to start drawing the first wedge </span><span>END_ANGLE</span> <span>=</span> <span>450</span> <span># At what angle to finish drawing the last wedge </span><span>SIZE</span> <span>=</span> <span>(</span><span>END_ANGLE</span> <span>-</span> <span>START_ANGLE</span><span>)</span> <span>/</span> <span>len</span><span>(</span><span>df</span><span>)</span> <span># The size of each wedge </span><span>PAD</span> <span>=</span> <span>0.2</span> <span>*</span> <span>SIZE</span> <span># The padding between wedges </span>
<span>INNER_PADDING</span> <span>=</span> <span>2</span> <span>*</span> <span>df</span><span>.</span><span>score</span><span>.</span><span>min</span><span>()</span>
<span>LIMIT</span> <span>=</span> <span>(</span><span>INNER_PADDING</span> <span>+</span> <span>df</span><span>.</span><span>score</span><span>.</span><span>max</span><span>())</span> <span>*</span> <span>1.3</span> <span># Limit of the axes </span>
START_ANGLE = 100 # At what angle to start drawing the first wedge END_ANGLE = 450 # At what angle to finish drawing the last wedge SIZE = (END_ANGLE - START_ANGLE) / len(df) # The size of each wedge PAD = 0.2 * SIZE # The padding between wedges INNER_PADDING = 2 * df.score.min() LIMIT = (INNER_PADDING + df.score.max()) * 1.3 # Limit of the axes

Enter fullscreen mode Exit fullscreen mode

Inner padding creates distance between the origo and the start of each wedge. It opens a space in the middle of the graph where I can add a title.

Boilerplate code

As a software engineer, I strive to write reusable code, and it’s the same when I’m working on data visualizations.

That’s why I always start by creating a few lines of boilerplate code that I can extend with reusable functions.

<span>fig</span><span>,</span> <span>ax</span> <span>=</span> <span>plt</span><span>.</span><span>subplots</span><span>(</span><span>nrows</span><span>=</span><span>1</span><span>,</span> <span>ncols</span><span>=</span><span>1</span><span>,</span> <span>figsize</span><span>=</span><span>(</span><span>30</span><span>,</span> <span>30</span><span>))</span>
<span>ax</span><span>.</span><span>set</span><span>(</span><span>xlim</span><span>=</span><span>(</span><span>-</span><span>LIMIT</span><span>,</span> <span>LIMIT</span><span>),</span> <span>ylim</span><span>=</span><span>(</span><span>-</span><span>LIMIT</span><span>,</span> <span>LIMIT</span><span>))</span>
<span>for</span> <span>i</span><span>,</span> <span>row</span> <span>in</span> <span>df</span><span>.</span><span>iterrows</span><span>():</span>
<span>bar_length</span> <span>=</span> <span>row</span><span>.</span><span>score</span>
<span>name</span> <span>=</span> <span>row</span><span>.</span><span>country</span>
<span>length</span> <span>=</span> <span>bar_length</span> <span>+</span> <span>INNER_PADDING</span>
<span>start</span> <span>=</span> <span>100</span> <span>+</span> <span>i</span><span>*</span><span>SIZE</span> <span>+</span> <span>PAD</span>
<span>end</span> <span>=</span> <span>100</span> <span>+</span> <span>(</span><span>i</span><span>+</span><span>1</span><span>)</span><span>*</span><span>SIZE</span>
<span>angle</span> <span>=</span> <span>(</span><span>end</span> <span>+</span> <span>start</span><span>)</span> <span>/</span> <span>2</span>
<span># Create variables here </span>
<span># Add wedge functions here </span>
<span># Add general functions here </span>
<span>plt</span><span>.</span><span>axis</span><span>(</span><span>"</span><span>off</span><span>"</span><span>)</span>
<span>plt</span><span>.</span><span>tight_layout</span><span>()</span>
<span>plt</span><span>.</span><span>show</span><span>()</span>
<span>fig</span><span>,</span> <span>ax</span> <span>=</span> <span>plt</span><span>.</span><span>subplots</span><span>(</span><span>nrows</span><span>=</span><span>1</span><span>,</span> <span>ncols</span><span>=</span><span>1</span><span>,</span> <span>figsize</span><span>=</span><span>(</span><span>30</span><span>,</span> <span>30</span><span>))</span>
<span>ax</span><span>.</span><span>set</span><span>(</span><span>xlim</span><span>=</span><span>(</span><span>-</span><span>LIMIT</span><span>,</span> <span>LIMIT</span><span>),</span> <span>ylim</span><span>=</span><span>(</span><span>-</span><span>LIMIT</span><span>,</span> <span>LIMIT</span><span>))</span>

<span>for</span> <span>i</span><span>,</span> <span>row</span> <span>in</span> <span>df</span><span>.</span><span>iterrows</span><span>():</span>
    <span>bar_length</span> <span>=</span> <span>row</span><span>.</span><span>score</span>
    <span>name</span> <span>=</span> <span>row</span><span>.</span><span>country</span>
    <span>length</span> <span>=</span> <span>bar_length</span> <span>+</span> <span>INNER_PADDING</span>
    <span>start</span> <span>=</span> <span>100</span> <span>+</span> <span>i</span><span>*</span><span>SIZE</span> <span>+</span> <span>PAD</span>
    <span>end</span> <span>=</span> <span>100</span> <span>+</span> <span>(</span><span>i</span><span>+</span><span>1</span><span>)</span><span>*</span><span>SIZE</span>
    <span>angle</span> <span>=</span> <span>(</span><span>end</span> <span>+</span> <span>start</span><span>)</span> <span>/</span> <span>2</span>

    <span># Create variables here </span>
    <span># Add wedge functions here </span>
<span># Add general functions here </span>
<span>plt</span><span>.</span><span>axis</span><span>(</span><span>"</span><span>off</span><span>"</span><span>)</span>
<span>plt</span><span>.</span><span>tight_layout</span><span>()</span>
<span>plt</span><span>.</span><span>show</span><span>()</span>
fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(30, 30)) ax.set(xlim=(-LIMIT, LIMIT), ylim=(-LIMIT, LIMIT)) for i, row in df.iterrows(): bar_length = row.score name = row.country length = bar_length + INNER_PADDING start = 100 + i*SIZE + PAD end = 100 + (i+1)*SIZE angle = (end + start) / 2 # Create variables here # Add wedge functions here # Add general functions here plt.axis("off") plt.tight_layout() plt.show()

Enter fullscreen mode Exit fullscreen mode

For the rest of the tutorial, I will create and add functions and variables under one of the three comments.


Step 2: Drawing wedges

To get more power over the visuals in Matplotlib, it helps to use the underlying components rather than the built-in graph functions.

Drawing a wedge

For example, instead of using plt.pie() to create a pie chart, you can use plt.patches.Wedge() to draw the individual pieces.

That’s why I created the following function, which draws a wedge based on angles, length, bar length, and color.

<span>def</span> <span>draw_wedge</span><span>(</span><span>ax</span><span>,</span> <span>start_angle</span><span>,</span> <span>end_angle</span><span>,</span> <span>length</span><span>,</span> <span>bar_length</span><span>,</span> <span>color</span><span>):</span>
<span>ax</span><span>.</span><span>add_artist</span><span>(</span>
<span>Wedge</span><span>((</span><span>0</span><span>,</span> <span>0</span><span>),</span>
<span>length</span><span>,</span> <span>start_angle</span><span>,</span> <span>end_angle</span><span>,</span>
<span>color</span><span>=</span><span>color</span><span>,</span> <span>width</span><span>=</span><span>bar_length</span>
<span>)</span>
<span>)</span>
<span>def</span> <span>draw_wedge</span><span>(</span><span>ax</span><span>,</span> <span>start_angle</span><span>,</span> <span>end_angle</span><span>,</span> <span>length</span><span>,</span> <span>bar_length</span><span>,</span> <span>color</span><span>):</span>
    <span>ax</span><span>.</span><span>add_artist</span><span>(</span>
        <span>Wedge</span><span>((</span><span>0</span><span>,</span> <span>0</span><span>),</span>
            <span>length</span><span>,</span> <span>start_angle</span><span>,</span> <span>end_angle</span><span>,</span>
            <span>color</span><span>=</span><span>color</span><span>,</span> <span>width</span><span>=</span><span>bar_length</span>
        <span>)</span>
    <span>)</span>
def draw_wedge(ax, start_angle, end_angle, length, bar_length, color): ax.add_artist( Wedge((0, 0), length, start_angle, end_angle, color=color, width=bar_length ) )

Enter fullscreen mode Exit fullscreen mode

In the boilerplate code, I add draw_wedge() under the “Add functions here” comment as below.

<span>bar_length</span> <span>=</span> <span>row</span><span>.</span><span>score</span>
<span>length</span> <span>=</span> <span>bar_length</span> <span># + INNER_PADDING </span><span>start</span> <span>=</span> <span>100</span> <span>+</span> <span>i</span><span>*</span><span>SIZE</span> <span>+</span> <span>PAD</span>
<span>end</span> <span>=</span> <span>100</span> <span>+</span> <span>(</span><span>i</span><span>+</span><span>1</span><span>)</span><span>*</span><span>SIZE</span>
<span>.</span>
<span>.</span>
<span>.</span>
<span># Add functions here </span><span>draw_wedge</span><span>(</span><span>ax</span><span>,</span> <span>start</span><span>,</span> <span>end</span><span>,</span> <span>length</span><span>,</span> <span>bar_length</span><span>,</span> <span>"</span><span>#000</span><span>"</span><span>)</span>
<span>bar_length</span> <span>=</span> <span>row</span><span>.</span><span>score</span>
<span>length</span> <span>=</span> <span>bar_length</span> <span># + INNER_PADDING </span><span>start</span> <span>=</span> <span>100</span> <span>+</span> <span>i</span><span>*</span><span>SIZE</span> <span>+</span> <span>PAD</span>
<span>end</span> <span>=</span> <span>100</span> <span>+</span> <span>(</span><span>i</span><span>+</span><span>1</span><span>)</span><span>*</span><span>SIZE</span>
<span>.</span>
<span>.</span>
<span>.</span>

<span># Add functions here </span><span>draw_wedge</span><span>(</span><span>ax</span><span>,</span> <span>start</span><span>,</span> <span>end</span><span>,</span> <span>length</span><span>,</span> <span>bar_length</span><span>,</span> <span>"</span><span>#000</span><span>"</span><span>)</span>
bar_length = row.score length = bar_length # + INNER_PADDING start = 100 + i*SIZE + PAD end = 100 + (i+1)*SIZE . . . # Add functions here draw_wedge(ax, start, end, length, bar_length, "#000")

Enter fullscreen mode Exit fullscreen mode

I use row.score to define bar_length so that the visible part of the bars has an accurate size relation to each other.
For now, I’ve removed the INNER_PADDING to show you what it does.

When I run the code, I get the following figure.

As you can see, we have a long way to go until we get something similar to the polar histogram that you saw at the beginning, but at least we’ve managed to draw the wedges.

We get a lot of visual artefacts close to the middle, so let’s uncomment INNER_PADDING.

Here’s what we get.

Much better.

Adding color

Next, I have a simple color function that decides the color for each wedge based on the income level of that country.

<span>def</span> <span>color</span><span>(</span><span>income_group</span><span>):</span>
<span>if</span> <span>income_group</span> <span>==</span> <span>"</span><span>High income</span><span>"</span><span>:</span>
<span>return</span> <span>"</span><span>#468FA8</span><span>"</span>
<span>elif</span> <span>income_group</span> <span>==</span> <span>"</span><span>Lower middle income</span><span>"</span><span>:</span>
<span>return</span> <span>"</span><span>#E5625E</span><span>"</span>
<span>elif</span> <span>income_group</span> <span>==</span> <span>"</span><span>Upper middle income</span><span>"</span><span>:</span>
<span>return</span> <span>"</span><span>#62466B</span><span>"</span>
<span>elif</span> <span>income_group</span> <span>==</span> <span>"</span><span>Low income</span><span>"</span><span>:</span>
<span>return</span> <span>"</span><span>#6B0F1A</span><span>"</span>
<span>else</span><span>:</span>
<span>return</span> <span>"</span><span>#909090</span><span>"</span>
<span>def</span> <span>color</span><span>(</span><span>income_group</span><span>):</span>
    <span>if</span> <span>income_group</span> <span>==</span> <span>"</span><span>High income</span><span>"</span><span>:</span>
        <span>return</span> <span>"</span><span>#468FA8</span><span>"</span>
    <span>elif</span> <span>income_group</span> <span>==</span> <span>"</span><span>Lower middle income</span><span>"</span><span>:</span>
        <span>return</span> <span>"</span><span>#E5625E</span><span>"</span>
    <span>elif</span> <span>income_group</span> <span>==</span> <span>"</span><span>Upper middle income</span><span>"</span><span>:</span>
        <span>return</span> <span>"</span><span>#62466B</span><span>"</span>
    <span>elif</span> <span>income_group</span> <span>==</span> <span>"</span><span>Low income</span><span>"</span><span>:</span>
        <span>return</span> <span>"</span><span>#6B0F1A</span><span>"</span>
    <span>else</span><span>:</span>
        <span>return</span> <span>"</span><span>#909090</span><span>"</span>
def color(income_group): if income_group == "High income": return "#468FA8" elif income_group == "Lower middle income": return "#E5625E" elif income_group == "Upper middle income": return "#62466B" elif income_group == "Low income": return "#6B0F1A" else: return "#909090"

Enter fullscreen mode Exit fullscreen mode

I use that function as input to the draw_wedge function.

<span># Add functions here </span><span>draw_wedge</span><span>(</span><span>ax</span><span>,</span> <span>start</span><span>,</span> <span>end</span><span>,</span> <span>length</span><span>,</span> <span>bar_length</span><span>,</span> <span>color</span><span>(</span><span>row</span><span>.</span><span>income</span><span>))</span>
<span># Add functions here </span><span>draw_wedge</span><span>(</span><span>ax</span><span>,</span> <span>start</span><span>,</span> <span>end</span><span>,</span> <span>length</span><span>,</span> <span>bar_length</span><span>,</span> <span>color</span><span>(</span><span>row</span><span>.</span><span>income</span><span>))</span>
# Add functions here draw_wedge(ax, start, end, length, bar_length, color(row.income))

Enter fullscreen mode Exit fullscreen mode

Here’s the result.

With INNER_PADDING and color() there are no strange artifacts left. It’s time to add information that explains what we’re looking at.


Step 3: Adding labels

Let’s add labels for each bar in the polar histogram. I want each bar to display the country’s flag, name, and happiness score.

Defining the positions

When you add flags and text to a chart in Matplotlib, you need to calculate the correct positions.

That’s often tricky, especially when you have an unusual shape like we have in the polar histogram.

The function below takes the length of a wedge and its angle to calculate a position. Padding pushes the position away from the bar to add some visual space.

<span>def</span> <span>get_xy_with_padding</span><span>(</span><span>length</span><span>,</span> <span>angle</span><span>,</span> <span>padding</span><span>):</span>
<span>x</span> <span>=</span> <span>math</span><span>.</span><span>cos</span><span>(</span><span>math</span><span>.</span><span>radians</span><span>(</span><span>angle</span><span>))</span> <span>*</span> <span>(</span><span>length</span> <span>+</span> <span>padding</span><span>)</span>
<span>y</span> <span>=</span> <span>math</span><span>.</span><span>sin</span><span>(</span><span>math</span><span>.</span><span>radians</span><span>(</span><span>angle</span><span>))</span> <span>*</span> <span>(</span><span>length</span> <span>+</span> <span>padding</span><span>)</span>
<span>return</span> <span>x</span><span>,</span> <span>y</span>
<span>def</span> <span>get_xy_with_padding</span><span>(</span><span>length</span><span>,</span> <span>angle</span><span>,</span> <span>padding</span><span>):</span>
    <span>x</span> <span>=</span> <span>math</span><span>.</span><span>cos</span><span>(</span><span>math</span><span>.</span><span>radians</span><span>(</span><span>angle</span><span>))</span> <span>*</span> <span>(</span><span>length</span> <span>+</span> <span>padding</span><span>)</span>
    <span>y</span> <span>=</span> <span>math</span><span>.</span><span>sin</span><span>(</span><span>math</span><span>.</span><span>radians</span><span>(</span><span>angle</span><span>))</span> <span>*</span> <span>(</span><span>length</span> <span>+</span> <span>padding</span><span>)</span>
    <span>return</span> <span>x</span><span>,</span> <span>y</span>
def get_xy_with_padding(length, angle, padding): x = math.cos(math.radians(angle)) * (length + padding) y = math.sin(math.radians(angle)) * (length + padding) return x, y

Enter fullscreen mode Exit fullscreen mode

We can use this function for both flags and text.

Adding flags

For flags, I’m using these rounded ones from FlatIcon.

They require a license, so, unfortunately, I can’t share them, but you can find similar flags in other places.

Here’s my function to add a flag to the graph. It takes the position, the country’s name (which corresponds to the name of the correct file), zoom, and rotation.

<span>def</span> <span>add_flag</span><span>(</span><span>ax</span><span>,</span> <span>x</span><span>,</span> <span>y</span><span>,</span> <span>name</span><span>,</span> <span>zoom</span><span>,</span> <span>rotation</span><span>):</span>
<span>flag</span> <span>=</span> <span>Image</span><span>.</span><span>open</span><span>(</span><span>"</span><span><location>/{}.png</span><span>"</span><span>.</span><span>format</span><span>(</span><span>name</span><span>.</span><span>lower</span><span>()))</span>
<span>flag</span> <span>=</span> <span>flag</span><span>.</span><span>rotate</span><span>(</span><span>rotation</span> <span>if</span> <span>rotation</span> <span>></span> <span>270</span> <span>else</span> <span>rotation</span> <span>-</span> <span>180</span><span>)</span>
<span>im</span> <span>=</span> <span>OffsetImage</span><span>(</span><span>flag</span><span>,</span> <span>zoom</span><span>=</span><span>zoom</span><span>,</span> <span>interpolation</span><span>=</span><span>"</span><span>lanczos</span><span>"</span><span>,</span> <span>resample</span><span>=</span><span>True</span><span>,</span> <span>visible</span><span>=</span><span>True</span><span>)</span>
<span>ax</span><span>.</span><span>add_artist</span><span>(</span><span>AnnotationBbox</span><span>(</span>
<span>im</span><span>,</span> <span>(</span><span>x</span><span>,</span> <span>y</span><span>),</span> <span>frameon</span><span>=</span><span>False</span><span>,</span>
<span>xycoords</span><span>=</span><span>"</span><span>data</span><span>"</span><span>,</span>
<span>))</span>
<span>def</span> <span>add_flag</span><span>(</span><span>ax</span><span>,</span> <span>x</span><span>,</span> <span>y</span><span>,</span> <span>name</span><span>,</span> <span>zoom</span><span>,</span> <span>rotation</span><span>):</span>
    <span>flag</span> <span>=</span> <span>Image</span><span>.</span><span>open</span><span>(</span><span>"</span><span><location>/{}.png</span><span>"</span><span>.</span><span>format</span><span>(</span><span>name</span><span>.</span><span>lower</span><span>()))</span>
    <span>flag</span> <span>=</span> <span>flag</span><span>.</span><span>rotate</span><span>(</span><span>rotation</span> <span>if</span> <span>rotation</span> <span>></span> <span>270</span> <span>else</span> <span>rotation</span> <span>-</span> <span>180</span><span>)</span>
    <span>im</span> <span>=</span> <span>OffsetImage</span><span>(</span><span>flag</span><span>,</span> <span>zoom</span><span>=</span><span>zoom</span><span>,</span> <span>interpolation</span><span>=</span><span>"</span><span>lanczos</span><span>"</span><span>,</span> <span>resample</span><span>=</span><span>True</span><span>,</span> <span>visible</span><span>=</span><span>True</span><span>)</span>

    <span>ax</span><span>.</span><span>add_artist</span><span>(</span><span>AnnotationBbox</span><span>(</span>
        <span>im</span><span>,</span> <span>(</span><span>x</span><span>,</span> <span>y</span><span>),</span> <span>frameon</span><span>=</span><span>False</span><span>,</span>
        <span>xycoords</span><span>=</span><span>"</span><span>data</span><span>"</span><span>,</span>
    <span>))</span>
def add_flag(ax, x, y, name, zoom, rotation): flag = Image.open("<location>/{}.png".format(name.lower())) flag = flag.rotate(rotation if rotation > 270 else rotation - 180) im = OffsetImage(flag, zoom=zoom, interpolation="lanczos", resample=True, visible=True) ax.add_artist(AnnotationBbox( im, (x, y), frameon=False, xycoords="data", ))

Enter fullscreen mode Exit fullscreen mode

I change how the flag rotates if the angle exceeds 270 degrees. That happens when we start adding bars on the right part of the chart. At that point, the flag is to the left of the text, and changing the rotation makes reading more natural.

Now, we can calculate the angle, use get_xy_with_padding() and put flags on the chart.

<span>bar_length</span> <span>=</span> <span>row</span><span>.</span><span>score</span>
<span>length</span> <span>=</span> <span>bar_length</span> <span>+</span> <span>INNER_PADDING</span>
<span>start</span> <span>=</span> <span>START_ANGLE</span> <span>+</span> <span>i</span><span>*</span><span>SIZE</span> <span>+</span> <span>PAD</span>
<span>end</span> <span>=</span> <span>START_ANGLE</span> <span>+</span> <span>(</span><span>i</span><span>+</span><span>1</span><span>)</span><span>*</span><span>SIZE</span>
<span># Add variables here </span><span>angle</span> <span>=</span> <span>(</span><span>end</span> <span>+</span> <span>start</span><span>)</span> <span>/</span> <span>2</span>
<span>flag_zoom</span> <span>=</span> <span>0.004</span> <span>*</span> <span>length</span>
<span>flag_x</span><span>,</span> <span>flag_y</span> <span>=</span> <span>get_xy_with_padding</span><span>(</span><span>length</span><span>,</span> <span>angle</span><span>,</span> <span>0.1</span> <span>*</span> <span>length</span><span>)</span>
<span># Add functions here </span><span>...</span>
<span>add_flag</span><span>(</span><span>ax</span><span>,</span> <span>flag_x</span><span>,</span> <span>flag_y</span><span>,</span> <span>row</span><span>.</span><span>country</span><span>,</span> <span>flag_zoom</span><span>,</span> <span>angle</span><span>)</span>
<span>bar_length</span> <span>=</span> <span>row</span><span>.</span><span>score</span>
<span>length</span> <span>=</span> <span>bar_length</span> <span>+</span> <span>INNER_PADDING</span>
<span>start</span> <span>=</span> <span>START_ANGLE</span> <span>+</span> <span>i</span><span>*</span><span>SIZE</span> <span>+</span> <span>PAD</span>
<span>end</span> <span>=</span> <span>START_ANGLE</span> <span>+</span> <span>(</span><span>i</span><span>+</span><span>1</span><span>)</span><span>*</span><span>SIZE</span>

<span># Add variables here </span><span>angle</span> <span>=</span> <span>(</span><span>end</span> <span>+</span> <span>start</span><span>)</span> <span>/</span> <span>2</span>
<span>flag_zoom</span> <span>=</span> <span>0.004</span> <span>*</span> <span>length</span>
<span>flag_x</span><span>,</span> <span>flag_y</span> <span>=</span> <span>get_xy_with_padding</span><span>(</span><span>length</span><span>,</span> <span>angle</span><span>,</span> <span>0.1</span> <span>*</span> <span>length</span><span>)</span>

<span># Add functions here </span><span>...</span>
<span>add_flag</span><span>(</span><span>ax</span><span>,</span> <span>flag_x</span><span>,</span> <span>flag_y</span><span>,</span> <span>row</span><span>.</span><span>country</span><span>,</span> <span>flag_zoom</span><span>,</span> <span>angle</span><span>)</span>
bar_length = row.score length = bar_length + INNER_PADDING start = START_ANGLE + i*SIZE + PAD end = START_ANGLE + (i+1)*SIZE # Add variables here angle = (end + start) / 2 flag_zoom = 0.004 * length flag_x, flag_y = get_xy_with_padding(length, angle, 0.1 * length) # Add functions here ... add_flag(ax, flag_x, flag_y, row.country, flag_zoom, angle)

Enter fullscreen mode Exit fullscreen mode

The flag_zoom parameters decide the size of the flag and depend on the score. If a country has a low score, there’s less room for a flag, and we need to make it a bit smaller.

Fantastic.

Adding country names and scores

To add the name and score of the country, I’ve written the following function.

As with the flags, I change the rotation if the angle exceeds 270 degrees. Otherwise, the text would be upside down.

<span>def</span> <span>add_text</span><span>(</span><span>ax</span><span>,</span> <span>x</span><span>,</span> <span>y</span><span>,</span> <span>country</span><span>,</span> <span>score</span><span>,</span> <span>angle</span><span>):</span>
<span>if</span> <span>angle</span> <span><</span> <span>270</span><span>:</span>
<span>text</span> <span>=</span> <span>"</span><span>{} ({})</span><span>"</span><span>.</span><span>format</span><span>(</span><span>country</span><span>,</span> <span>score</span><span>)</span>
<span>ax</span><span>.</span><span>text</span><span>(</span><span>x</span><span>,</span> <span>y</span><span>,</span> <span>text</span><span>,</span> <span>fontsize</span><span>=</span><span>13</span><span>,</span> <span>rotation</span><span>=</span><span>angle</span><span>-</span><span>180</span><span>,</span> <span>ha</span><span>=</span><span>"</span><span>right</span><span>"</span><span>,</span> <span>va</span><span>=</span><span>"</span><span>center</span><span>"</span><span>,</span> <span>rotation_mode</span><span>=</span><span>"</span><span>anchor</span><span>"</span><span>)</span>
<span>else</span><span>:</span>
<span>text</span> <span>=</span> <span>"</span><span>({}) {}</span><span>"</span><span>.</span><span>format</span><span>(</span><span>score</span><span>,</span> <span>country</span><span>)</span>
<span>ax</span><span>.</span><span>text</span><span>(</span><span>x</span><span>,</span> <span>y</span><span>,</span> <span>text</span><span>,</span> <span>fontsize</span><span>=</span><span>13</span><span>,</span> <span>rotation</span><span>=</span><span>angle</span><span>,</span> <span>ha</span><span>=</span><span>"</span><span>left</span><span>"</span><span>,</span> <span>va</span><span>=</span><span>"</span><span>center</span><span>"</span><span>,</span> <span>rotation_mode</span><span>=</span><span>"</span><span>anchor</span><span>"</span><span>)</span>
<span>def</span> <span>add_text</span><span>(</span><span>ax</span><span>,</span> <span>x</span><span>,</span> <span>y</span><span>,</span> <span>country</span><span>,</span> <span>score</span><span>,</span> <span>angle</span><span>):</span>
    <span>if</span> <span>angle</span> <span><</span> <span>270</span><span>:</span>
        <span>text</span> <span>=</span> <span>"</span><span>{} ({})</span><span>"</span><span>.</span><span>format</span><span>(</span><span>country</span><span>,</span> <span>score</span><span>)</span>
        <span>ax</span><span>.</span><span>text</span><span>(</span><span>x</span><span>,</span> <span>y</span><span>,</span> <span>text</span><span>,</span> <span>fontsize</span><span>=</span><span>13</span><span>,</span> <span>rotation</span><span>=</span><span>angle</span><span>-</span><span>180</span><span>,</span> <span>ha</span><span>=</span><span>"</span><span>right</span><span>"</span><span>,</span> <span>va</span><span>=</span><span>"</span><span>center</span><span>"</span><span>,</span> <span>rotation_mode</span><span>=</span><span>"</span><span>anchor</span><span>"</span><span>)</span>
    <span>else</span><span>:</span>
        <span>text</span> <span>=</span> <span>"</span><span>({}) {}</span><span>"</span><span>.</span><span>format</span><span>(</span><span>score</span><span>,</span> <span>country</span><span>)</span>
        <span>ax</span><span>.</span><span>text</span><span>(</span><span>x</span><span>,</span> <span>y</span><span>,</span> <span>text</span><span>,</span> <span>fontsize</span><span>=</span><span>13</span><span>,</span> <span>rotation</span><span>=</span><span>angle</span><span>,</span> <span>ha</span><span>=</span><span>"</span><span>left</span><span>"</span><span>,</span> <span>va</span><span>=</span><span>"</span><span>center</span><span>"</span><span>,</span> <span>rotation_mode</span><span>=</span><span>"</span><span>anchor</span><span>"</span><span>)</span>
def add_text(ax, x, y, country, score, angle): if angle < 270: text = "{} ({})".format(country, score) ax.text(x, y, text, fontsize=13, rotation=angle-180, ha="right", va="center", rotation_mode="anchor") else: text = "({}) {}".format(score, country) ax.text(x, y, text, fontsize=13, rotation=angle, ha="left", va="center", rotation_mode="anchor")

Enter fullscreen mode Exit fullscreen mode

We calculate the position of the text in the same way as we did with the flags.

The only difference is that we add more padding since we want it further from the wedges.

<span>bar_length</span> <span>=</span> <span>row</span><span>.</span><span>score</span>
<span>length</span> <span>=</span> <span>bar_length</span> <span>+</span> <span>INNER_PADDING</span>
<span>start</span> <span>=</span> <span>START_ANGLE</span> <span>+</span> <span>i</span><span>*</span><span>SIZE</span> <span>+</span> <span>PAD</span>
<span>end</span> <span>=</span> <span>START_ANGLE</span> <span>+</span> <span>(</span><span>i</span><span>+</span><span>1</span><span>)</span><span>*</span><span>SIZE</span>
<span># Add variables here </span><span>angle</span> <span>=</span> <span>(</span><span>end</span> <span>+</span> <span>start</span><span>)</span> <span>/</span> <span>2</span>
<span>flag_zoom</span> <span>=</span> <span>0.004</span> <span>*</span> <span>length</span>
<span>flag_x</span><span>,</span> <span>flag_y</span> <span>=</span> <span>get_xy_with_padding</span><span>(</span><span>length</span><span>,</span> <span>angle</span><span>,</span> <span>0.1</span> <span>*</span> <span>length</span><span>)</span>
<span>text_x</span><span>,</span> <span>text_y</span> <span>=</span> <span>get_xy_with_padding</span><span>(</span><span>length</span><span>,</span> <span>angle</span><span>,</span> <span>16</span><span>*</span><span>flag_zoom</span><span>)</span>
<span># Add functions here </span><span>...</span>
<span>add_flag</span><span>(</span><span>ax</span><span>,</span> <span>flag_x</span><span>,</span> <span>flag_y</span><span>,</span> <span>row</span><span>.</span><span>country</span><span>,</span> <span>flag_zoom</span><span>,</span> <span>angle</span><span>)</span>
<span>add_text</span><span>(</span><span>ax</span><span>,</span> <span>text_x</span><span>,</span> <span>text_y</span><span>,</span> <span>row</span><span>.</span><span>country</span><span>,</span> <span>bar_length</span><span>,</span> <span>angle</span><span>)</span>
<span>bar_length</span> <span>=</span> <span>row</span><span>.</span><span>score</span>
<span>length</span> <span>=</span> <span>bar_length</span> <span>+</span> <span>INNER_PADDING</span>
<span>start</span> <span>=</span> <span>START_ANGLE</span> <span>+</span> <span>i</span><span>*</span><span>SIZE</span> <span>+</span> <span>PAD</span>
<span>end</span> <span>=</span> <span>START_ANGLE</span> <span>+</span> <span>(</span><span>i</span><span>+</span><span>1</span><span>)</span><span>*</span><span>SIZE</span>

<span># Add variables here </span><span>angle</span> <span>=</span> <span>(</span><span>end</span> <span>+</span> <span>start</span><span>)</span> <span>/</span> <span>2</span>
<span>flag_zoom</span> <span>=</span> <span>0.004</span> <span>*</span> <span>length</span>
<span>flag_x</span><span>,</span> <span>flag_y</span> <span>=</span> <span>get_xy_with_padding</span><span>(</span><span>length</span><span>,</span> <span>angle</span><span>,</span> <span>0.1</span> <span>*</span> <span>length</span><span>)</span>
<span>text_x</span><span>,</span> <span>text_y</span> <span>=</span> <span>get_xy_with_padding</span><span>(</span><span>length</span><span>,</span> <span>angle</span><span>,</span> <span>16</span><span>*</span><span>flag_zoom</span><span>)</span>

<span># Add functions here </span><span>...</span>
<span>add_flag</span><span>(</span><span>ax</span><span>,</span> <span>flag_x</span><span>,</span> <span>flag_y</span><span>,</span> <span>row</span><span>.</span><span>country</span><span>,</span> <span>flag_zoom</span><span>,</span> <span>angle</span><span>)</span>
<span>add_text</span><span>(</span><span>ax</span><span>,</span> <span>text_x</span><span>,</span> <span>text_y</span><span>,</span> <span>row</span><span>.</span><span>country</span><span>,</span> <span>bar_length</span><span>,</span> <span>angle</span><span>)</span>
bar_length = row.score length = bar_length + INNER_PADDING start = START_ANGLE + i*SIZE + PAD end = START_ANGLE + (i+1)*SIZE # Add variables here angle = (end + start) / 2 flag_zoom = 0.004 * length flag_x, flag_y = get_xy_with_padding(length, angle, 0.1 * length) text_x, text_y = get_xy_with_padding(length, angle, 16*flag_zoom) # Add functions here ... add_flag(ax, flag_x, flag_y, row.country, flag_zoom, angle) add_text(ax, text_x, text_y, row.country, bar_length, angle)

Enter fullscreen mode Exit fullscreen mode

Now we have the following graph, and it’s starting to look much better.

Now it’s time to tell the users what they are looking at.


Step 4: Adding information

We have added all the data. It’s time to make the chart readable by adding helpful information and guidance.

Drawing reference lines

An excellent type of visual helper is reference lines; they work just as well here as with standard bar charts.

The idea is to draw a line at a specific score, which indirectly helps us compare different countries.

Here’s my function to draw reference lines. I’m reusing the draw_wedge() function to draw a wedge from 0 to 360 degrees.

<span>def</span> <span>draw_reference_line</span><span>(</span><span>ax</span><span>,</span> <span>point</span><span>,</span> <span>size</span><span>,</span> <span>padding</span><span>,</span> <span>fontsize</span><span>=</span><span>18</span><span>):</span>
<span>draw_wedge</span><span>(</span><span>ax</span><span>,</span> <span>0</span><span>,</span> <span>360</span><span>,</span> <span>point</span><span>+</span><span>padding</span><span>+</span><span>size</span><span>/</span><span>2</span><span>,</span> <span>size</span><span>,</span> <span>background_color</span><span>)</span>
<span>ax</span><span>.</span><span>text</span><span>(</span><span>-</span><span>0.6</span><span>,</span> <span>padding</span> <span>+</span> <span>point</span><span>,</span> <span>point</span><span>,</span> <span>va</span><span>=</span><span>"</span><span>center</span><span>"</span><span>,</span> <span>rotation</span><span>=</span><span>1</span><span>,</span> <span>fontsize</span><span>=</span><span>fontsize</span><span>)</span>
<span>def</span> <span>draw_reference_line</span><span>(</span><span>ax</span><span>,</span> <span>point</span><span>,</span> <span>size</span><span>,</span> <span>padding</span><span>,</span> <span>fontsize</span><span>=</span><span>18</span><span>):</span>
    <span>draw_wedge</span><span>(</span><span>ax</span><span>,</span> <span>0</span><span>,</span> <span>360</span><span>,</span> <span>point</span><span>+</span><span>padding</span><span>+</span><span>size</span><span>/</span><span>2</span><span>,</span> <span>size</span><span>,</span> <span>background_color</span><span>)</span>
    <span>ax</span><span>.</span><span>text</span><span>(</span><span>-</span><span>0.6</span><span>,</span> <span>padding</span> <span>+</span> <span>point</span><span>,</span> <span>point</span><span>,</span> <span>va</span><span>=</span><span>"</span><span>center</span><span>"</span><span>,</span> <span>rotation</span><span>=</span><span>1</span><span>,</span> <span>fontsize</span><span>=</span><span>fontsize</span><span>)</span>
def draw_reference_line(ax, point, size, padding, fontsize=18): draw_wedge(ax, 0, 360, point+padding+size/2, size, background_color) ax.text(-0.6, padding + point, point, va="center", rotation=1, fontsize=fontsize)

Enter fullscreen mode Exit fullscreen mode

I run the function once for each score to draw multiple reference lines.

<span># Add general functions here </span><span>draw_reference_line</span><span>(</span><span>ax</span><span>,</span> <span>2.0</span><span>,</span> <span>0.05</span><span>,</span> <span>INNER_PADDING</span><span>)</span>
<span>draw_reference_line</span><span>(</span><span>ax</span><span>,</span> <span>4.0</span><span>,</span> <span>0.05</span><span>,</span> <span>INNER_PADDING</span><span>)</span>
<span>draw_reference_line</span><span>(</span><span>ax</span><span>,</span> <span>6.0</span><span>,</span> <span>0.05</span><span>,</span> <span>INNER_PADDING</span><span>)</span>
<span># Add general functions here </span><span>draw_reference_line</span><span>(</span><span>ax</span><span>,</span> <span>2.0</span><span>,</span> <span>0.05</span><span>,</span> <span>INNER_PADDING</span><span>)</span>
<span>draw_reference_line</span><span>(</span><span>ax</span><span>,</span> <span>4.0</span><span>,</span> <span>0.05</span><span>,</span> <span>INNER_PADDING</span><span>)</span>
<span>draw_reference_line</span><span>(</span><span>ax</span><span>,</span> <span>6.0</span><span>,</span> <span>0.05</span><span>,</span> <span>INNER_PADDING</span><span>)</span>
# Add general functions here draw_reference_line(ax, 2.0, 0.05, INNER_PADDING) draw_reference_line(ax, 4.0, 0.05, INNER_PADDING) draw_reference_line(ax, 6.0, 0.05, INNER_PADDING)

Enter fullscreen mode Exit fullscreen mode

Here’s the result.

It makes a significant difference.

Adding a title

The purpose of the gap in the center of the graph is to create a natural place for a title. Having the title in the center is unusual and can immediately capture a viewer’s interest.

The code for adding the title is standard Matplotlib functionality.

<span># Add general functions here </span><span>...</span>
<span>plt</span><span>.</span><span>title</span><span>(</span>
<span>"</span><span>World Happiness Report 2022</span><span>"</span><span>.</span><span>replace</span><span>(</span><span>"</span><span> </span><span>"</span><span>,</span> <span>"</span><span>\n</span><span>"</span><span>),</span>
<span>x</span><span>=</span><span>0.5</span><span>,</span> <span>y</span><span>=</span><span>0.5</span><span>,</span> <span>va</span><span>=</span><span>"</span><span>center</span><span>"</span><span>,</span> <span>ha</span><span>=</span><span>"</span><span>center</span><span>"</span><span>,</span>
<span>fontsize</span><span>=</span><span>64</span><span>,</span> <span>linespacing</span><span>=</span><span>1.5</span>
<span>)</span>
<span># Add general functions here </span><span>...</span>
<span>plt</span><span>.</span><span>title</span><span>(</span>
  <span>"</span><span>World Happiness Report 2022</span><span>"</span><span>.</span><span>replace</span><span>(</span><span>"</span><span> </span><span>"</span><span>,</span> <span>"</span><span>\n</span><span>"</span><span>),</span> 
  <span>x</span><span>=</span><span>0.5</span><span>,</span> <span>y</span><span>=</span><span>0.5</span><span>,</span> <span>va</span><span>=</span><span>"</span><span>center</span><span>"</span><span>,</span> <span>ha</span><span>=</span><span>"</span><span>center</span><span>"</span><span>,</span> 
  <span>fontsize</span><span>=</span><span>64</span><span>,</span> <span>linespacing</span><span>=</span><span>1.5</span>
<span>)</span>
# Add general functions here ... plt.title( "World Happiness Report 2022".replace(" ", "\n"), x=0.5, y=0.5, va="center", ha="center", fontsize=64, linespacing=1.5 )

Enter fullscreen mode Exit fullscreen mode

Here’s what it looks like.

It’s getting close, but we still have one more thing to do.

Adding a legend

There’s no way for the viewer to understand what the colors mean, but we can fix that by adding a legend.

To add a legend, I’ve created the following function that takes the labels to add, their colors, and a title.

<span>def</span> <span>add_legend</span><span>(</span><span>labels</span><span>,</span> <span>colors</span><span>,</span> <span>title</span><span>):</span>
<span>lines</span> <span>=</span> <span>[</span>
<span>Line2D</span><span>([],</span> <span>[],</span> <span>marker</span><span>=</span><span>'</span><span>o</span><span>'</span><span>,</span> <span>markersize</span><span>=</span><span>24</span><span>,</span> <span>linewidth</span><span>=</span><span>0</span><span>,</span> <span>color</span><span>=</span><span>c</span><span>)</span>
<span>for</span> <span>c</span> <span>in</span> <span>colors</span>
<span>]</span>
<span>plt</span><span>.</span><span>legend</span><span>(</span>
<span>lines</span><span>,</span> <span>labels</span><span>,</span>
<span>fontsize</span><span>=</span><span>18</span><span>,</span> <span>loc</span><span>=</span><span>"</span><span>upper left</span><span>"</span><span>,</span> <span>alignment</span><span>=</span><span>"</span><span>left</span><span>"</span><span>,</span>
<span>borderpad</span><span>=</span><span>1.3</span><span>,</span> <span>edgecolor</span><span>=</span><span>"</span><span>#E4C9C9</span><span>"</span><span>,</span> <span>labelspacing</span><span>=</span><span>1</span><span>,</span>
<span>facecolor</span><span>=</span><span>"</span><span>#F1E4E4</span><span>"</span><span>,</span> <span>framealpha</span><span>=</span><span>1</span><span>,</span> <span>borderaxespad</span><span>=</span><span>1</span><span>,</span>
<span>title</span><span>=</span><span>title</span><span>,</span> <span>title_fontsize</span><span>=</span><span>20</span><span>,</span>
<span>)</span>
<span>def</span> <span>add_legend</span><span>(</span><span>labels</span><span>,</span> <span>colors</span><span>,</span> <span>title</span><span>):</span>
    <span>lines</span> <span>=</span> <span>[</span>
        <span>Line2D</span><span>([],</span> <span>[],</span> <span>marker</span><span>=</span><span>'</span><span>o</span><span>'</span><span>,</span> <span>markersize</span><span>=</span><span>24</span><span>,</span> <span>linewidth</span><span>=</span><span>0</span><span>,</span> <span>color</span><span>=</span><span>c</span><span>)</span> 
        <span>for</span> <span>c</span> <span>in</span> <span>colors</span>
    <span>]</span>

    <span>plt</span><span>.</span><span>legend</span><span>(</span>
        <span>lines</span><span>,</span> <span>labels</span><span>,</span>
        <span>fontsize</span><span>=</span><span>18</span><span>,</span> <span>loc</span><span>=</span><span>"</span><span>upper left</span><span>"</span><span>,</span> <span>alignment</span><span>=</span><span>"</span><span>left</span><span>"</span><span>,</span>
        <span>borderpad</span><span>=</span><span>1.3</span><span>,</span> <span>edgecolor</span><span>=</span><span>"</span><span>#E4C9C9</span><span>"</span><span>,</span> <span>labelspacing</span><span>=</span><span>1</span><span>,</span>
        <span>facecolor</span><span>=</span><span>"</span><span>#F1E4E4</span><span>"</span><span>,</span> <span>framealpha</span><span>=</span><span>1</span><span>,</span> <span>borderaxespad</span><span>=</span><span>1</span><span>,</span>
        <span>title</span><span>=</span><span>title</span><span>,</span> <span>title_fontsize</span><span>=</span><span>20</span><span>,</span>
    <span>)</span>
def add_legend(labels, colors, title): lines = [ Line2D([], [], marker='o', markersize=24, linewidth=0, color=c) for c in colors ] plt.legend( lines, labels, fontsize=18, loc="upper left", alignment="left", borderpad=1.3, edgecolor="#E4C9C9", labelspacing=1, facecolor="#F1E4E4", framealpha=1, borderaxespad=1, title=title, title_fontsize=20, )

Enter fullscreen mode Exit fullscreen mode

I add the function under “Add general functions here” and run it together with everything else.

<span># Add general functions here </span><span>...</span>
<span>add_legend</span><span>(</span>
<span>labels</span><span>=</span><span>[</span><span>"</span><span>High income</span><span>"</span><span>,</span> <span>"</span><span>Upper middle income</span><span>"</span><span>,</span> <span>"</span><span>Lower middle income</span><span>"</span><span>,</span> <span>"</span><span>Low income</span><span>"</span><span>,</span> <span>"</span><span>Unknown</span><span>"</span><span>],</span>
<span>colors</span><span>=</span><span>[</span><span>"</span><span>#468FA8</span><span>"</span><span>,</span> <span>"</span><span>#62466B</span><span>"</span><span>,</span> <span>"</span><span>#E5625E</span><span>"</span><span>,</span> <span>"</span><span>#6B0F1A</span><span>"</span><span>,</span> <span>"</span><span>#909090</span><span>"</span><span>],</span>
<span>title</span><span>=</span><span>"</span><span>Income level according to the World Bank</span><span>\n</span><span>"</span>
<span>)</span>
<span># Add general functions here </span><span>...</span>

<span>add_legend</span><span>(</span>
    <span>labels</span><span>=</span><span>[</span><span>"</span><span>High income</span><span>"</span><span>,</span> <span>"</span><span>Upper middle income</span><span>"</span><span>,</span> <span>"</span><span>Lower middle income</span><span>"</span><span>,</span> <span>"</span><span>Low income</span><span>"</span><span>,</span> <span>"</span><span>Unknown</span><span>"</span><span>],</span>
    <span>colors</span><span>=</span><span>[</span><span>"</span><span>#468FA8</span><span>"</span><span>,</span> <span>"</span><span>#62466B</span><span>"</span><span>,</span> <span>"</span><span>#E5625E</span><span>"</span><span>,</span> <span>"</span><span>#6B0F1A</span><span>"</span><span>,</span> <span>"</span><span>#909090</span><span>"</span><span>],</span>
    <span>title</span><span>=</span><span>"</span><span>Income level according to the World Bank</span><span>\n</span><span>"</span>
<span>)</span>
# Add general functions here ... add_legend( labels=["High income", "Upper middle income", "Lower middle income", "Low income", "Unknown"], colors=["#468FA8", "#62466B", "#E5625E", "#6B0F1A", "#909090"], title="Income level according to the World Bank\n" )

Enter fullscreen mode Exit fullscreen mode

The final result looks like this.

That’s it. We have recreated the beautiful polar histogram you saw at the top.

Your entire main block of code should now look like this.

<span>fig</span><span>,</span> <span>ax</span> <span>=</span> <span>plt</span><span>.</span><span>subplots</span><span>(</span><span>nrows</span><span>=</span><span>1</span><span>,</span> <span>ncols</span><span>=</span><span>1</span><span>,</span> <span>figsize</span><span>=</span><span>(</span><span>30</span><span>,</span> <span>30</span><span>))</span>
<span>ax</span><span>.</span><span>set</span><span>(</span><span>xlim</span><span>=</span><span>(</span><span>-</span><span>LIMIT</span><span>,</span> <span>LIMIT</span><span>),</span> <span>ylim</span><span>=</span><span>(</span><span>-</span><span>LIMIT</span><span>,</span> <span>LIMIT</span><span>))</span>
<span>for</span> <span>i</span><span>,</span> <span>row</span> <span>in</span> <span>df</span><span>.</span><span>iterrows</span><span>():</span>
<span>bar_length</span> <span>=</span> <span>row</span><span>.</span><span>score</span>
<span>length</span> <span>=</span> <span>bar_length</span> <span>+</span> <span>INNER_PADDING</span>
<span>start</span> <span>=</span> <span>START_ANGLE</span> <span>+</span> <span>i</span><span>*</span><span>SIZE</span> <span>+</span> <span>PAD</span>
<span>end</span> <span>=</span> <span>START_ANGLE</span> <span>+</span> <span>(</span><span>i</span><span>+</span><span>1</span><span>)</span><span>*</span><span>SIZE</span>
<span>angle</span> <span>=</span> <span>(</span><span>end</span> <span>+</span> <span>start</span><span>)</span> <span>/</span> <span>2</span>
<span># Add variables here </span> <span>flag_zoom</span> <span>=</span> <span>0.004</span> <span>*</span> <span>length</span>
<span>flag_x</span><span>,</span> <span>flag_y</span> <span>=</span> <span>get_xy_with_padding</span><span>(</span><span>length</span><span>,</span> <span>angle</span><span>,</span> <span>8</span><span>*</span><span>flag_zoom</span><span>)</span>
<span>text_x</span><span>,</span> <span>text_y</span> <span>=</span> <span>get_xy_with_padding</span><span>(</span><span>length</span><span>,</span> <span>angle</span><span>,</span> <span>16</span><span>*</span><span>flag_zoom</span><span>)</span>
<span># Add functions here </span> <span>draw_wedge</span><span>(</span><span>ax</span><span>,</span> <span>start</span><span>,</span> <span>end</span><span>,</span> <span>length</span><span>,</span> <span>bar_length</span><span>,</span> <span>color</span><span>(</span><span>row</span><span>.</span><span>income</span><span>))</span>
<span>add_flag</span><span>(</span><span>ax</span><span>,</span> <span>flag_x</span><span>,</span> <span>flag_y</span><span>,</span> <span>row</span><span>.</span><span>country</span><span>,</span> <span>flag_zoom</span><span>,</span> <span>angle</span><span>)</span>
<span>add_text</span><span>(</span><span>ax</span><span>,</span> <span>text_x</span><span>,</span> <span>text_y</span><span>,</span> <span>row</span><span>.</span><span>country</span><span>,</span> <span>bar_length</span><span>,</span> <span>angle</span><span>)</span>
<span>ax</span><span>.</span><span>text</span><span>(</span><span>1</span><span>-</span><span>LIMIT</span><span>,</span> <span>LIMIT</span><span>-</span><span>2</span><span>,</span> <span>"</span><span>+ main title</span><span>"</span><span>,</span> <span>fontsize</span><span>=</span><span>58</span><span>)</span>
<span># Add general functions here </span><span>draw_reference_line</span><span>(</span><span>ax</span><span>,</span> <span>2.0</span><span>,</span> <span>0.06</span><span>,</span> <span>INNER_PADDING</span><span>)</span>
<span>draw_reference_line</span><span>(</span><span>ax</span><span>,</span> <span>4.0</span><span>,</span> <span>0.06</span><span>,</span> <span>INNER_PADDING</span><span>)</span>
<span>draw_reference_line</span><span>(</span><span>ax</span><span>,</span> <span>6.0</span><span>,</span> <span>0.06</span><span>,</span> <span>INNER_PADDING</span><span>)</span>
<span>plt</span><span>.</span><span>title</span><span>(</span><span>"</span><span>World Happiness Report 2022</span><span>"</span><span>.</span><span>replace</span><span>(</span><span>"</span><span> </span><span>"</span><span>,</span> <span>"</span><span>\n</span><span>"</span><span>),</span> <span>x</span><span>=</span><span>0.5</span><span>,</span> <span>y</span><span>=</span><span>0.5</span><span>,</span> <span>va</span><span>=</span><span>"</span><span>center</span><span>"</span><span>,</span> <span>ha</span><span>=</span><span>"</span><span>center</span><span>"</span><span>,</span> <span>fontsize</span><span>=</span><span>64</span><span>,</span> <span>linespacing</span><span>=</span><span>1.5</span><span>)</span>
<span>add_legend</span><span>(</span>
<span>labels</span><span>=</span><span>[</span><span>"</span><span>High income</span><span>"</span><span>,</span> <span>"</span><span>Upper middle income</span><span>"</span><span>,</span> <span>"</span><span>Lower middle income</span><span>"</span><span>,</span> <span>"</span><span>Low income</span><span>"</span><span>,</span> <span>"</span><span>Unknown</span><span>"</span><span>],</span>
<span>colors</span><span>=</span><span>[</span><span>"</span><span>#468FA8</span><span>"</span><span>,</span> <span>"</span><span>#62466B</span><span>"</span><span>,</span> <span>"</span><span>#E5625E</span><span>"</span><span>,</span> <span>"</span><span>#6B0F1A</span><span>"</span><span>,</span> <span>"</span><span>#909090</span><span>"</span><span>],</span>
<span>title</span><span>=</span><span>"</span><span>Income level according to the World Bank</span><span>\n</span><span>"</span>
<span>)</span>
<span>plt</span><span>.</span><span>axis</span><span>(</span><span>"</span><span>off</span><span>"</span><span>)</span>
<span>plt</span><span>.</span><span>tight_layout</span><span>()</span>
<span>plt</span><span>.</span><span>show</span><span>()</span>
<span>fig</span><span>,</span> <span>ax</span> <span>=</span> <span>plt</span><span>.</span><span>subplots</span><span>(</span><span>nrows</span><span>=</span><span>1</span><span>,</span> <span>ncols</span><span>=</span><span>1</span><span>,</span> <span>figsize</span><span>=</span><span>(</span><span>30</span><span>,</span> <span>30</span><span>))</span>
<span>ax</span><span>.</span><span>set</span><span>(</span><span>xlim</span><span>=</span><span>(</span><span>-</span><span>LIMIT</span><span>,</span> <span>LIMIT</span><span>),</span> <span>ylim</span><span>=</span><span>(</span><span>-</span><span>LIMIT</span><span>,</span> <span>LIMIT</span><span>))</span>

<span>for</span> <span>i</span><span>,</span> <span>row</span> <span>in</span> <span>df</span><span>.</span><span>iterrows</span><span>():</span>
    <span>bar_length</span> <span>=</span> <span>row</span><span>.</span><span>score</span>
    <span>length</span> <span>=</span> <span>bar_length</span> <span>+</span> <span>INNER_PADDING</span>
    <span>start</span> <span>=</span> <span>START_ANGLE</span> <span>+</span> <span>i</span><span>*</span><span>SIZE</span> <span>+</span> <span>PAD</span>
    <span>end</span> <span>=</span> <span>START_ANGLE</span> <span>+</span> <span>(</span><span>i</span><span>+</span><span>1</span><span>)</span><span>*</span><span>SIZE</span>
    <span>angle</span> <span>=</span> <span>(</span><span>end</span> <span>+</span> <span>start</span><span>)</span> <span>/</span> <span>2</span>

    <span># Add variables here </span>    <span>flag_zoom</span> <span>=</span> <span>0.004</span> <span>*</span> <span>length</span>
    <span>flag_x</span><span>,</span> <span>flag_y</span> <span>=</span> <span>get_xy_with_padding</span><span>(</span><span>length</span><span>,</span> <span>angle</span><span>,</span> <span>8</span><span>*</span><span>flag_zoom</span><span>)</span>
    <span>text_x</span><span>,</span> <span>text_y</span> <span>=</span> <span>get_xy_with_padding</span><span>(</span><span>length</span><span>,</span> <span>angle</span><span>,</span> <span>16</span><span>*</span><span>flag_zoom</span><span>)</span>

    <span># Add functions here </span>    <span>draw_wedge</span><span>(</span><span>ax</span><span>,</span> <span>start</span><span>,</span> <span>end</span><span>,</span> <span>length</span><span>,</span> <span>bar_length</span><span>,</span> <span>color</span><span>(</span><span>row</span><span>.</span><span>income</span><span>))</span>
    <span>add_flag</span><span>(</span><span>ax</span><span>,</span> <span>flag_x</span><span>,</span> <span>flag_y</span><span>,</span> <span>row</span><span>.</span><span>country</span><span>,</span> <span>flag_zoom</span><span>,</span> <span>angle</span><span>)</span>
    <span>add_text</span><span>(</span><span>ax</span><span>,</span> <span>text_x</span><span>,</span> <span>text_y</span><span>,</span> <span>row</span><span>.</span><span>country</span><span>,</span> <span>bar_length</span><span>,</span> <span>angle</span><span>)</span>

<span>ax</span><span>.</span><span>text</span><span>(</span><span>1</span><span>-</span><span>LIMIT</span><span>,</span> <span>LIMIT</span><span>-</span><span>2</span><span>,</span> <span>"</span><span>+ main title</span><span>"</span><span>,</span> <span>fontsize</span><span>=</span><span>58</span><span>)</span>

<span># Add general functions here </span><span>draw_reference_line</span><span>(</span><span>ax</span><span>,</span> <span>2.0</span><span>,</span> <span>0.06</span><span>,</span> <span>INNER_PADDING</span><span>)</span>
<span>draw_reference_line</span><span>(</span><span>ax</span><span>,</span> <span>4.0</span><span>,</span> <span>0.06</span><span>,</span> <span>INNER_PADDING</span><span>)</span>
<span>draw_reference_line</span><span>(</span><span>ax</span><span>,</span> <span>6.0</span><span>,</span> <span>0.06</span><span>,</span> <span>INNER_PADDING</span><span>)</span>
<span>plt</span><span>.</span><span>title</span><span>(</span><span>"</span><span>World Happiness Report 2022</span><span>"</span><span>.</span><span>replace</span><span>(</span><span>"</span><span> </span><span>"</span><span>,</span> <span>"</span><span>\n</span><span>"</span><span>),</span> <span>x</span><span>=</span><span>0.5</span><span>,</span> <span>y</span><span>=</span><span>0.5</span><span>,</span> <span>va</span><span>=</span><span>"</span><span>center</span><span>"</span><span>,</span> <span>ha</span><span>=</span><span>"</span><span>center</span><span>"</span><span>,</span> <span>fontsize</span><span>=</span><span>64</span><span>,</span> <span>linespacing</span><span>=</span><span>1.5</span><span>)</span>

<span>add_legend</span><span>(</span>
    <span>labels</span><span>=</span><span>[</span><span>"</span><span>High income</span><span>"</span><span>,</span> <span>"</span><span>Upper middle income</span><span>"</span><span>,</span> <span>"</span><span>Lower middle income</span><span>"</span><span>,</span> <span>"</span><span>Low income</span><span>"</span><span>,</span> <span>"</span><span>Unknown</span><span>"</span><span>],</span>
    <span>colors</span><span>=</span><span>[</span><span>"</span><span>#468FA8</span><span>"</span><span>,</span> <span>"</span><span>#62466B</span><span>"</span><span>,</span> <span>"</span><span>#E5625E</span><span>"</span><span>,</span> <span>"</span><span>#6B0F1A</span><span>"</span><span>,</span> <span>"</span><span>#909090</span><span>"</span><span>],</span>
    <span>title</span><span>=</span><span>"</span><span>Income level according to the World Bank</span><span>\n</span><span>"</span>
<span>)</span>

<span>plt</span><span>.</span><span>axis</span><span>(</span><span>"</span><span>off</span><span>"</span><span>)</span>
<span>plt</span><span>.</span><span>tight_layout</span><span>()</span>
<span>plt</span><span>.</span><span>show</span><span>()</span>
fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(30, 30)) ax.set(xlim=(-LIMIT, LIMIT), ylim=(-LIMIT, LIMIT)) for i, row in df.iterrows(): bar_length = row.score length = bar_length + INNER_PADDING start = START_ANGLE + i*SIZE + PAD end = START_ANGLE + (i+1)*SIZE angle = (end + start) / 2 # Add variables here flag_zoom = 0.004 * length flag_x, flag_y = get_xy_with_padding(length, angle, 8*flag_zoom) text_x, text_y = get_xy_with_padding(length, angle, 16*flag_zoom) # Add functions here draw_wedge(ax, start, end, length, bar_length, color(row.income)) add_flag(ax, flag_x, flag_y, row.country, flag_zoom, angle) add_text(ax, text_x, text_y, row.country, bar_length, angle) ax.text(1-LIMIT, LIMIT-2, "+ main title", fontsize=58) # Add general functions here draw_reference_line(ax, 2.0, 0.06, INNER_PADDING) draw_reference_line(ax, 4.0, 0.06, INNER_PADDING) draw_reference_line(ax, 6.0, 0.06, INNER_PADDING) plt.title("World Happiness Report 2022".replace(" ", "\n"), x=0.5, y=0.5, va="center", ha="center", fontsize=64, linespacing=1.5) add_legend( labels=["High income", "Upper middle income", "Lower middle income", "Low income", "Unknown"], colors=["#468FA8", "#62466B", "#E5625E", "#6B0F1A", "#909090"], title="Income level according to the World Bank\n" ) plt.axis("off") plt.tight_layout() plt.show()

Enter fullscreen mode Exit fullscreen mode

That’s it for this tutorial; congratulations on reaching the end.


Conclusion

Today, we learned to create a beautiful polar histogram using Matplotlib and Python.

Polar histograms are surprisingly easy to create, allowing us to cram more information into a single chart.

I used the World Happiness Report in this tutorial, but you can change it to another inspiring dataset.

I hope you learned a few techniques to help you bring your chart ideas to life.

原文链接:How to Create a Beautiful Polar Histogram With Python and Matplotlib

© 版权声明
THE END
喜欢就支持一下吧
点赞7 分享
The best hearts are always the bravest.
心灵最高尚的人也总是最勇敢的人
评论 抢沙发

请登录后发表评论

    暂无评论内容