Plotting a Pie Chart with Gnuplot and Org Mode

Menu

Recent versions of GnuPlot support drawing arcs and the feature can be used to plot pie charts.

The process can be greatly simplified by using Org Mode and a source block to transform the values to be plotted into percentages and arcs (or radians).

Here we show how.

Input Data: World Greenhouse Gas Emissions

We take the Greenhouse Gas Emissions by Sector from Our World in Data. The table shows World emission from 2020 and is expressed in tons.

Entity World
Greenhouse gas emissions from agriculture 5865470500
Greenhouse gas emissions from land use change and forestry 1392230000
Greenhouse gas emissions from waste 1652870000
Greenhouse gas emissions from buildings 2980520000
Greenhouse gas emissions from industry 3126930000
Greenhouse gas emissions from manufacturing and construction 6223020000
Greenhouse gas emissions from transport 7288009700
Greenhouse gas emissions from electricity and heat 15181350000
Fugitive emissions of greenhouse gases from energy production 3223690000
Greenhouse gas emissions from other fuel combustion 579050000
Greenhouse gas emissions from bunker fuels 938140000

Preprocessing

We use a source block in Ruby which preprocess the table and adds information useful for plotting in GnuPlot. Here we export both code and result; in general you might want to set :exports none

We change the label and add various columns:

  • Label: we add a \n and the percentage
  • Percentage (not really needed for the plot)
  • Start angle
  • End angle
  • Coordinates for label: we take the angle between start and end and then use trigonometric functions to compute the Cartesian coordinates
  • Color (taken from a colors map, if you want to use your own)

… and we make it very simple. More complex elaborations could, for instance, aggregate entries with low percentages, to make the chart easier to read.

# we repeat as necessary
colors = [0x8FCA74, 0x5470C6, 0xFAC858, 0xEE6666, 0x73C0DE, 0x3BA272, 0xFC8452, 0x9A60B4]

total = world_emissions.map { |x| x[1] }.sum

# Note that computations are not rounded, but data is presented rounded

start_angle = 0 # start from top and move clockwise
radius = 100    # how big of a circly do you want?

enh_table = world_emissions.each_with_index.map do |data, index|
  perc = data[1].to_f / total
  perc_100 = perc * 100
  fraction = 360 * perc
  end_angle = start_angle + fraction
  mid_angle = (end_angle - start_angle) / 2.0
  color = colors[index % colors.size]

  # we take the mid angle between arc_end and arc_begin which is: (arc_end + arc_begin) / 2
  # then we convert from polar to rectangular:  2 * PI / 360. In the formulas, we simplify the "2"
  cartesian_x = (1.3 * radius * Math.cos(((end_angle + start_angle) * Math::PI) / 360)).round(0)
  cartesian_y = (1.3 * radius * Math.sin(((end_angle + start_angle) * Math::PI) / 360)).round(0)

  entry = [
    data[0] + "\n(#{perc_100.round(0)}%)", data[1],
    perc.round(2), perc_100.round(2),
    0, 0, radius, start_angle.round(0), end_angle.round(0),
    color,
    cartesian_x, cartesian_y
  ]

  start_angle = end_angle

  entry
end

# Add header (nil separates header from data)
# Nice thing about header is that it is removed from data when assigning vars in Source Blocks
[["Label", "Emissions (t)", "Perc [0..1]", "Perc %", "Center X", "Center Y", "Radius", "Start Angle", "End Angle", "Color", "x", "y"]] +
  [nil] +
  enh_table

Pie Chart

We are now ready to plot the table. The syntax is described at page 76 of the GnuPlot manual (version 6.0). The Pie Chart starts from 0 degrees and develops counterclockwise

pie_chart.svg

Pie Chart, Starting from 90 degrees

If you prefer to start from the Y-axis, you need to take the modulus, so that degrees never exceed 360.

# we repeat as necessary
colors = [0x8FCA74, 0x5470C6, 0xFAC858, 0xEE6666, 0x73C0DE, 0x3BA272, 0xFC8452, 0x9A60B4]

total = world_emissions.map { |x| x[1] }.sum

# Note that computations are not rounded, but data is presented rounded

start_angle = 90 # start from top and move clockwise
radius = 100    # how big of a circly do you want?

enh_table = world_emissions.each_with_index.map do |data, index|
  perc = data[1].to_f / total
  perc_100 = perc * 100
  fraction = 360 * perc
  end_angle = (start_angle + fraction) % 360
  mid_angle = ((start_angle + fraction + start_angle) / 2.0) % 360
  color = colors[index % colors.size]

  # we take the mid angle between arc_end and arc_begin which is: (arc_end + arc_begin) / 2
  # then we convert from polar to rectangular:  2 * PI / 360. In the formulas, we simplify the "2"
  cartesian_x = (1.3 * radius * Math.cos((mid_angle * 2 * Math::PI) / 360)).round(0)
  cartesian_y = (1.3 * radius * Math.sin((mid_angle * 2 * Math::PI) / 360)).round(0)

  entry = [
    data[0] + "\n(#{perc_100.round(0)}%)", data[1],
    perc.round(2), perc_100.round(2),
    0, 0, radius, start_angle.round(0), end_angle.round(0),
    color,
    cartesian_x, cartesian_y
  ]

  start_angle = end_angle

  entry
end

# Add header (nil separates header from data)
# Nice thing about header is that it is removed from data when assigning vars in Source Blocks
[["Label", "Emissions (t)", "Perc [0..1]", "Perc %", "Center X", "Center Y", "Radius", "Start Angle", "End Angle", "Color", "x", "y"]] +
  [nil] +
  enh_table

pie_chart_from_90.svg